Merge "Add tests for DefaultQuotaBackend"
diff --git a/.gitmodules b/.gitmodules
index 8d75bcc..4ce7b5f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -8,6 +8,11 @@
 	url = ../plugins/commit-message-length-validator
 	branch = .
 
+[submodule "plugins/delete-project"]
+	path = plugins/delete-project
+	url = ../plugins/delete-project
+	branch = .
+
 [submodule "plugins/download-commands"]
 	path = plugins/download-commands
 	url = ../plugins/download-commands
@@ -32,3 +37,8 @@
 	path = plugins/singleusergroup
 	url = ../plugins/singleusergroup
 	branch = .
+
+[submodule "plugins/webhooks"]
+	path = plugins/webhooks
+	url = ../plugins/webhooks
+	branch = .
diff --git a/BUILD b/BUILD
index 5ddf8b1..f0b1954 100644
--- a/BUILD
+++ b/BUILD
@@ -23,7 +23,6 @@
     cmd = ("cat bazel-out/volatile-status.txt bazel-out/stable-status.txt | " +
            "grep STABLE_BUILD_GERRIT_LABEL | cut -d ' ' -f 2 > $@"),
     stamp = 1,
-    visibility = ["//visibility:public"],
 )
 
 genrule(
@@ -31,7 +30,6 @@
     srcs = ["//Documentation:licenses.txt"],
     outs = ["LICENSES.txt"],
     cmd = "cp $< $@",
-    visibility = ["//visibility:public"],
 )
 
 pkg_war(
diff --git a/Documentation/BUILD b/Documentation/BUILD
index b322fef..8a7a313 100644
--- a/Documentation/BUILD
+++ b/Documentation/BUILD
@@ -40,7 +40,6 @@
         ":prettify_files",
         "//:LICENSES.txt",
     ],
-    visibility = ["//visibility:public"],
 )
 
 license_map(
@@ -50,7 +49,6 @@
         "//polygerrit-ui/app:polygerrit_ui",
         "//java/com/google/gerrit/pgm",
     ],
-    visibility = ["//visibility:public"],
 )
 
 license_map(
@@ -58,7 +56,6 @@
     targets = [
         "//polygerrit-ui/app:polygerrit_ui",
     ],
-    visibility = ["//visibility:public"],
 )
 
 DOC_DIR = "Documentation"
@@ -86,7 +83,6 @@
     srcs = SRCS,
     attributes = documentation_attributes(),
     backend = "html5",
-    visibility = ["//visibility:public"],
 )
 
 genasciidoc_zip(
@@ -95,7 +91,6 @@
     attributes = documentation_attributes(),
     backend = "html5",
     directory = DOC_DIR,
-    visibility = ["//visibility:public"],
 )
 
 genasciidoc_zip(
@@ -105,5 +100,4 @@
     backend = "html5",
     directory = DOC_DIR,
     searchbox = False,
-    visibility = ["//visibility:public"],
 )
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 13e3a53..1d1f17e 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -1204,8 +1204,7 @@
 [[capability_accessDatabase]]
 === Access Database
 
-Allow users to access the database using the `gsql` command, and view code
-review metadata refs in repositories.
+Allow users to view code review metadata refs in repositories.
 
 
 [[capability_administrateServer]]
@@ -1362,6 +1361,12 @@
 command, but also to the web UI results pagination size.
 
 
+[[capability_readAs]]
+=== Read As
+
+Allow users to impersonate any user to see which refs they can read.
+
+
 [[capability_runAs]]
 === Run As
 
diff --git a/Documentation/cmd-gsql.txt b/Documentation/cmd-gsql.txt
deleted file mode 100644
index 7f2aaf7..0000000
--- a/Documentation/cmd-gsql.txt
+++ /dev/null
@@ -1,64 +0,0 @@
-= gerrit gsql
-
-== NAME
-gerrit gsql - Administrative interface to active database.
-
-== SYNOPSIS
-[verse]
---
-_ssh_ -p <port> <host> _gerrit gsql_
-  [--format {PRETTY | JSON | JSON_SINGLE}]
-  [-c QUERY]
---
-
-== DESCRIPTION
-Provides interactive query support directly against the underlying
-SQL database used by the host Gerrit server.  All SQL statements
-are supported, including SELECT, UPDATE, INSERT, DELETE and ALTER.
-
-== OPTIONS
---format::
-	Set the format records are output in.  In PRETTY (the
-	default) records are displayed in a tabular output suitable
-	for reading by a human on a sufficiently wide terminal.
-	In JSON mode records are output as JSON objects using the
-	column names as the property names, one object per line.
-	In JSON_SINGLE mode the whole result set is output as a
-	single JSON object.
-
--c::
-	Execute the single query statement supplied, and then exit.
-
-== ACCESS
-Caller must have been granted the
-link:access-control.html#capability_accessDatabase[Access Database]
-global capability.
-
-== SCRIPTING
-Intended for interactive use only, unless format is JSON, or
-JSON_SINGLE.
-
-== EXAMPLES
-To manually correct a user's SSH user name:
-
-----
-$ ssh -p 29418 review.example.com gerrit gsql
-Welcome to Gerrit Code Review v2.0.25
-(PostgreSQL 8.3.8)
-
-Type '\h' for help.  Type '\r' to clear the buffer.
-
-gerrit> update accounts set ssh_user_name = 'alice' where account_id=1;
-UPDATE 1; 1 ms
-gerrit> \q
-Bye
-
-$ ssh -p 29418 review.example.com gerrit flush-caches --cache sshkeys --cache accounts
-----
-
-GERRIT
-------
-Part of link:index.html[Gerrit Code Review]
-
-SEARCHBOX
----------
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 25099fa..e999218 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -130,9 +130,6 @@
 link:cmd-gc.html[gerrit gc]::
 	Run the Git garbage collection.
 
-link:cmd-gsql.html[gerrit gsql]::
-	Administrative interface to active database.
-
 link:cmd-index-activate.html[gerrit index activate]::
 	Activate the latest index version available.
 
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 6362597..037731d 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -693,7 +693,9 @@
 allows to limit the memory used by H2 and thus prevent out-of-memory
 caused by the H2 database using too much memory.
 +
-See <<database.h2.cacheSize,database.h2.cacheSize>> for a detailed discussion.
+Technically the H2 cache size is configured using the CACHE_SIZE parameter in
+the H2 JDBC connection URL, as described
+link:http://www.h2database.com/html/features.html#cache_settings[here]
 +
 Default is unset, using up to half of the available memory.
 +
@@ -1677,215 +1679,6 @@
 +
 Default is 1 hour.
 
-[[database]]
-=== Section database
-
-The database section configures ReviewDb, where Gerrit stores its metadata
-records about account groups and change reviews. Starting from 2.15, accounts
-are always stored in NoteDb and, optionally, changes too. See the
-link:note-db.html[NoteDb documentation] for more information.
-
-Note that user file reviewed flags are stored in a separate database. See the
-<<accountPatchReviewDb,accountPatchReviewDb>> section for more information.
-
-----
-[database]
-  type = POSTGRESQL
-  hostname = localhost
-  database = reviewdb
-  username = gerrit
-  password = s3kr3t
-----
-
-[[database.type]]database.type::
-+
-Type of database server to connect to.  If set this value will be
-used to automatically create correct database.driver and database.url
-values to open the connection.
-+
-* `DB2`
-+
-Connect to a DB2 database server.
-+
-* `DERBY`
-+
-Connect to an Apache Derby database server.
-+
-* `H2`
-+
-Connect to a local embedded H2 database.
-+
-* `JDBC`
-+
-Connect using a JDBC driver class name and URL.
-+
-* `MAXDB`
-+
-Connect to an SAP MaxDB database server.
-+
-* `MYSQL`
-+
-Connect to a MySQL database server.
-+
-* `MARIADB`
-+
-Connect to a MariaDB database server.
-+
-* `ORACLE`
-+
-Connect to an Oracle database server.
-+
-* `POSTGRESQL`
-+
-Connect to a PostgreSQL database server.
-
-+
-If not specified, database.driver and database.url are used as-is,
-and if they are also not specified, defaults to H2.
-
-[[database.hostname]]database.hostname::
-+
-Hostname of the database server.  Defaults to 'localhost'.
-
-[[database.port]]database.port::
-+
-Port number of the database server.  Defaults to the default port
-of the server named by database.type.
-
-[[database.database]]database.database::
-+
-For POSTGRESQL or MYSQL, the name of the database on the server.
-+
-For H2, this is the path to the database, and if not absolute is
-relative to `'$site_path'`.
-
-[[database.username]]database.username::
-+
-Username to connect to the database server as.
-
-[[database.password]]database.password::
-+
-Password to authenticate to the database server with.
-
-[[database.driver]]database.driver::
-+
-Name of the JDBC driver class to connect to the database with.
-Setting this usually isn't necessary as it can be derived from
-database.type or database.url for any supported database.
-
-[[database.url]]database.url::
-+
-'jdbc:' URL for the database.  Setting this variable usually
-isn't necessary as it can be constructed from the all of the
-above properties.
-
-[[database.connectionPool]]database.connectionPool::
-+
-If true, use connection pooling for database connections. Otherwise, a
-new database connection is opened for each request.
-+
-Default is false for MySQL, and true for other database backends.
-
-[[database.poolLimit]]database.poolLimit::
-+
-Maximum number of open database connections.  If the server needs
-more than this number, request processing threads will wait up
-to <<database.poolMaxWait, poolMaxWait>> seconds for a
-connection to be released before they abort with an exception.
-This limit must be several units higher than the total number of
-httpd and sshd threads as some request processing code paths may
-need multiple connections.
-+
-Default is <<sshd.threads, sshd.threads>>
- + <<httpd.maxThreads, httpd.maxThreads>> + 2.
-+
-This setting only applies if
-<<database.connectionPool,database.connectionPool>> is true.
-
-[[database.poolMinIdle]]database.poolMinIdle::
-+
-Minimum number of connections to keep idle in the pool.
-Default is 4.
-+
-This setting only applies if
-<<database.connectionPool,database.connectionPool>> is true.
-
-[[database.poolMaxIdle]]database.poolMaxIdle::
-+
-Maximum number of connections to keep idle in the pool.  If there
-are more idle connections, connections will be closed instead of
-being returned back to the pool.
-Default is min(<<database.poolLimit, database.poolLimit>>, 16).
-+
-This setting only applies if
-<<database.connectionPool,database.connectionPool>> is true.
-
-[[database.poolMaxWait]]database.poolMaxWait::
-+
-Maximum amount of time a request processing thread will wait to
-acquire a database connection from the pool.  If no connection is
-released within this time period, the processing thread will abort
-its current operations and return an error to the client.
-Values should use common unit suffixes to express their setting:
-+
-* ms, milliseconds
-* s, sec, second, seconds
-* m, min, minute, minutes
-* h, hr, hour, hours
-
-+
---
-If a unit suffix is not specified, `milliseconds` is assumed.
-
-Default is `30 seconds`.
-
-This setting only applies if
-<<database.connectionPool,database.connectionPool>> is true.
---
-
-[[database.dataSourceInterceptorClass]]database.dataSourceInterceptorClass::
-
-Class that implements DataSourceInterceptor interface to monitor SQL activity.
-This class must have default constructor and be available on Gerrit's bootstrap
-classpath, e. g. in `$gerrit_site/lib` directory. Example implementation of
-SQL monitoring can be found in javamelody-plugin.
-
-[[database.h2]]database.h2::
-+
-The settings in this section are used for the reviewdb if the
-<<database.type,database.type>> is H2.
-+
-Additionally gerrit uses H2 for storing reviewed flags on changes.
-
-[[database.h2.cacheSize]]database.h2.cacheSize::
-+
-The size of the H2 internal database cache, in bytes. The H2 internal cache for
-persistent H2-backed caches is controlled by
-<<cache.h2CacheSize,cache.h2CacheSize>>.
-+
-H2 uses memory to cache its database content. The parameter `cacheSize`
-allows to limit the memory used by H2 and thus prevent out-of-memory
-caused by the H2 database using too much memory.
-+
-Technically the H2 cache size is configured using the CACHE_SIZE parameter in
-the H2 JDBC connection URL, as described
-link:http://www.h2database.com/html/features.html#cache_settings[here]
-+
-Default is unset, using up to half of the available memory.
-+
-H2 will persist this value in the database, so to unset explicitly specify 0.
-+
-Common unit suffixes of 'k', 'm', or 'g' are supported.
-
-[[database.h2.autoServer]]database.h2.autoServer::
-+
-If `true` enable the automatic mixed mode
-(see link:http://www.h2database.com/html/features.html#auto_mixed_mode[Automatic Mixed Mode]).
-This enables concurrent access to the embedded H2 database from command line
-utils (e.g. MigrateToNoteDb).
-+
-Default is `false`.
-
 [[download]]
 === Section download
 
@@ -2979,7 +2772,7 @@
 for production use. For compatibility information, please refer to the
 link:https://www.gerritcodereview.com/elasticsearch.html[project homepage].
 
-When using Elasticsearch versions 2.4 and 5.6, the open and closed changes are
+When using Elasticsearch version 5.6, the open and closed changes are
 indexed in a single index, separated into types `open_changes` and `closed_changes`
 respectively. When using version 6.2 or later, the open and closed changes are
 merged into the default `_doc` type. The latter is also used for the accounts and
@@ -3016,6 +2809,22 @@
 +
 Defaults to `30000 ms`.
 
+[[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/_basic_concepts.html#getting-started-shards-and-replicas[
+Elasticsearch documentation] for details.
++
+Defaults to 5.
+
+[[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/_basic_concepts.html#getting-started-shards-and-replicas[
+Elasticsearch documentation] for details.
++
+Defaults to 1.
+
 ==== Elasticsearch Security
 
 When security is enabled in Elasticsearch, the username and password must be provided.
@@ -3023,11 +2832,11 @@
 
 For further information about Elasticsearch security, please refer to the documentation:
 
-* link:https://www.elastic.co/guide/en/elasticsearch/plugins/2.4/security.html[Elasticsearch 2.4]
 * link:https://www.elastic.co/guide/en/x-pack/5.6/security-getting-started.html[Elasticsearch 5.6]
 * link:https://www.elastic.co/guide/en/x-pack/6.2/security-getting-started.html[Elasticsearch 6.2]
 * link:https://www.elastic.co/guide/en/elastic-stack-overview/6.3/security-getting-started.html[Elasticsearch 6.3]
 * link:https://www.elastic.co/guide/en/elastic-stack-overview/6.4/security-getting-started.html[Elasticsearch 6.4]
+* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.5/security-getting-started.html[Elasticsearch 6.5]
 
 [[elasticsearch.username]]elasticsearch.username::
 +
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index a138b14..bda0b67 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -61,6 +61,18 @@
 link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[delete-project]]
+=== delete-project
+
+Provides the ability to delete a project.
+
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/delete-project[
+Project] |
+link:https://gerrit.googlesource.com/plugins/delete-project/+doc/master/src/main/resources/Documentation/about.md[
+Documentation] |
+link:https://gerrit.googlesource.com/plugins/delete-project/+doc/master/src/main/resources/Documentation/config.md[
+Configuration]
+
 [[download-commands]]
 === download-commands
 
@@ -112,16 +124,6 @@
 link:https://gerrit.googlesource.com/plugins/reviewnotes/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
-[[review-strategy]]
-=== review-strategy
-
-This plugin allows users to configure different review strategies.
-
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/review-strategy[
-Project] |
-link:https://gerrit.googlesource.com/plugins/review-strategy/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
-
 [[singleusergroup]]
 === singleusergroup
 
@@ -220,16 +222,6 @@
 link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
-[[delete-project]]
-=== delete-project
-
-Provides the ability to delete a project.
-
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/delete-project[
-Project] |
-link:https://gerrit.googlesource.com/plugins/delete-project/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
-
 [[egit]]
 === egit
 
@@ -564,6 +556,16 @@
 link:https://gerrit.googlesource.com/plugins/reparent/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[review-strategy]]
+=== review-strategy
+
+This plugin allows users to configure different review strategies.
+
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/review-strategy[
+Project] |
+link:https://gerrit.googlesource.com/plugins/review-strategy/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
 [[reviewers]]
 === reviewers
 
diff --git a/Documentation/database-setup.txt b/Documentation/database-setup.txt
deleted file mode 100644
index 2153751..0000000
--- a/Documentation/database-setup.txt
+++ /dev/null
@@ -1,280 +0,0 @@
-[[createdb]]
-== Database Setup
-
-During the init phase of Gerrit you will need to specify which database to use.
-
-[[createdb_h2]]
-=== H2
-
-If you choose H2, Gerrit will automatically set up the embedded H2 database as
-backend so no set up or configuration is necessary.
-
-Using the embedded H2 database is the easiest way to get a Gerrit
-site up and running, making it ideal for proof of concepts or small team
-servers.  On the flip side, H2 is not the recommended option for large
-corporate installations. This is because there is no easy way to interact
-with the database while Gerrit is offline, it's not easy to backup the data,
-and it's not possible to set up H2 in a load balanced/hotswap configuration.
-
-If this option interests you, you might want to consider
-link:linux-quickstart.html[the quick guide].
-
-[[createdb_derby]]
-=== Apache Derby
-
-If Derby is selected, Gerrit will automatically set up the embedded Derby
-database as backend so no set up or configuration is necessary.
-
-Currently only support for embedded mode is added. There are two other
-deployment options for Apache Derby that can be added later:
-
-* link:http://db.apache.org/derby/papers/DerbyTut/ns_intro.html#Network+Server+Options[
-Derby Network Server (standalone mode)]
-
-* link:http://db.apache.org/derby/papers/DerbyTut/ns_intro.html#Embedded+Server[
-Embedded Server (hybrid mode)]
-
-[[createdb_postgres]]
-=== PostgreSQL
-
-This option is more complicated than the H2 option but is recommended
-for larger installations. It's the database backend with the largest userbase
-in the Gerrit community.
-
-Create a user for the web application within PostgreSQL, assign it a
-password, create a database to store the metadata, and grant the user
-full rights on the newly created database:
-
-----
-  $ createuser --username=postgres -RDIElPS gerrit
-  $ createdb --username=postgres -E UTF-8 -O gerrit reviewdb
-----
-
-Visit PostgreSQL's link:http://www.postgresql.org/docs/9.1/interactive/index.html[documentation] for further information regarding
-using PostgreSQL.
-
-[[createdb_mysql]]
-=== MySQL
-
-Requirements: MySQL version 5.1 or later.
-
-This option is also more complicated than the H2 option. Just as with
-PostgreSQL it's also recommended for larger installations.
-
-Create a user for the web application within the database, assign it a
-password, create a database, and give the newly created user full
-rights on it:
-
-----
-  mysql
-
-  CREATE USER 'gerrit'@'localhost' IDENTIFIED BY 'secret';
-  CREATE DATABASE reviewdb DEFAULT CHARACTER SET 'utf8';
-  GRANT ALL ON reviewdb.* TO 'gerrit'@'localhost';
-  FLUSH PRIVILEGES;
-----
-
-Visit MySQL's link:http://dev.mysql.com/doc/[documentation] for further
-information regarding using MySQL.
-
-[[createdb_mariadb]]
-=== MariaDB
-
-Requirements: MariaDB version 5.5 or later.
-
-Refer to MySQL section above how to create MariaDB database.
-
-Visit MariaDB's link:https://mariadb.com/kb/en/mariadb/[documentation] for further
-information regarding using MariaDB.
-
-[[createdb_oracle]]
-=== Oracle
-
-PostgreSQL or H2 is the recommended database for Gerrit Code Review.
-Oracle is supported for environments where running on an existing Oracle
-installation simplifies administrative overheads, such as database backups.
-
-Create a user for the web application within sqlplus, assign it a
-password, and grant the user full rights on the newly created database:
-
-----
-  SQL> create user gerrit identified by secret_password default tablespace users;
-  SQL> grant connect, resources to gerrit;
-----
-
-JDBC driver ojdbc6.jar must be obtained from your Oracle distribution. Gerrit
-initialization process tries to copy it from a known location:
-
-----
-/u01/app/oracle/product/11.2.0/xe/jdbc/lib/ojdbc6.jar
-----
-
-If this file can not be located at this place, then the alternative location
-can be provided.
-
-Instance name is the Oracle SID. Sample database section in
-$site_path/etc/gerrit.config:
-
-----
-[database]
-        type = oracle
-        instance = xe
-        hostname = localhost
-        username = gerrit
-        port = 1521
-----
-
-Sample database section in $site_path/etc/secure.config:
-
-----
-[database]
-        password = secret_password
-----
-
-[[createdb_maxdb]]
-=== SAP MaxDB
-
-SAP MaxDB is a supported database for running Gerrit Code Review. However it is
-recommended only for environments where you intend to run Gerrit on an existing
-MaxDB installation to reduce administrative overhead.
-
-In the MaxDB studio or using the SQLCLI command line interface create a user
-'gerrit' with the user class 'RESOURCE' and a password <secret password>. This
-will also create an associated schema on the database.
-
-To run Gerrit on MaxDB, you need to obtain the MaxDB JDBC driver. It can be
-found in your MaxDB installation at the following location:
-
-- on Windows 64bit at "C:\Program Files\sdb\MaxDB\runtime\jar\sapdbc.jar"
-- on Linux at "/opt/sdb/MaxDB/runtime/jar/sapdbc.jar"
-
-It needs to be stored in the 'lib' folder of the review site.
-
-In the following sample database section it is assumed that the database name is
-'reviewdb' and the database is installed on localhost:
-
-In $site_path/etc/gerrit.config:
-
-----
-[database]
-        type = maxdb
-        database = reviewdb
-        hostname = localhost
-        username = gerrit
-
-----
-
-In $site_path/etc/secure.config:
-
-----
-[database]
-        password = <secret password>
-----
-
-Visit SAP MaxDB's link:http://maxdb.sap.com/documentation/[documentation] for further
-information regarding using SAP MaxDB.
-
-[[createdb_db2]]
-=== DB2
-
-IBM DB2 is a supported database for running Gerrit Code Review. However it is
-recommended only for environments where you intend to run Gerrit on an existing
-DB2 installation to reduce administrative overhead.
-
-Create a system wide user for the Gerrit application, and grant the user
-full rights on the newly created database:
-
-----
-  db2 => create database gerrit
-  db2 => connect to gerrit
-  db2 => grant connect,accessctrl,dataaccess,dbadm,secadm on database to gerrit;
-----
-
-JDBC driver db2jcc4.jar and db2jcc_license_cu.jar must be obtained
-from your DB2 distribution. Gerrit initialization process tries to copy
-it from a known location:
-
-----
-/opt/ibm/db2/V10.5/java/db2jcc4.jar
-/opt/ibm/db2/V10.5/java/db2jcc_license_cu.jar
-----
-
-If these files cannot be located at this place, then an alternative location
-can be provided during init step execution.
-
-Sample database section in $site_path/etc/gerrit.config:
-
-----
-[database]
-        type = db2
-        database = gerrit
-        hostname = localhost
-        username = gerrit
-        port = 50001
-----
-
-Sample database section in $site_path/etc/secure.config:
-
-----
-[database]
-        password = secret_password
-----
-
-[[createdb_hana]]
-=== SAP HANA
-
-SAP HANA is a supported database for running Gerrit Code Review. However it is
-recommended only for environments where you intend to run Gerrit on an existing
-HANA installation to reduce administrative overhead.
-
-In the HANA studio or the SAP HANA Web-based Development Workbench create a user
-'GERRIT2' with the role 'RESTRICTED_USER_JDBC_ACCESS' and a password
-<secret password>. This will also create an associated schema on the database.
-As this user would be required to change the password upon first login you might
-want to to disable the password lifetime check by executing
-'ALTER USER GERRIT2 DISABLE PASSWORD LIFETIME'.
-
-To run Gerrit on HANA, you need to obtain the HANA JDBC driver. It can be found
-as described
-link:http://help.sap.com/saphelp_hanaplatform/helpdata/en/ff/15928cf5594d78b841fbbe649f04b4/frameset.htm[here].
-It needs to be stored in the 'lib' folder of the review site.
-
-In the following sample database section it is assumed that HANA is running on
-the host 'hana.host' and listening on port '4242' where a schema/user GERRIT2
-was created:
-
-In $site_path/etc/gerrit.config:
-
-----
-[database]
-        type = hana
-        hostname = hana.host
-        port = 4242
-        username = GERRIT2
-
-----
-
-In order to configure a specific database in a multi-database environment (MDC)
-the database name has to be specified additionally:
-
-In $site_path/etc/gerrit.config:
-
-----
-[database]
-        type = hana
-        hostname = hana.host
-        database = tdb1
-        port = 4242
-        username = GERRIT2
-
-----
-
-In $site_path/etc/secure.config:
-
-----
-[database]
-        password = <secret password>
-----
-
-Visit SAP HANA's link:http://help.sap.com/hana_appliance/[documentation] for
-further information regarding using SAP HANA.
diff --git a/Documentation/dev-design.txt b/Documentation/dev-design.txt
index 2d6e24c..d2fd958 100644
--- a/Documentation/dev-design.txt
+++ b/Documentation/dev-design.txt
@@ -65,6 +65,9 @@
 changing the implementation from Python on Google App Engine, to Java
 on a J2EE servlet container and an SQL database.
 
+Since Gerrit 3.x link:note-db.html[NoteDb] replaced the SQL database
+and all metadata is now stored in Git.
+
 * link:http://video.google.com/videoplay?docid=-8502904076440714866[Mondrian Code Review On The Web]
 * link:https://github.com/rietveld-codereview/rietveld[Rietveld - Code Review for Subversion]
 * link:http://eagain.net/gitweb/?p=gitosis.git;a=blob;f=README.rst;hb=HEAD[Gitosis README]
@@ -83,9 +86,7 @@
 
 Each Git commit created on the client desktop system is converted
 into a unique change record which can be reviewed independently.
-Change records are stored in a database: PostgreSQL, MySQL, or the
-built-in H2, where they can be queried to present customized user
-dashboards, enumerating any pending changes.
+Change records are stored in NoteDb.
 
 A summary of each newly uploaded change is automatically emailed
 to reviewers, so they receive a direct hyperlink to review the
@@ -167,7 +168,6 @@
 online and operating in order to authenticate that user.
 
 * link:http://www.kernel.org/pub/software/scm/git/docs/gitrepository-layout.html[Git Repository Format]
-* link:http://www.postgresql.org/about/[About PostgreSQL]
 * link:http://openid.net/developers/specs/[OpenID Specifications]
 
 *1  Although an effort is underway to eliminate the use of the
@@ -363,7 +363,7 @@
 project's mailing list archives.
 
 The user's name and email address is stored unencrypted in the
-Gerrit metadata store, typically a PostgreSQL database.
+link:config-accounts.html#all-users[All-Users] repository.
 
 The snail-mail mailing address, country, and phone and fax numbers
 are gathered to help project leads contact the user should there
@@ -633,12 +633,6 @@
 
 === Backups
 
-PostgreSQL and MySQL can be configured to replicate their data to
-other systems, where they are applied to a warm-standby backup in
-real time.  Gerrit instances which care about redundancy will setup
-this feature of PostgreSQL or MySQL to ensure the warm-standby is
-reasonably current should the master go offline.
-
 Using the standard replication plugin, Gerrit can be configured
 to replicate changes made to the local Git repositories over any
 standard Git transports. After the plugin is installed, remote
diff --git a/Documentation/dev-inspector.txt b/Documentation/dev-inspector.txt
index 2134f2f..b1559ca 100644
--- a/Documentation/dev-inspector.txt
+++ b/Documentation/dev-inspector.txt
@@ -54,8 +54,6 @@
 ----
 "Shell" is "com.google.gerrit.pgm.shell.JythonShell@61644f2d"
 "m" is "com.google.gerrit.lifecycle.LifecycleManager@6f03b248"
-"ds" is "com.google.gerrit.server.schema.DataSourceProvider@6b3592c"
-"schk" is "com.google.gerrit.server.schema.SchemaVersionCheck@5e8cb9bd"
 
 Welcome to the Gerrit Inspector
 Enter help() to see the above again, EOF to quit and stop Gerrit
@@ -109,71 +107,11 @@
 'registerNatives', 'toString', 'wait']
 ----
 
-Startup script provides some convenient variables to access some global Gerrit components,
-for example a connection to the review database is kept open:
-
-----
->>> ds
-org.apache.commons.dbcp.BasicDataSource@61db2215
->>> ds.driverClassName
-u'org.postgresql.Driver'
->>> ds.dataSource
-org.apache.commons.dbcp.PoolingDataSource@23226fe1
->>> ds.dataSource.connection
-jdbc:postgresql://localhost/reviewdb, UserName=rv, PostgreSQL Native Driver
-----
-
-It is also possible to interact with the ORM layer:
-
-----
->>> db = schk.schema.open()
->>> db
-com.google.gerrit.reviewdb.server.ReviewDb_Schema_GwtOrm$$28@24cbbdf3
->>> db.getDialect()
-com.google.gwtorm.schema.sql.DialectPostgreSQL@4de07d3e
->>> for x in db.patchSets().iterateAllEntities():
-...     print x
-...
-[PatchSet 1,1]
-[PatchSet 2,1]
-[PatchSet 3,1]
-[PatchSet 4,1]
-[PatchSet 5,1]
-[PatchSet 6,1]
-[PatchSet 7,1]
-[PatchSet 8,1]
-[PatchSet 6,2]
->>> for x in db.patchComments().iterateAllEntities():
-...     print x
-com.google.gerrit.reviewdb.client.PatchLineComment@5381298a
-com.google.gerrit.reviewdb.client.PatchLineComment@44ce4dda
-com.google.gerrit.reviewdb.client.PatchLineComment@44594680
->>> dir(com.google.gerrit.reviewdb.client.PatchLineComment)
-['Key', 'STATUS_DRAFT', 'STATUS_PUBLISHED', 'Status', '__class__',
-'__copy__', '__deepcopy__', '__delattr__', '__doc__', '__eq__',
-'__getattribute__', '__hash__', '__init__', '__ne__', '__new__',
-'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__',
-'__unicode__', 'author', 'class', 'clone', 'equals', 'finalize',
-'getAuthor', 'getClass', 'getKey', 'getLine', 'getMessage',
-'getParentUuid', 'getSide', 'getStatus', 'getWrittenOn', 'hashCode',
-'key', 'line', 'lineNbr', 'message', 'notify', 'notifyAll',
-'parentUuid', 'registerNatives', 'setMessage', 'setSide', 'setStatus',
-'side', 'status', 'toString', 'updated', 'wait', 'writtenOn']
->>> for x in db.patchComments().iterateAllEntities():
-...     print x.status, x.line, x.message
-...
-P 2 I like it!
-P 2 more
-P 1 better
-----
-
 A built-in *help()* function provides values of global variables
 defined in the interpreter:
 
 ----
 >>> help()
-"schk" is "com.google.gerrit.server.schema.SchemaVersionCheck@5e8cb9bd"
-"ds" is "com.google.gerrit.server.schema.DataSourceProvider@6b3592c"
 "m" is "com.google.gerrit.lifecycle.LifecycleManager@6f03b248"
 "Shell" is "com.google.gerrit.pgm.shell.JythonShell@61644f2d"
 "d" is "com.google.gerrit.pgm.Daemon@28a3f689"
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 39a8d61..e84effd 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2102,6 +2102,16 @@
 `com.google.gerrit.server.git.ChangeReportFormatter` interface, a plugin
 may change the formatting of the report.
 
+[[url-formatting]]
+== URL Formatting
+
+URLs to various parts of Gerrit are usually formed by adding suffixes to
+the canonical web URL.
+
+By implementing the
+`com.google.gerrit.server.config.UrlFormatter` interface, a plugin may
+change the format of the URL.
+
 [[links-to-external-tools]]
 == Links To External Tools
 
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index 92d080b..433e0c1 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -187,27 +187,6 @@
 CAUTION: When using the Inspector, be careful not to modify the internal state
 of the system.
 
-=== Querying the database
-
-The embedded H2 database can be queried and updated from the command line. If
-the daemon is not running, run:
-
-----
-  $(bazel info output_base)/external/local_jdk/bin/java \
-     -jar bazel-bin/gerrit.war gsql -d ../gerrit_testsite -s
-----
-
-NOTE: To learn why using `java -jar` isn't sufficient, see
-<<special_bazel_java_version,this explanation>>.
-
-Alternatively, if the daemon is running and the database is in use, use an
-administrator user account to connect over SSH:
-
-----
-  ssh -p 29418 user@localhost gerrit gsql
-----
-
-
 == Switching between branches
 
 When using `git checkout` without `--recurse-submodules` to switch between
diff --git a/Documentation/dev-release-subproject.txt b/Documentation/dev-release-subproject.txt
index 4886849..c9369b9 100644
--- a/Documentation/dev-release-subproject.txt
+++ b/Documentation/dev-release-subproject.txt
@@ -73,7 +73,7 @@
 * Deploy the new release:
 +
 ----
-  mvn deploy
+  mvn deploy -DperformRelease=true
 ----
 
 * Push the pom change(s) to the project's repository
diff --git a/Documentation/install-j2ee.txt b/Documentation/install-j2ee.txt
index 91d73cc..48751b7 100644
--- a/Documentation/install-j2ee.txt
+++ b/Documentation/install-j2ee.txt
@@ -14,9 +14,8 @@
 
 == Installation
 
-* Complete the link:install.html#createdb[database setup] and
-  link:install.html#init[site initialization] tasks described
-  in the standard installation documentation.
+* Complete the link:install.html#init[site initialization] task
+  described in the standard installation documentation.
 
 * Stop the embedded daemon that was automatically started by 'init':
 +
@@ -24,13 +23,6 @@
   review_site/bin/gerrit.sh stop
 ----
 
-* Configure JNDI DataSource 'jdbc/ReviewDb'.
-+
-This DataSource must point to the database you created above.
-Don't forget to ensure your JNDI configuration can load the
-necessary JDBC drivers.  You may wish to ensure connection pooling
-is configured and enabled within the DataSource.
-
 * Deploy the 'gerrit.war' file to your application server.
 +
 The deployment process differs between servers, but typically this
@@ -70,20 +62,8 @@
   java -jar webapps/gerrit.war cat extra/jetty7/gerrit.xml >contexts/gerrit.xml
 ----
 
-Install the required additional libraries by copying them into the
-`'$JETTY_HOME'/lib/ext` directory:
-
-----
-  cp ../review_db/lib/* lib/ext/
-  java -jar webapps/gerrit.war cat lib/commons-dbcp-1.2.2.jar >lib/ext/commons-dbcp-1.2.2.jar
-  java -jar webapps/gerrit.war cat lib/commons-pool-1.5.4.jar >lib/ext/commons-pool-1.5.4.jar
-  java -jar webapps/gerrit.war cat lib/h2-1.2.128.jar >lib/ext/h2-1.2.128.jar
-  java -jar webapps/gerrit.war cat lib/postgresql-8.4-701.jdbc4.jar >lib/ext/postgresql-8.4-701.jdbc4.jar
-----
-
 Edit `'$JETTY_HOME'/contexts/gerrit.xml` to correctly configure
-the database and outgoing SMTP connections, especially the user
-and password fields.
+outgoing SMTP connections.
 
 If OpenID authentication (or certain enterprise single-sign-on
 solutions) is being used, you may need to increase the
diff --git a/Documentation/install.txt b/Documentation/install.txt
index dbca368..be55417 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -9,10 +9,6 @@
 +
 Gerrit is not yet compatible with Java 9 or newer at this time.
 
-By default, Gerrit uses link:note-db.html[NoteDB] as the storage backend. (If
-desired, you can _optionally_ use an external database such as MySQL or
-PostgreSQL.)
-
 [[cryptography]]
 == Configure Java for Strong Cryptography
 
@@ -60,8 +56,6 @@
 If you would prefer to build Gerrit directly from source, review
 the notes under link:dev-readme.html[developer setup].
 
-include::database-setup.txt[]
-
 [[init]]
 == Initialize the Site
 
@@ -217,8 +211,7 @@
         --StartPath=C:\MY\GERRIT\SITE ^
         --StartMode=jvm --StopMode=jvm ^
         --StartClass=com.google.gerrit.launcher.GerritLauncher --StartMethod=daemonStart ^
-        --StopClass=com.google.gerrit.launcher.GerritLauncher --StopMethod=daemonStop ^
-        ++DependsOn=postgresql-x64-9.4
+        --StopClass=com.google.gerrit.launcher.GerritLauncher --StopMethod=daemonStop
 ====
 
 [[customize]]
@@ -255,8 +248,6 @@
 
 == External Documentation Links
 
-* http://www.postgresql.org/docs/[PostgreSQL Documentation]
-* http://dev.mysql.com/doc/[MySQL Documentation]
 * http://www.kernel.org/pub/software/scm/git/docs/git-daemon.html[git-daemon]
 
 
diff --git a/Documentation/pgm-gsql.txt b/Documentation/pgm-gsql.txt
deleted file mode 100644
index 4986522..0000000
--- a/Documentation/pgm-gsql.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-= gsql
-
-== NAME
-gsql - Administrative interface to idle database
-
-== SYNOPSIS
-[verse]
---
-_java_ -jar gerrit.war _gsql_
-  -d <SITE_PATH>
---
-
-== DESCRIPTION
-Interactive query support against the configured SQL database.
-All SQL statements are supported, including SELECT, UPDATE, INSERT,
-DELETE and ALTER.
-
-This command is primarily intended to access a local H2 database
-which is not currently open by a Gerrit daemon.  To access an open
-database use link:cmd-gsql.html[gerrit gsql] over SSH.
-
-== OPTIONS
-
--d::
---site-path::
-	Location of the gerrit.config file, and all other per-site
-	configuration data, supporting libraries and log files.
-
-== CONTEXT
-This command can only be run on a server which has direct
-connectivity to the metadata database, and local access to the
-managed Git repositories.
-
-== EXAMPLES
-To manually correct a user's SSH user name:
-
-----
-	$ java -jar gerrit.war gsql
-	Welcome to Gerrit Code Review v2.0.25
-	(PostgreSQL 8.3.8)
-
-	Type '\h' for help.  Type '\r' to clear the buffer.
-
-	gerrit> update accounts set ssh_user_name = 'alice' where account_id=1;
-	UPDATE 1; 1 ms
-	gerrit> \q
-	Bye
-----
-
-GERRIT
-------
-Part of link:index.html[Gerrit Code Review]
-
-SEARCHBOX
----------
diff --git a/Documentation/pgm-index.txt b/Documentation/pgm-index.txt
index d61cc0b..dde0231 100644
--- a/Documentation/pgm-index.txt
+++ b/Documentation/pgm-index.txt
@@ -15,9 +15,6 @@
 link:pgm-daemon.html[daemon]::
 	Gerrit HTTP, SSH network server.
 
-link:pgm-gsql.html[gsql]::
-	Administrative interface to idle database.
-
 link:pgm-prolog-shell.html[prolog-shell]::
 	Simple interactive Prolog interpreter.
 
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index b1c9a88..b60e56d 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2454,7 +2454,7 @@
 Retrieves a change message including link:#detailed-accounts[detailed account information].
 
 --
-'GET /changes/link:#change-id[\{change-id\}]/message/link:#change-message-id[\{change-message-id\}'
+'GET /changes/link:#change-id[\{change-id\}]/message/link:#change-message-id[\{change-message-id\}]'
 --
 
 As response a link:#change-message-info[ChangeMessageInfo] entity is returned.
@@ -2483,8 +2483,8 @@
 [[delete-change-message]]
 === Delete Change Message
 --
-'DELETE /changes/link:#change-id[\{change-id\}]/message/link:#change-message-id[\{change-message-id\}' +
-'POST /changes/link:#change-id[\{change-id\}]//message/link:#change-message-id[\{change-message-id\}/delete'
+'DELETE /changes/link:#change-id[\{change-id\}]/message/link:#change-message-id[\{change-message-id\}]' +
+'POST /changes/link:#change-id[\{change-id\}]//message/link:#change-message-id[\{change-message-id\}]/delete'
 --
 
 Deletes a change message by replacing the change message with a new message,
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 49ab36f..238eda6 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1192,21 +1192,19 @@
   Content-Type: application/json; charset=UTF-8
 
   {
-    "remove": [
-      {
-        "refs/*": {
-          "permissions": {
-            "read": {
-              "rules": {
-                "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
-                  "action": "ALLOW"
-                }
+    "remove": {
+      "refs/*": {
+        "permissions": {
+          "read": {
+            "rules": {
+              "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
+                "action": "ALLOW"
               }
             }
           }
         }
       }
-    ]
+    }
   }
 ----
 
diff --git a/WORKSPACE b/WORKSPACE
index 8c8102b..9e51fcd 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -191,9 +191,9 @@
 
 maven_jar(
     name = "gwtorm-client",
-    artifact = "com.google.gerrit:gwtorm:1.18",
-    sha1 = "f326dec463439a92ccb32f05b38345e21d0b5ecf",
-    src_sha1 = "e0b973d5cafef3d145fa80cdf032fcead1186d29",
+    artifact = "com.google.gerrit:gwtorm:1.20",
+    sha1 = "a4809769b710bc8ce3f203125630b8419f0e58b0",
+    src_sha1 = "cb63296276ce3228b2d83a37017a99e38ad8ed42",
 )
 
 maven_jar(
@@ -211,6 +211,12 @@
 )
 
 maven_jar(
+    name = "guava-failureaccess",
+    artifact = "com.google.guava:failureaccess:1.0.1",
+    sha1 = "1dcf1de382a0bf95a3d8b0849546c88bac1292c9",
+)
+
+maven_jar(
     name = "j2objc",
     artifact = "com.google.j2objc:j2objc-annotations:1.1",
     sha1 = "ed28ded51a8b1c6b112568def5f4b455e6809019",
@@ -562,18 +568,18 @@
     sha1 = "18d4d07010c24405129a6dbb0e92057f8779fb9d",
 )
 
-AUTO_VALUE_VERSION = "1.6.2"
+AUTO_VALUE_VERSION = "1.6.3"
 
 maven_jar(
     name = "auto-value",
     artifact = "com.google.auto.value:auto-value:" + AUTO_VALUE_VERSION,
-    sha1 = "e7eae562942315a983eea3e191b72d755c153620",
+    sha1 = "8edb6675b9c09ffdcc19937428e7ef1e3d066e12",
 )
 
 maven_jar(
     name = "auto-value-annotations",
     artifact = "com.google.auto.value:auto-value-annotations:" + AUTO_VALUE_VERSION,
-    sha1 = "ed193d86e0af90cc2342aedbe73c5d86b03fa09b",
+    sha1 = "b88c1bb7f149f6d2cc03898359283e57b08f39cc",
 )
 
 # Transitive dependency of commons-compress
@@ -989,12 +995,6 @@
 )
 
 maven_jar(
-    name = "postgresql",
-    artifact = "org.postgresql:postgresql:42.2.5",
-    sha1 = "951b7eda125f3137538a94e2cbdcf744088ad4c2",
-)
-
-maven_jar(
     name = "commons-io",
     artifact = "commons-io:commons-io:2.2",
     sha1 = "83b5b8a7ba1c08f9e8c8ff2373724e33d3c1e22a",
@@ -1016,8 +1016,8 @@
 # and httpasyncclient as necessary.
 maven_jar(
     name = "elasticsearch-rest-client",
-    artifact = "org.elasticsearch.client:elasticsearch-rest-client:6.5.0",
-    sha1 = "241436d27cf65b84d17126dc7b6b947e8e2c173c",
+    artifact = "org.elasticsearch.client:elasticsearch-rest-client:6.5.2",
+    sha1 = "6ad0dd15affe56be7dfe638fe317bc5f7456b730",
 )
 
 JACKSON_VERSION = "2.9.7"
@@ -1028,10 +1028,18 @@
     sha1 = "4b7f0e0dc527fab032e9800ed231080fdc3ac015",
 )
 
+TESTCONTAINERS_VERSION = "1.10.2"
+
 maven_jar(
     name = "testcontainers",
-    artifact = "org.testcontainers:testcontainers:1.8.0",
-    sha1 = "bc413912f7044f9f12aa0782853aef0a067ee52a",
+    artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION,
+    sha1 = "dfe35b1887685000fecee7f102bd8ce55643665c",
+)
+
+maven_jar(
+    name = "testcontainers-elasticsearch",
+    artifact = "org.testcontainers:elasticsearch:" + TESTCONTAINERS_VERSION,
+    sha1 = "c6eb4a3a0ad114929b659fa59c2ee9fe1c1d6a58",
 )
 
 maven_jar(
@@ -1058,6 +1066,32 @@
     sha1 = "485de3a253e23f645037828c07f1d7f1af40763a",
 )
 
+maven_jar(
+    name = "mockito",
+    artifact = "org.mockito:mockito-core:2.23.4",
+    sha1 = "a35b6f8ffcfa786771eac7d7d903429e790fdf3f",
+)
+
+BYTE_BUDDY_VERSION = "1.9.3"
+
+maven_jar(
+    name = "byte-buddy",
+    artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
+    sha1 = "f32e510b239620852fc9a2387fac41fd053d6a4d",
+)
+
+maven_jar(
+    name = "byte-buddy-agent",
+    artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
+    sha1 = "f5b78c16cf4060664d80b6ca32d80dca4bd3d264",
+)
+
+maven_jar(
+    name = "objenesis",
+    artifact = "org.objenesis:objenesis:2.6",
+    sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
+)
+
 load("//tools/bzl:js.bzl", "bower_archive", "npm_binary")
 
 # NPM binaries bundled along with their dependencies.
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 68b0699..eae3e6b 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -38,7 +38,6 @@
 import com.google.common.primitives.Chars;
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
 import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
-import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GroupDescription;
@@ -50,7 +49,6 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
 import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.api.changes.RelatedChangeAndCommitInfo;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
@@ -295,7 +293,6 @@
   @Inject private AccountIndexer accountIndexer;
   @Inject private Groups groups;
   @Inject private GroupIndexer groupIndexer;
-  @Inject private ProjectOperations projectOperations;
 
   private ProjectResetter resetter;
   private List<Repository> toClose;
@@ -430,10 +427,10 @@
 
     db = reviewDbProvider.open();
 
-    // All groups which were added during the server start (e.g. in ReviewDbSchemaCreator) aren't
+    // All groups which were added during the server start (e.g. in SchemaCreatorImpl) aren't
     // contained in the instance of the group index which is available here and in tests. There are
     // two reasons:
-    // 1) No group index is available in ReviewDbSchemaCreator when using an in-memory database.
+    // 1) No group index is available in SchemaCreatorImpl when using an in-memory database.
     // (This could be fixed by using the IndexManagerOnInit in InMemoryDatabase similar as BaseInit
     // uses it.)
     // 2) During the on-init part of the server start, we use another instance of the index than
@@ -552,24 +549,7 @@
     return resourcePrefix + name;
   }
 
-  protected Project.NameKey createProject(String nameSuffix) throws RestApiException {
-    return createProject(nameSuffix, null);
-  }
-
-  protected Project.NameKey createProject(String nameSuffix, Project.NameKey parent)
-      throws RestApiException {
-    // Default for createEmptyCommit should match TestProjectConfig.
-    return createProject(nameSuffix, parent, true, null);
-  }
-
-  protected Project.NameKey createProject(
-      String nameSuffix, Project.NameKey parent, boolean createEmptyCommit)
-      throws RestApiException {
-    // Default for createEmptyCommit should match TestProjectConfig.
-    return createProject(nameSuffix, parent, createEmptyCommit, null);
-  }
-
-  protected Project.NameKey createProject(
+  protected Project.NameKey createProjectOverAPI(
       String nameSuffix, Project.NameKey parent, boolean createEmptyCommit, SubmitType submitType)
       throws RestApiException {
     ProjectInput in = new ProjectInput();
@@ -1196,10 +1176,6 @@
     }
   }
 
-  protected RevCommit getHead(Repository repo) throws Exception {
-    return getHead(repo, "HEAD");
-  }
-
   @Nullable
   protected RevCommit getRemoteHead(Project.NameKey project, String branch) throws Exception {
     try (Repository repo = repoManager.openRepository(project)) {
@@ -1669,13 +1645,4 @@
     comments.sort(Comparator.comparing(c -> c.id));
     return comments;
   }
-
-  protected List<RelatedChangeAndCommitInfo> getRelated(PatchSet.Id ps) throws Exception {
-    return getRelated(ps.getParentKey(), ps.get());
-  }
-
-  protected List<RelatedChangeAndCommitInfo> getRelated(Change.Id changeId, int ps)
-      throws Exception {
-    return gApi.changes().id(changeId.get()).revision(ps).related().changes;
-  }
 }
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index b0a39cf..f74246f 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -26,6 +26,7 @@
         "//java/com/google/gerrit/pgm/util",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/audit",
         "//java/com/google/gerrit/server/git/receive",
         "//java/com/google/gerrit/server/project/testing:project-test-util",
         "//java/com/google/gerrit/server/restapi",
@@ -49,6 +50,7 @@
         "//lib/guice:guice-servlet",
         "//lib/jgit/org.eclipse.jgit:jgit",
         "//lib/mina:sshd",
+        "//lib/mockito",
         "//prolog:gerrit-prolog-common",
     ],
 )
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index 981ee6b..4788ab7 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -40,15 +40,11 @@
 import com.google.gerrit.server.config.SitePath;
 import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
 import com.google.gerrit.server.ssh.NoSshModule;
-import com.google.gerrit.server.util.ManualRequestContext;
-import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.SocketUtil;
 import com.google.gerrit.server.util.SystemLog;
 import com.google.gerrit.testing.FakeEmailSender;
 import com.google.gerrit.testing.FakeGroupAuditService;
-import com.google.gerrit.testing.InMemoryDatabase;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.NoteDbChecker;
 import com.google.gerrit.testing.NoteDbMode;
 import com.google.gerrit.testing.SshMode;
 import com.google.inject.AbstractModule;
@@ -228,6 +224,9 @@
 
           // Silence non-critical messages from apache.http.
           .put("org.apache.http", Level.WARN)
+
+          // Silence non-critical messages from Jetty.
+          .put("org.eclipse.jetty", Level.WARN)
           .build();
 
   private static boolean forceLocalDisk() {
@@ -312,7 +311,7 @@
       if (!desc.memory()) {
         init(desc, baseConfig, site);
       }
-      return start(desc, baseConfig, site, testSysModule, null, null);
+      return start(desc, baseConfig, site, testSysModule, null);
     } catch (Exception e) {
       throw e;
     }
@@ -330,8 +329,6 @@
    * @param testSysModule optional additional module to add to the system injector.
    * @param inMemoryRepoManager {@link InMemoryRepositoryManager} that should be used if the site is
    *     started in memory
-   * @param inMemoryDatabaseInstance {@link com.google.gerrit.testing.InMemoryDatabase.Instance}
-   *     that should be used if the site is started in memory
    * @param additionalArgs additional command-line arguments for the daemon program; only allowed if
    *     the test is not in-memory.
    * @return started server.
@@ -343,7 +340,6 @@
       Path site,
       @Nullable Module testSysModule,
       @Nullable InMemoryRepositoryManager inMemoryRepoManager,
-      @Nullable InMemoryDatabase.Instance inMemoryDatabaseInstance,
       String... additionalArgs)
       throws Exception {
     checkArgument(site != null, "site is required (even for in-memory server");
@@ -368,8 +364,7 @@
 
     if (desc.memory()) {
       checkArgument(additionalArgs.length == 0, "cannot pass args to in-memory server");
-      return startInMemory(
-          desc, site, baseConfig, daemon, inMemoryRepoManager, inMemoryDatabaseInstance);
+      return startInMemory(desc, site, baseConfig, daemon, inMemoryRepoManager);
     }
     return startOnDisk(desc, site, daemon, serverStarted, additionalArgs);
   }
@@ -379,8 +374,7 @@
       Path site,
       Config baseConfig,
       Daemon daemon,
-      @Nullable InMemoryRepositoryManager inMemoryRepoManager,
-      @Nullable InMemoryDatabase.Instance inMemoryDatabaseInstance)
+      @Nullable InMemoryRepositoryManager inMemoryRepoManager)
       throws Exception {
     Config cfg = desc.buildConfig(baseConfig);
     mergeTestConfig(cfg);
@@ -395,8 +389,7 @@
     daemon.setLuceneModule(LuceneIndexModule.singleVersionAllLatest(0, isSlave(baseConfig)));
     daemon.setDatabaseForTesting(
         ImmutableList.<Module>of(
-            new InMemoryTestingDatabaseModule(
-                cfg, site, inMemoryRepoManager, inMemoryDatabaseInstance),
+            new InMemoryTestingDatabaseModule(cfg, site, inMemoryRepoManager),
             new AbstractModule() {
               @Override
               protected void configure() {
@@ -594,21 +587,9 @@
       inMemoryRepoManager = server.testInjector.getInstance(InMemoryRepositoryManager.class);
     }
 
-    InMemoryDatabase.Instance dbInstance = null;
-    if (hasBinding(server.testInjector, InMemoryDatabase.class)) {
-      InMemoryDatabase inMemoryDatabase = server.testInjector.getInstance(InMemoryDatabase.class);
-      dbInstance = inMemoryDatabase.getDbInstance();
-      dbInstance.setKeepOpen(true);
-    }
-    try {
-      server.close();
-      server.daemon.stop();
-      return start(server.desc, cfg, site, null, inMemoryRepoManager, dbInstance);
-    } finally {
-      if (dbInstance != null) {
-        dbInstance.setKeepOpen(false);
-      }
-    }
+    server.close();
+    server.daemon.stop();
+    return start(server.desc, cfg, site, null, inMemoryRepoManager);
   }
 
   private static boolean hasBinding(Injector injector, Class<?> clazz) {
@@ -617,40 +598,19 @@
 
   @Override
   public void close() throws Exception {
-    try {
-      checkNoteDbState();
-    } finally {
-      daemon.getLifecycleManager().stop();
-      if (daemonService != null) {
-        System.out.println("Gerrit Server Shutdown");
-        daemonService.shutdownNow();
-        daemonService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
-      }
-      RepositoryCache.clear();
+    daemon.getLifecycleManager().stop();
+    if (daemonService != null) {
+      System.out.println("Gerrit Server Shutdown");
+      daemonService.shutdownNow();
+      daemonService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
     }
+    RepositoryCache.clear();
   }
 
   public Path getSitePath() {
     return sitePath;
   }
 
-  private void checkNoteDbState() throws Exception {
-    NoteDbMode mode = NoteDbMode.get();
-    if (mode != NoteDbMode.CHECK && mode != NoteDbMode.PRIMARY) {
-      return;
-    }
-    NoteDbChecker checker = testInjector.getInstance(NoteDbChecker.class);
-    OneOffRequestContext oneOffRequestContext =
-        testInjector.getInstance(OneOffRequestContext.class);
-    try (ManualRequestContext ctx = oneOffRequestContext.open()) {
-      if (mode == NoteDbMode.CHECK) {
-        checker.rebuildAndCheckAllChanges();
-      } else if (mode == NoteDbMode.PRIMARY) {
-        checker.assertNoReviewDbChanges(desc.testDescription());
-      }
-    }
-  }
-
   @Override
   public String toString() {
     return MoreObjects.toStringHelper(this).addValue(desc).toString();
diff --git a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index 0bbf9f1..e15d162 100644
--- a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -28,16 +28,11 @@
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.config.TrackingFootersProvider;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.GwtormChangeBundleReader;
 import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.server.schema.DataSourceType;
 import com.google.gerrit.server.schema.NotesMigrationSchemaFactory;
 import com.google.gerrit.server.schema.ReviewDbFactory;
 import com.google.gerrit.server.schema.ReviewDbSchemaModule;
-import com.google.gerrit.server.schema.ReviewDbSchemaVersion;
 import com.google.gerrit.testing.InMemoryDatabase;
-import com.google.gerrit.testing.InMemoryH2Type;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.OrmRuntimeException;
@@ -46,7 +41,6 @@
 import com.google.inject.Key;
 import com.google.inject.ProvisionException;
 import com.google.inject.TypeLiteral;
-import com.google.inject.util.Providers;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -56,17 +50,12 @@
   private final Config cfg;
   private final Path sitePath;
   @Nullable private final InMemoryRepositoryManager repoManager;
-  @Nullable private final InMemoryDatabase.Instance inMemoryDatabaseInstance;
 
   InMemoryTestingDatabaseModule(
-      Config cfg,
-      Path sitePath,
-      @Nullable InMemoryRepositoryManager repoManager,
-      @Nullable InMemoryDatabase.Instance inMemoryDatabaseInstance) {
+      Config cfg, Path sitePath, @Nullable InMemoryRepositoryManager repoManager) {
     this.cfg = cfg;
     this.sitePath = sitePath;
     this.repoManager = repoManager;
-    this.inMemoryDatabaseInstance = inMemoryDatabaseInstance;
     makeSiteDirs(sitePath);
   }
 
@@ -85,16 +74,13 @@
     }
 
     bind(MetricMaker.class).to(DisabledMetricMaker.class);
-    bind(DataSourceType.class).to(InMemoryH2Type.class);
 
     install(new NotesMigration.Module());
     TypeLiteral<SchemaFactory<ReviewDb>> schemaFactory =
         new TypeLiteral<SchemaFactory<ReviewDb>>() {};
     bind(schemaFactory).to(NotesMigrationSchemaFactory.class);
-    bind(InMemoryDatabase.Instance.class).toProvider(Providers.of(inMemoryDatabaseInstance));
     bind(Key.get(schemaFactory, ReviewDbFactory.class)).to(InMemoryDatabase.class);
     bind(InMemoryDatabase.class).in(SINGLETON);
-    bind(ChangeBundleReader.class).to(GwtormChangeBundleReader.class);
 
     listener().to(CreateDatabase.class);
 
@@ -102,7 +88,6 @@
     bind(TrackingFooters.class).toProvider(TrackingFootersProvider.class).in(SINGLETON);
 
     install(new ReviewDbSchemaModule());
-    bind(ReviewDbSchemaVersion.class).to(ReviewDbSchemaVersion.C);
 
     install(new SshdModule());
   }
@@ -125,9 +110,7 @@
     }
 
     @Override
-    public void stop() {
-      mem.getDbInstance().drop();
-    }
+    public void stop() {}
   }
 
   private static void makeSiteDirs(Path p) {
diff --git a/java/com/google/gerrit/acceptance/InProcessProtocol.java b/java/com/google/gerrit/acceptance/InProcessProtocol.java
index de8d10c..401d417 100644
--- a/java/com/google/gerrit/acceptance/InProcessProtocol.java
+++ b/java/com/google/gerrit/acceptance/InProcessProtocol.java
@@ -39,6 +39,7 @@
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.util.RequestContext;
@@ -208,7 +209,7 @@
 
   private static class Upload implements UploadPackFactory<Context> {
     private final TransferConfig transferConfig;
-    private final DynamicSet<UploadPackInitializer> uploadPackInitializers;
+    private final PluginSetContext<UploadPackInitializer> uploadPackInitializers;
     private final DynamicSet<PreUploadHook> preUploadHooks;
     private final UploadValidators.Factory uploadValidatorsFactory;
     private final ThreadLocalRequestContext threadContext;
@@ -218,7 +219,7 @@
     @Inject
     Upload(
         TransferConfig transferConfig,
-        DynamicSet<UploadPackInitializer> uploadPackInitializers,
+        PluginSetContext<UploadPackInitializer> uploadPackInitializers,
         DynamicSet<PreUploadHook> preUploadHooks,
         UploadValidators.Factory uploadValidatorsFactory,
         ThreadLocalRequestContext threadContext,
@@ -267,9 +268,7 @@
       List<PreUploadHook> hooks = Lists.newArrayList(preUploadHooks);
       hooks.add(uploadValidatorsFactory.create(projectState.getProject(), repo, "localhost-test"));
       up.setPreUploadHook(PreUploadHookChain.newChain(hooks));
-      for (UploadPackInitializer initializer : uploadPackInitializers) {
-        initializer.init(req.project, up);
-      }
+      uploadPackInitializers.runEach(initializer -> initializer.init(req.project, up));
       return up;
     }
   }
@@ -279,7 +278,7 @@
     private final ProjectCache projectCache;
     private final AsyncReceiveCommits.Factory factory;
     private final TransferConfig config;
-    private final DynamicSet<ReceivePackInitializer> receivePackInitializers;
+    private final PluginSetContext<ReceivePackInitializer> receivePackInitializers;
     private final DynamicSet<PostReceiveHook> postReceiveHooks;
     private final ThreadLocalRequestContext threadContext;
     private final PermissionBackend permissionBackend;
@@ -290,7 +289,7 @@
         ProjectCache projectCache,
         AsyncReceiveCommits.Factory factory,
         TransferConfig config,
-        DynamicSet<ReceivePackInitializer> receivePackInitializers,
+        PluginSetContext<ReceivePackInitializer> receivePackInitializers,
         DynamicSet<PostReceiveHook> postReceiveHooks,
         ThreadLocalRequestContext threadContext,
         PermissionBackend permissionBackend) {
@@ -339,9 +338,8 @@
         rp.setTimeout(config.getTimeout());
         rp.setMaxObjectSizeLimit(config.getMaxObjectSizeLimit());
 
-        for (ReceivePackInitializer initializer : receivePackInitializers) {
-          initializer.init(projectState.getNameKey(), rp);
-        }
+        receivePackInitializers.runEach(
+            initializer -> initializer.init(projectState.getNameKey(), rp));
 
         rp.setPostReceiveHook(PostReceiveHookChain.newChain(Lists.newArrayList(postReceiveHooks)));
         return rp;
diff --git a/java/com/google/gerrit/acceptance/PushOneCommit.java b/java/com/google/gerrit/acceptance/PushOneCommit.java
index 5e45df2..d0735c8 100644
--- a/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.GitUtil.pushHead;
-import static java.util.stream.Collectors.toList;
 import static org.junit.Assert.assertEquals;
 
 import com.google.common.base.Strings;
@@ -31,7 +30,6 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -43,7 +41,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Stream;
 import org.eclipse.jgit.api.TagCommand;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -144,7 +141,6 @@
   private final ChangeNotes.Factory notesFactory;
   private final ApprovalsUtil approvalsUtil;
   private final Provider<InternalChangeQuery> queryProvider;
-  private final NotesMigration notesMigration;
   private final ReviewDb db;
   private final TestRepository<?> testRepo;
 
@@ -162,7 +158,6 @@
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
       Provider<InternalChangeQuery> queryProvider,
-      NotesMigration notesMigration,
       @Assisted ReviewDb db,
       @Assisted PersonIdent i,
       @Assisted TestRepository<?> testRepo)
@@ -171,7 +166,6 @@
         notesFactory,
         approvalsUtil,
         queryProvider,
-        notesMigration,
         db,
         i,
         testRepo,
@@ -185,7 +179,6 @@
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
       Provider<InternalChangeQuery> queryProvider,
-      NotesMigration notesMigration,
       @Assisted ReviewDb db,
       @Assisted PersonIdent i,
       @Assisted TestRepository<?> testRepo,
@@ -195,7 +188,6 @@
         notesFactory,
         approvalsUtil,
         queryProvider,
-        notesMigration,
         db,
         i,
         testRepo,
@@ -210,7 +202,6 @@
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
       Provider<InternalChangeQuery> queryProvider,
-      NotesMigration notesMigration,
       @Assisted ReviewDb db,
       @Assisted PersonIdent i,
       @Assisted TestRepository<?> testRepo,
@@ -222,7 +213,6 @@
         notesFactory,
         approvalsUtil,
         queryProvider,
-        notesMigration,
         db,
         i,
         testRepo,
@@ -237,24 +227,13 @@
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
       Provider<InternalChangeQuery> queryProvider,
-      NotesMigration notesMigration,
       @Assisted ReviewDb db,
       @Assisted PersonIdent i,
       @Assisted TestRepository<?> testRepo,
       @Assisted String subject,
       @Assisted Map<String, String> files)
       throws Exception {
-    this(
-        notesFactory,
-        approvalsUtil,
-        queryProvider,
-        notesMigration,
-        db,
-        i,
-        testRepo,
-        subject,
-        files,
-        null);
+    this(notesFactory, approvalsUtil, queryProvider, db, i, testRepo, subject, files, null);
   }
 
   @AssistedInject
@@ -262,7 +241,6 @@
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
       Provider<InternalChangeQuery> queryProvider,
-      NotesMigration notesMigration,
       @Assisted ReviewDb db,
       @Assisted PersonIdent i,
       @Assisted TestRepository<?> testRepo,
@@ -275,7 +253,6 @@
         notesFactory,
         approvalsUtil,
         queryProvider,
-        notesMigration,
         db,
         i,
         testRepo,
@@ -288,7 +265,6 @@
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
       Provider<InternalChangeQuery> queryProvider,
-      NotesMigration notesMigration,
       ReviewDb db,
       PersonIdent i,
       TestRepository<?> testRepo,
@@ -301,7 +277,6 @@
     this.notesFactory = notesFactory;
     this.approvalsUtil = approvalsUtil;
     this.queryProvider = queryProvider;
-    this.notesMigration = notesMigration;
     this.subject = subject;
     this.files = files;
     this.changeId = changeId;
@@ -436,22 +411,15 @@
       assertThat(c.getSubject()).isEqualTo(resSubj);
       assertThat(c.getStatus()).isEqualTo(expectedStatus);
       assertThat(Strings.emptyToNull(c.getTopic())).isEqualTo(expectedTopic);
-      if (notesMigration.readChanges()) {
-        assertReviewers(c, ReviewerStateInternal.REVIEWER, expectedReviewers);
-        assertReviewers(c, ReviewerStateInternal.CC, expectedCcs);
-      } else {
-        assertReviewers(
-            c,
-            ReviewerStateInternal.REVIEWER,
-            Stream.concat(expectedReviewers.stream(), expectedCcs.stream()).collect(toList()));
-      }
+      assertReviewers(c, ReviewerStateInternal.REVIEWER, expectedReviewers);
+      assertReviewers(c, ReviewerStateInternal.CC, expectedCcs);
     }
 
     private void assertReviewers(
         Change c, ReviewerStateInternal state, List<TestAccount> expectedReviewers)
         throws OrmException {
       Iterable<Account.Id> actualIds =
-          approvalsUtil.getReviewers(db, notesFactory.createChecked(db, c)).byState(state);
+          approvalsUtil.getReviewers(notesFactory.createChecked(db, c)).byState(state);
       assertThat(actualIds)
           .containsExactlyElementsIn(Sets.newHashSet(TestAccount.ids(expectedReviewers)));
     }
diff --git a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
index a50de1a..c03c8b6 100644
--- a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
+++ b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
@@ -209,7 +209,7 @@
   private GerritServer startImpl(@Nullable Module testSysModule, String... additionalArgs)
       throws Exception {
     return GerritServer.start(
-        serverDesc, baseConfig, sitePaths.site_path, testSysModule, null, null, additionalArgs);
+        serverDesc, baseConfig, sitePaths.site_path, testSysModule, null, additionalArgs);
   }
 
   protected static void runGerrit(String... args) throws Exception {
diff --git a/java/com/google/gerrit/acceptance/testsuite/ThrowingConsumer.java b/java/com/google/gerrit/acceptance/testsuite/ThrowingConsumer.java
index 5efdc81..8efb6ae 100644
--- a/java/com/google/gerrit/acceptance/testsuite/ThrowingConsumer.java
+++ b/java/com/google/gerrit/acceptance/testsuite/ThrowingConsumer.java
@@ -17,4 +17,12 @@
 @FunctionalInterface
 public interface ThrowingConsumer<T> {
   void accept(T t) throws Exception;
+
+  default void acceptAndThrowSilently(T t) {
+    try {
+      accept(t);
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/acceptance/testsuite/ThrowingFunction.java b/java/com/google/gerrit/acceptance/testsuite/ThrowingFunction.java
index d41672a..2337331 100644
--- a/java/com/google/gerrit/acceptance/testsuite/ThrowingFunction.java
+++ b/java/com/google/gerrit/acceptance/testsuite/ThrowingFunction.java
@@ -18,4 +18,12 @@
 public interface ThrowingFunction<T, R> {
 
   R apply(T value) throws Exception;
+
+  default R applyAndThrowSilently(T t) {
+    try {
+      return apply(t);
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java
index 61b7599..61b828e 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperations.java
@@ -28,11 +28,11 @@
 
   /**
    * Starts the fluent chain for a querying or modifying an account. Please see the methods of
-   * {@link MoreAccountOperations} for details on possible operations.
+   * {@link PerAccountOperations} for details on possible operations.
    *
    * @return an aggregation of operations on a specific account
    */
-  MoreAccountOperations account(Account.Id accountId);
+  PerAccountOperations account(Account.Id accountId);
 
   /**
    * Starts the fluent chain to create an account. The returned builder can be used to specify the
@@ -58,14 +58,14 @@
   TestAccountCreation.Builder newAccount();
 
   /** An aggregation of methods on a specific account. */
-  interface MoreAccountOperations {
+  interface PerAccountOperations {
 
     /**
      * Checks whether the account exists.
      *
      * @return {@code true} if the account exists
      */
-    boolean exists() throws Exception;
+    boolean exists();
 
     /**
      * Retrieves the account.
@@ -76,7 +76,7 @@
      *
      * @return the corresponding {@code TestAccount}
      */
-    TestAccount get() throws Exception;
+    TestAccount get();
 
     /**
      * Starts the fluent chain to update an account. The returned builder can be used to specify how
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
index ebbcfe4..7f55c97 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
@@ -50,8 +50,8 @@
   }
 
   @Override
-  public MoreAccountOperations account(Account.Id accountId) {
-    return new MoreAccountOperationsImpl(accountId);
+  public PerAccountOperations account(Account.Id accountId) {
+    return new PerAccountOperationsImpl(accountId);
   }
 
   @Override
@@ -100,28 +100,35 @@
     return builder.addExternalId(ExternalId.createUsername(username, accountId, httpPassword));
   }
 
-  private class MoreAccountOperationsImpl implements MoreAccountOperations {
+  private class PerAccountOperationsImpl implements PerAccountOperations {
     private final Account.Id accountId;
 
-    MoreAccountOperationsImpl(Account.Id accountId) {
+    PerAccountOperationsImpl(Account.Id accountId) {
       this.accountId = accountId;
     }
 
     @Override
-    public boolean exists() throws Exception {
-      return accounts.get(accountId).isPresent();
+    public boolean exists() {
+      return getAccountState(accountId).isPresent();
     }
 
     @Override
-    public TestAccount get() throws Exception {
+    public TestAccount get() {
       AccountState account =
-          accounts
-              .get(accountId)
+          getAccountState(accountId)
               .orElseThrow(
                   () -> new IllegalStateException("Tried to get non-existing test account"));
       return toTestAccount(account);
     }
 
+    private Optional<AccountState> getAccountState(Account.Id accountId) {
+      try {
+        return accounts.get(accountId);
+      } catch (IOException | ConfigInvalidException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+
     private TestAccount toTestAccount(AccountState accountState) {
       Account account = accountState.getAccount();
       return TestAccount.builder()
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
index ab32409..f2414e0 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
@@ -88,9 +88,9 @@
 
     abstract TestAccountCreation autoBuild();
 
-    public Account.Id create() throws Exception {
+    public Account.Id create() {
       TestAccountCreation accountUpdate = autoBuild();
-      return accountUpdate.accountCreator().apply(accountUpdate);
+      return accountUpdate.accountCreator().applyAndThrowSilently(accountUpdate);
     }
   }
 }
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountUpdate.java b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountUpdate.java
index 251f452..da599e7 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountUpdate.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountUpdate.java
@@ -86,9 +86,9 @@
 
     abstract TestAccountUpdate autoBuild();
 
-    public void update() throws Exception {
+    public void update() {
       TestAccountUpdate accountUpdate = autoBuild();
-      accountUpdate.accountUpdater().accept(accountUpdate);
+      accountUpdate.accountUpdater().acceptAndThrowSilently(accountUpdate);
     }
   }
 }
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java
index f75ca2e..533d06b 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperations.java
@@ -27,11 +27,11 @@
 public interface GroupOperations {
   /**
    * Starts the fluent chain for querying or modifying a group. Please see the methods of {@link
-   * MoreGroupOperations} for details on possible operations.
+   * PerGroupOperations} for details on possible operations.
    *
    * @return an aggregation of operations on a specific group
    */
-  MoreGroupOperations group(AccountGroup.UUID groupUuid);
+  PerGroupOperations group(AccountGroup.UUID groupUuid);
 
   /**
    * Starts the fluent chain to create a group. The returned builder can be used to specify the
@@ -56,14 +56,14 @@
   TestGroupCreation.Builder newGroup();
 
   /** An aggregation of methods on a specific group. */
-  interface MoreGroupOperations {
+  interface PerGroupOperations {
 
     /**
      * Checks whether the group exists.
      *
      * @return {@code true} if the group exists
      */
-    boolean exists() throws Exception;
+    boolean exists();
 
     /**
      * Retrieves the group.
@@ -74,7 +74,7 @@
      *
      * @return the corresponding {@code TestGroup}
      */
-    TestGroup get() throws Exception;
+    TestGroup get();
 
     /**
      * Starts the fluent chain to update a group. The returned builder can be used to specify how
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
index f9769c5..7ce5afa 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
@@ -60,8 +60,8 @@
   }
 
   @Override
-  public MoreGroupOperations group(AccountGroup.UUID groupUuid) {
-    return new MoreGroupOperationsImpl(groupUuid);
+  public PerGroupOperations group(AccountGroup.UUID groupUuid) {
+    return new PerGroupOperationsImpl(groupUuid);
   }
 
   @Override
@@ -101,25 +101,33 @@
     return builder.build();
   }
 
-  private class MoreGroupOperationsImpl implements MoreGroupOperations {
+  private class PerGroupOperationsImpl implements PerGroupOperations {
     private final AccountGroup.UUID groupUuid;
 
-    MoreGroupOperationsImpl(AccountGroup.UUID groupUuid) {
+    PerGroupOperationsImpl(AccountGroup.UUID groupUuid) {
       this.groupUuid = groupUuid;
     }
 
     @Override
-    public boolean exists() throws Exception {
-      return groups.getGroup(groupUuid).isPresent();
+    public boolean exists() {
+      return getGroup(groupUuid).isPresent();
     }
 
     @Override
-    public TestGroup get() throws Exception {
-      Optional<InternalGroup> group = groups.getGroup(groupUuid);
+    public TestGroup get() {
+      Optional<InternalGroup> group = getGroup(groupUuid);
       checkState(group.isPresent(), "Tried to get non-existing test group");
       return toTestGroup(group.get());
     }
 
+    private Optional<InternalGroup> getGroup(AccountGroup.UUID groupUuid) {
+      try {
+        return groups.getGroup(groupUuid);
+      } catch (IOException | ConfigInvalidException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+
     private TestGroup toTestGroup(InternalGroup internalGroup) {
       return TestGroup.builder()
           .groupUuid(internalGroup.getGroupUUID())
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
index efed720..612ce2a 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
@@ -104,9 +104,9 @@
      *
      * @return the UUID of the created group
      */
-    public AccountGroup.UUID create() throws Exception {
+    public AccountGroup.UUID create() {
       TestGroupCreation groupCreation = autoBuild();
-      return groupCreation.groupCreator().apply(groupCreation);
+      return groupCreation.groupCreator().applyAndThrowSilently(groupCreation);
     }
   }
 }
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
index 095a270..bc9d569 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
@@ -126,9 +126,9 @@
     abstract TestGroupUpdate autoBuild();
 
     /** Executes the group update as specified. */
-    public void update() throws Exception {
+    public void update() {
       TestGroupUpdate groupUpdater = autoBuild();
-      groupUpdater.groupUpdater().accept(groupUpdater);
+      groupUpdater.groupUpdater().acceptAndThrowSilently(groupUpdater);
     }
   }
 }
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
index 6bf4114..f296a69 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
@@ -16,24 +16,20 @@
 
 import com.google.gerrit.acceptance.testsuite.project.TestProjectCreation.Builder;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.ProjectOwnerGroupsProvider;
 import com.google.gerrit.server.project.CreateProjectArgs;
 import com.google.gerrit.server.project.ProjectCreator;
+import com.google.inject.Inject;
 import java.util.ArrayList;
 import java.util.Collections;
-import javax.inject.Inject;
 import org.apache.commons.lang.RandomStringUtils;
 import org.eclipse.jgit.lib.Constants;
 
 public class ProjectOperationsImpl implements ProjectOperations {
   private final ProjectCreator projectCreator;
-  private final ProjectOwnerGroupsProvider.Factory projectOwnerGroups;
 
   @Inject
-  ProjectOperationsImpl(
-      ProjectOwnerGroupsProvider.Factory projectOwnerGroups, ProjectCreator projectCreator) {
+  ProjectOperationsImpl(ProjectCreator projectCreator) {
     this.projectCreator = projectCreator;
-    this.projectOwnerGroups = projectOwnerGroups;
   }
 
   @Override
@@ -49,7 +45,8 @@
     args.branch = Collections.singletonList(Constants.R_HEADS + Constants.MASTER);
     args.createEmptyCommit = projectCreation.createEmptyCommit().orElse(true);
     projectCreation.parent().ifPresent(p -> args.newParent = p);
-    args.ownerIds = new ArrayList<>(projectOwnerGroups.create(args.getProject()).get());
+    // ProjectCreator wants non-null owner IDs.
+    args.ownerIds = new ArrayList<>();
     projectCreation.submitType().ifPresent(st -> args.submitType = st);
     projectCreator.createProject(args);
     return new Project.NameKey(name);
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
index aef2625..31af1d2 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
@@ -48,13 +48,7 @@
 
     public abstract TestProjectCreation.Builder createEmptyCommit(boolean value);
 
-    /**
-     * Creates empty commit on creation. This is necessary for the project's branches to be born.
-     */
-    public TestProjectCreation.Builder withEmptyCommit() {
-      return createEmptyCommit(true);
-    }
-
+    /** Skips the empty commit on creation. This means that project's branches will not exist. */
     public TestProjectCreation.Builder noEmptyCommit() {
       return createEmptyCommit(false);
     }
@@ -69,9 +63,9 @@
      *
      * @return the name of the created project
      */
-    public Project.NameKey create() throws Exception {
+    public Project.NameKey create() {
       TestProjectCreation creation = autoBuild();
-      return creation.projectCreator().apply(creation);
+      return creation.projectCreator().applyAndThrowSilently(creation);
     }
   }
 }
diff --git a/java/com/google/gerrit/common/data/AccessSection.java b/java/com/google/gerrit/common/data/AccessSection.java
index c8d8d41..b3da199 100644
--- a/java/com/google/gerrit/common/data/AccessSection.java
+++ b/java/com/google/gerrit/common/data/AccessSection.java
@@ -37,7 +37,7 @@
     super(refPattern);
   }
 
-  public List<Permission> getPermissions() {
+  public ImmutableList<Permission> getPermissions() {
     return permissions == null ? ImmutableList.of() : ImmutableList.copyOf(permissions);
   }
 
diff --git a/java/com/google/gerrit/common/data/GlobalCapability.java b/java/com/google/gerrit/common/data/GlobalCapability.java
index 3e11256..fbe1deb 100644
--- a/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -22,7 +22,7 @@
 
 /** Server wide capabilities. Represented as {@link Permission} objects. */
 public class GlobalCapability {
-  /** Ability to access the database (with gsql). */
+  /** Ability to view code review metadata refs in repositories. */
   public static final String ACCESS_DATABASE = "accessDatabase";
 
   /**
diff --git a/java/com/google/gerrit/common/data/LabelType.java b/java/com/google/gerrit/common/data/LabelType.java
index ff7d25b..be4c33c 100644
--- a/java/com/google/gerrit/common/data/LabelType.java
+++ b/java/com/google/gerrit/common/data/LabelType.java
@@ -94,8 +94,7 @@
 
   protected String name;
 
-  // String rather than LabelFunction for backwards compatibility with GWT JSON interface.
-  protected String functionName;
+  protected LabelFunction function;
 
   protected boolean copyMinScore;
   protected boolean copyMaxScore;
@@ -123,7 +122,7 @@
     values = sortValues(valueList);
     defaultValue = 0;
 
-    functionName = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
+    function = LabelFunction.MAX_WITH_BLOCK;
 
     maxNegative = Short.MIN_VALUE;
     maxPositive = Short.MAX_VALUE;
@@ -160,15 +159,11 @@
   }
 
   public LabelFunction getFunction() {
-    if (functionName == null) {
-      return null;
-    }
-    return LabelFunction.parse(functionName)
-        .orElseThrow(() -> new IllegalStateException("Unsupported functionName: " + functionName));
+    return function;
   }
 
   public void setFunction(@Nullable LabelFunction function) {
-    this.functionName = function != null ? function.getFunctionName() : null;
+    this.function = function;
   }
 
   public boolean canOverride() {
diff --git a/java/com/google/gerrit/common/data/Permission.java b/java/com/google/gerrit/common/data/Permission.java
index a30d412..2e9c2d6 100644
--- a/java/com/google/gerrit/common/data/Permission.java
+++ b/java/com/google/gerrit/common/data/Permission.java
@@ -158,7 +158,7 @@
     exclusiveGroup = newExclusiveGroup;
   }
 
-  public List<PermissionRule> getRules() {
+  public ImmutableList<PermissionRule> getRules() {
     return rules == null ? ImmutableList.of() : ImmutableList.copyOf(rules);
   }
 
diff --git a/java/com/google/gerrit/common/data/ProjectAccess.java b/java/com/google/gerrit/common/data/ProjectAccess.java
index ea17525..a40af22 100644
--- a/java/com/google/gerrit/common/data/ProjectAccess.java
+++ b/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import java.util.List;
@@ -31,7 +32,7 @@
   protected LabelTypes labelTypes;
   protected Map<String, String> capabilities;
   protected Map<AccountGroup.UUID, GroupInfo> groupInfo;
-  protected List<WebLinkInfoCommon> fileHistoryLinks;
+  protected List<WebLinkInfo> fileHistoryLinks;
 
   public ProjectAccess() {}
 
@@ -132,11 +133,11 @@
     groupInfo = m;
   }
 
-  public void setFileHistoryLinks(List<WebLinkInfoCommon> links) {
+  public void setFileHistoryLinks(List<WebLinkInfo> links) {
     fileHistoryLinks = links;
   }
 
-  public List<WebLinkInfoCommon> getFileHistoryLinks() {
+  public List<WebLinkInfo> getFileHistoryLinks() {
     return fileHistoryLinks;
   }
 }
diff --git a/java/com/google/gerrit/common/data/WebLinkInfoCommon.java b/java/com/google/gerrit/common/data/WebLinkInfoCommon.java
deleted file mode 100644
index dd0a70a..0000000
--- a/java/com/google/gerrit/common/data/WebLinkInfoCommon.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2015 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.common.data;
-
-public class WebLinkInfoCommon {
-  public WebLinkInfoCommon() {}
-
-  public String name;
-  public String imageUrl;
-  public String url;
-  public String target;
-}
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 6da19cd..3b589b2 100644
--- a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -107,6 +107,7 @@
     return content;
   }
 
+  private final ElasticConfiguration config;
   private final Schema<V> schema;
   private final SitePaths sitePaths;
   private final String indexNameRaw;
@@ -118,17 +119,18 @@
   protected final ElasticQueryBuilder queryBuilder;
 
   AbstractElasticIndex(
-      ElasticConfiguration cfg,
+      ElasticConfiguration config,
       SitePaths sitePaths,
       Schema<V> schema,
       ElasticRestClientProvider client,
       String indexName,
       String indexType) {
+    this.config = config;
     this.sitePaths = sitePaths;
     this.schema = schema;
     this.gson = new GsonBuilder().setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES).create();
     this.queryBuilder = new ElasticQueryBuilder();
-    this.indexName = cfg.getIndexName(indexName, schema.getVersion());
+    this.indexName = config.getIndexName(indexName, schema.getVersion());
     this.indexNameRaw = indexName;
     this.client = client;
     this.type = client.adapter().getType(indexType);
@@ -199,7 +201,7 @@
   protected abstract String getMappings();
 
   private String getSettings() {
-    return gson.toJson(ImmutableMap.of(SETTINGS, ElasticSetting.createSetting()));
+    return gson.toJson(ImmutableMap.of(SETTINGS, ElasticSetting.createSetting(config)));
   }
 
   protected abstract String getId(V v);
@@ -293,8 +295,11 @@
   }
 
   protected String getURI(String type, String request) throws UnsupportedEncodingException {
-    String encodedType = URLEncoder.encode(type, UTF_8.toString());
     String encodedIndexName = URLEncoder.encode(indexName, UTF_8.toString());
+    if (SEARCH.equals(request) && client.adapter().omitTypeFromSearch()) {
+      return encodedIndexName + "/" + request;
+    }
+    String encodedType = URLEncoder.encode(type, UTF_8.toString());
     return encodedIndexName + "/" + encodedType + "/" + request;
   }
 
diff --git a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
index 8d29d21..6863238 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
@@ -40,9 +40,13 @@
   static final String KEY_MAX_RETRY_TIMEOUT = "maxRetryTimeout";
   static final String KEY_PREFIX = "prefix";
   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 DEFAULT_PORT = "9200";
   static final String DEFAULT_USERNAME = "elastic";
   static final int DEFAULT_MAX_RETRY_TIMEOUT_MS = 30000;
+  static final int DEFAULT_NUMBER_OF_SHARDS = 5;
+  static final int DEFAULT_NUMBER_OF_REPLICAS = 1;
   static final TimeUnit MAX_RETRY_TIMEOUT_UNIT = TimeUnit.MILLISECONDS;
 
   private final Config cfg;
@@ -51,6 +55,8 @@
   final String username;
   final String password;
   final int maxRetryTimeout;
+  final int numberOfShards;
+  final int numberOfReplicas;
   final String prefix;
 
   @Inject
@@ -71,6 +77,10 @@
                 DEFAULT_MAX_RETRY_TIMEOUT_MS,
                 MAX_RETRY_TIMEOUT_UNIT);
     this.prefix = Strings.nullToEmpty(cfg.getString(SECTION_ELASTICSEARCH, null, KEY_PREFIX));
+    this.numberOfShards =
+        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.hosts = new ArrayList<>();
     for (String server : cfg.getStringList(SECTION_ELASTICSEARCH, null, KEY_SERVER)) {
       try {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
index 8011efa..b9d86d5 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
@@ -17,7 +17,6 @@
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.common.primitives.Ints;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.index.Index;
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.Schema;
@@ -26,6 +25,7 @@
 import com.google.gerrit.server.index.GerritIndexStatus;
 import com.google.gerrit.server.index.OnlineUpgradeListener;
 import com.google.gerrit.server.index.VersionManager;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -45,7 +45,7 @@
   ElasticIndexVersionManager(
       @GerritServerConfig Config cfg,
       SitePaths sitePaths,
-      DynamicSet<OnlineUpgradeListener> listeners,
+      PluginSetContext<OnlineUpgradeListener> listeners,
       Collection<IndexDefinition<?, ?, ?>> defs,
       ElasticIndexVersionDiscovery versionDiscovery) {
     super(sitePaths, listeners, defs, VersionManager.getOnlineUpgrade(cfg));
diff --git a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
index 65d2916..40c1bbb 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
@@ -21,6 +21,7 @@
 
   private final boolean ignoreUnmapped;
   private final boolean usePostV5Type;
+  private final boolean omitTypeFromSearch;
 
   private final String searchFilteringName;
   private final String indicesExistParam;
@@ -31,33 +32,16 @@
   private final String versionDiscoveryUrl;
 
   ElasticQueryAdapter(ElasticVersion version) {
-    this.ignoreUnmapped = version == ElasticVersion.V2_4;
-    this.usePostV5Type = version.isV6();
-    this.versionDiscoveryUrl = version.isV6() ? "/%s*" : "/%s*/_aliases";
-
-    switch (version) {
-      case V5_6:
-      case V6_2:
-      case V6_3:
-      case V6_4:
-      case V6_5:
-        this.searchFilteringName = "_source";
-        this.indicesExistParam = "?allow_no_indices=false";
-        this.exactFieldType = "keyword";
-        this.stringFieldType = "text";
-        this.indexProperty = "true";
-        this.rawFieldsKey = "_source";
-        break;
-      case V2_4:
-      default:
-        this.searchFilteringName = "fields";
-        this.indicesExistParam = "";
-        this.exactFieldType = "string";
-        this.stringFieldType = "string";
-        this.indexProperty = "not_analyzed";
-        this.rawFieldsKey = "fields";
-        break;
-    }
+    this.ignoreUnmapped = false;
+    this.usePostV5Type = version.isV6OrLater();
+    this.omitTypeFromSearch = version.isV7OrLater();
+    this.versionDiscoveryUrl = version.isV6OrLater() ? "/%s*" : "/%s*/_aliases";
+    this.searchFilteringName = "_source";
+    this.indicesExistParam = "?allow_no_indices=false";
+    this.exactFieldType = "keyword";
+    this.stringFieldType = "text";
+    this.indexProperty = "true";
+    this.rawFieldsKey = "_source";
   }
 
   void setIgnoreUnmapped(JsonObject properties) {
@@ -100,8 +84,12 @@
     return usePostV5Type;
   }
 
-  String getType(String preV6Type) {
-    return usePostV5Type() ? POST_V5_TYPE : preV6Type;
+  boolean omitTypeFromSearch() {
+    return omitTypeFromSearch;
+  }
+
+  String getType(String type) {
+    return usePostV5Type() ? POST_V5_TYPE : type;
   }
 
   String getVersionDiscoveryUrl(String name) {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticSetting.java b/java/com/google/gerrit/elasticsearch/ElasticSetting.java
index 6fd234d..98c313c 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticSetting.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticSetting.java
@@ -22,33 +22,33 @@
   private static final ImmutableMap<String, String> CUSTOM_CHAR_MAPPING =
       ImmutableMap.of("\\u002E", "\\u0020", "\\u005F", "\\u0020");
 
-  static SettingProperties createSetting() {
-    ElasticSetting.Builder settings = new ElasticSetting.Builder();
-    settings.addCharFilter();
-    settings.addAnalyzer();
-    return settings.build();
+  static SettingProperties createSetting(ElasticConfiguration config) {
+    return new ElasticSetting.Builder().addCharFilter().addAnalyzer().build(config);
   }
 
   static class Builder {
     private final ImmutableMap.Builder<String, FieldProperties> fields =
         new ImmutableMap.Builder<>();
 
-    SettingProperties build() {
+    SettingProperties build(ElasticConfiguration config) {
       SettingProperties properties = new SettingProperties();
       properties.analysis = fields.build();
+      properties.numberOfShards = config.numberOfShards;
+      properties.numberOfReplicas = config.numberOfReplicas;
       return properties;
     }
 
-    void addCharFilter() {
+    Builder addCharFilter() {
       FieldProperties charMapping = new FieldProperties("mapping");
       charMapping.mappings = getCustomCharMappings(CUSTOM_CHAR_MAPPING);
 
       FieldProperties charFilter = new FieldProperties();
       charFilter.customMapping = charMapping;
       fields.put("char_filter", charFilter);
+      return this;
     }
 
-    void addAnalyzer() {
+    Builder addAnalyzer() {
       FieldProperties customAnalyzer = new FieldProperties("custom");
       customAnalyzer.tokenizer = "standard";
       customAnalyzer.charFilter = new String[] {"custom_mapping"};
@@ -57,6 +57,7 @@
       FieldProperties analyzer = new FieldProperties();
       analyzer.customWithCharFilter = customAnalyzer;
       fields.put("analyzer", analyzer);
+      return this;
     }
 
     private static String[] getCustomCharMappings(ImmutableMap<String, String> map) {
@@ -72,6 +73,8 @@
 
   static class SettingProperties {
     Map<String, FieldProperties> analysis;
+    Integer numberOfShards;
+    Integer numberOfReplicas;
   }
 
   static class FieldProperties {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
index 4c98df1..b69f8f9 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticVersion.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -18,12 +18,12 @@
 import java.util.regex.Pattern;
 
 public enum ElasticVersion {
-  V2_4("2.4.*"),
   V5_6("5.6.*"),
   V6_2("6.2.*"),
   V6_3("6.3.*"),
   V6_4("6.4.*"),
-  V6_5("6.5.*");
+  V6_5("6.5.*"),
+  V7_0("7.0.*");
 
   private final String version;
   private final Pattern pattern;
@@ -56,8 +56,16 @@
     return Joiner.on(", ").join(ElasticVersion.values());
   }
 
-  public boolean isV6() {
-    return version.startsWith("6.");
+  public boolean isV6OrLater() {
+    return isAtLeastVersion(6);
+  }
+
+  public boolean isV7OrLater() {
+    return isAtLeastVersion(7);
+  }
+
+  private boolean isAtLeastVersion(int v) {
+    return Integer.valueOf(version.split("\\.")[0]) >= v;
   }
 
   @Override
diff --git a/java/com/google/gerrit/extensions/client/MenuItem.java b/java/com/google/gerrit/extensions/client/MenuItem.java
index 8375bba..0c7dd88 100644
--- a/java/com/google/gerrit/extensions/client/MenuItem.java
+++ b/java/com/google/gerrit/extensions/client/MenuItem.java
@@ -22,11 +22,6 @@
   public final String target;
   public final String id;
 
-  // Needed for GWT
-  public MenuItem() {
-    this(null, null, null, null);
-  }
-
   public MenuItem(String name, String url) {
     this(name, url, "_blank");
   }
diff --git a/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index e74c4b2..197a3b9 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.httpd;
 
 import com.google.common.cache.Cache;
-import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.Capable;
@@ -38,6 +38,7 @@
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.util.time.TimeUtil;
@@ -52,6 +53,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -61,6 +63,7 @@
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.http.server.GitServlet;
 import org.eclipse.jgit.http.server.GitSmartHttpTools;
@@ -130,6 +133,55 @@
     }
   }
 
+  static class HttpServletResponseWithStatusWrapper extends HttpServletResponseWrapper {
+    private int responseStatus;
+
+    HttpServletResponseWithStatusWrapper(HttpServletResponse response) {
+      super(response);
+      /* Even if we could read the status from response, we assume that it is all
+       * fine because we entered the filter without any prior issues.
+       * When Google will have upgraded to Servlet 3.0, we could actually
+       * call response.getStatus() and the code will be clearer.
+       */
+      responseStatus = HttpServletResponse.SC_OK;
+    }
+
+    @Override
+    public void setStatus(int sc) {
+      responseStatus = sc;
+      super.setStatus(sc);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public void setStatus(int sc, String sm) {
+      responseStatus = sc;
+      super.setStatus(sc, sm);
+    }
+
+    @Override
+    public void sendError(int sc) throws IOException {
+      this.responseStatus = sc;
+      super.sendError(sc);
+    }
+
+    @Override
+    public void sendError(int sc, String msg) throws IOException {
+      this.responseStatus = sc;
+      super.sendError(sc, msg);
+    }
+
+    @Override
+    public void sendRedirect(String location) throws IOException {
+      this.responseStatus = HttpServletResponse.SC_MOVED_TEMPORARILY;
+      super.sendRedirect(location);
+    }
+
+    public int getResponseStatus() {
+      return responseStatus;
+    }
+  }
+
   @Inject
   GitOverHttpServlet(
       Resolver resolver,
@@ -156,19 +208,15 @@
   }
 
   private static ListMultimap<String, String> extractParameters(HttpServletRequest request) {
-
-    ListMultimap<String, String> multiMap = ArrayListMultimap.create();
-    if (request.getQueryString() != null) {
-      request
-          .getParameterMap()
-          .forEach(
-              (k, v) -> {
-                for (int i = 0; i < v.length; i++) {
-                  multiMap.put(k, v[i]);
-                }
-              });
+    if (request.getQueryString() == null) {
+      return ImmutableListMultimap.of();
     }
-    return multiMap;
+    // Explicit cast is required to compile under Servlet API 2.5, where the return type is raw Map.
+    @SuppressWarnings("cast")
+    Map<String, String[]> parameterMap = (Map<String, String[]>) request.getParameterMap();
+    ImmutableListMultimap.Builder<String, String> b = ImmutableListMultimap.builder();
+    parameterMap.forEach(b::putAll);
+    return b.build();
   }
 
   static class Resolver implements RepositoryResolver<HttpServletRequest> {
@@ -238,14 +286,14 @@
     private final TransferConfig config;
     private final DynamicSet<PreUploadHook> preUploadHooks;
     private final DynamicSet<PostUploadHook> postUploadHooks;
-    private final DynamicSet<UploadPackInitializer> uploadPackInitializers;
+    private final PluginSetContext<UploadPackInitializer> uploadPackInitializers;
 
     @Inject
     UploadFactory(
         TransferConfig tc,
         DynamicSet<PreUploadHook> preUploadHooks,
         DynamicSet<PostUploadHook> postUploadHooks,
-        DynamicSet<UploadPackInitializer> uploadPackInitializers) {
+        PluginSetContext<UploadPackInitializer> uploadPackInitializers) {
       this.config = tc;
       this.preUploadHooks = preUploadHooks;
       this.postUploadHooks = postUploadHooks;
@@ -267,9 +315,7 @@
         }
       }
       ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
-      for (UploadPackInitializer initializer : uploadPackInitializers) {
-        initializer.init(state.getNameKey(), up);
-      }
+      uploadPackInitializers.runEach(initializer -> initializer.init(state.getNameKey(), up));
       return up;
     }
   }
@@ -301,41 +347,48 @@
       UploadPack up = (UploadPack) request.getAttribute(ServletUtils.ATTRIBUTE_HANDLER);
       PermissionBackend.ForProject perm =
           permissionBackend.currentUser().project(state.getNameKey());
+      HttpServletResponseWithStatusWrapper responseWrapper =
+          new HttpServletResponseWithStatusWrapper((HttpServletResponse) response);
+      HttpServletRequest httpRequest = (HttpServletRequest) request;
+      String sessionId = httpRequest.getSession().getId();
+
       try {
-        perm.check(ProjectPermission.RUN_UPLOAD_PACK);
-      } catch (AuthException e) {
-        GitSmartHttpTools.sendError(
-            (HttpServletRequest) request,
-            (HttpServletResponse) response,
-            HttpServletResponse.SC_FORBIDDEN,
-            "upload-pack not permitted on this server");
-        return;
-      } catch (PermissionBackendException e) {
-        throw new ServletException(e);
+        try {
+          perm.check(ProjectPermission.RUN_UPLOAD_PACK);
+        } catch (AuthException e) {
+          GitSmartHttpTools.sendError(
+              (HttpServletRequest) request,
+              responseWrapper,
+              HttpServletResponse.SC_FORBIDDEN,
+              "upload-pack not permitted on this server");
+          return;
+        } catch (PermissionBackendException e) {
+          responseWrapper.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+          throw new ServletException(e);
+        }
+
+        // We use getRemoteHost() here instead of getRemoteAddr() because REMOTE_ADDR
+        // may have been overridden by a proxy server -- we'll try to avoid this.
+        UploadValidators uploadValidators =
+            uploadValidatorsFactory.create(state.getProject(), repo, request.getRemoteHost());
+        up.setPreUploadHook(
+            PreUploadHookChain.newChain(
+                Lists.newArrayList(up.getPreUploadHook(), uploadValidators)));
+        up.setAdvertiseRefsHook(new DefaultAdvertiseRefsHook(perm, RefFilterOptions.defaults()));
+        next.doFilter(httpRequest, responseWrapper);
       } finally {
-        HttpServletRequest httpRequest = (HttpServletRequest) request;
-        HttpServletResponse httpResponse = (HttpServletResponse) response;
         groupAuditService.dispatch(
             new HttpAuditEvent(
-                httpRequest.getSession().getId(),
+                sessionId,
                 userProvider.get(),
                 extractWhat(httpRequest),
                 TimeUtil.nowMs(),
                 extractParameters(httpRequest),
                 httpRequest.getMethod(),
                 httpRequest,
-                httpResponse.getStatus(),
-                httpResponse));
+                responseWrapper.getResponseStatus(),
+                responseWrapper));
       }
-
-      // We use getRemoteHost() here instead of getRemoteAddr() because REMOTE_ADDR
-      // may have been overridden by a proxy server -- we'll try to avoid this.
-      UploadValidators uploadValidators =
-          uploadValidatorsFactory.create(state.getProject(), repo, request.getRemoteHost());
-      up.setPreUploadHook(
-          PreUploadHookChain.newChain(Lists.newArrayList(up.getPreUploadHook(), uploadValidators)));
-      up.setAdvertiseRefsHook(new DefaultAdvertiseRefsHook(perm, RefFilterOptions.defaults()));
-      next.doFilter(request, response);
     }
 
     @Override
@@ -411,25 +464,28 @@
       rp.getAdvertiseRefsHook().advertiseRefs(rp);
 
       ProjectState state = (ProjectState) request.getAttribute(ATT_STATE);
+      HttpServletResponseWithStatusWrapper responseWrapper =
+          new HttpServletResponseWithStatusWrapper((HttpServletResponse) response);
+      HttpServletRequest httpRequest = (HttpServletRequest) request;
       Capable canUpload;
       try {
-        permissionBackend
-            .currentUser()
-            .project(state.getNameKey())
-            .check(ProjectPermission.RUN_RECEIVE_PACK);
-        canUpload = arc.canUpload();
-      } catch (AuthException e) {
-        GitSmartHttpTools.sendError(
-            (HttpServletRequest) request,
-            (HttpServletResponse) response,
-            HttpServletResponse.SC_FORBIDDEN,
-            "receive-pack not permitted on this server");
-        return;
-      } catch (PermissionBackendException e) {
-        throw new RuntimeException(e);
+        try {
+          permissionBackend
+              .currentUser()
+              .project(state.getNameKey())
+              .check(ProjectPermission.RUN_RECEIVE_PACK);
+          canUpload = arc.canUpload();
+        } catch (AuthException e) {
+          GitSmartHttpTools.sendError(
+              httpRequest,
+              responseWrapper,
+              HttpServletResponse.SC_FORBIDDEN,
+              "receive-pack not permitted on this server");
+          return;
+        } catch (PermissionBackendException e) {
+          throw new RuntimeException(e);
+        }
       } finally {
-        HttpServletRequest httpRequest = (HttpServletRequest) request;
-        HttpServletResponse httpResponse = (HttpServletResponse) response;
         groupAuditService.dispatch(
             new HttpAuditEvent(
                 httpRequest.getSession().getId(),
@@ -439,26 +495,26 @@
                 extractParameters(httpRequest),
                 httpRequest.getMethod(),
                 httpRequest,
-                httpResponse.getStatus(),
-                httpResponse));
+                responseWrapper.getResponseStatus(),
+                responseWrapper));
       }
 
       if (canUpload != Capable.OK) {
         GitSmartHttpTools.sendError(
-            (HttpServletRequest) request,
-            (HttpServletResponse) response,
+            httpRequest,
+            responseWrapper,
             HttpServletResponse.SC_FORBIDDEN,
             "\n" + canUpload.getMessage());
         return;
       }
 
       if (!rp.isCheckReferencedObjectsAreReachable()) {
-        chain.doFilter(request, response);
+        chain.doFilter(request, responseWrapper);
         return;
       }
 
       if (!(userProvider.get().isIdentifiedUser())) {
-        chain.doFilter(request, response);
+        chain.doFilter(request, responseWrapper);
         return;
       }
 
@@ -475,7 +531,7 @@
         }
       }
 
-      chain.doFilter(request, response);
+      chain.doFilter(request, responseWrapper);
 
       if (isGet) {
         cache.put(cacheKey, Collections.unmodifiableSet(new HashSet<>(rp.getAdvertisedObjects())));
diff --git a/java/com/google/gerrit/httpd/UniversalWebLoginFilter.java b/java/com/google/gerrit/httpd/UniversalWebLoginFilter.java
index 4878006..6e32980 100644
--- a/java/com/google/gerrit/httpd/UniversalWebLoginFilter.java
+++ b/java/com/google/gerrit/httpd/UniversalWebLoginFilter.java
@@ -14,10 +14,11 @@
 
 package com.google.gerrit.httpd;
 
-import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.plugincontext.PluginItemContext;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -34,8 +35,8 @@
 import javax.servlet.http.HttpServletResponse;
 
 public class UniversalWebLoginFilter implements Filter {
-  private final DynamicItem<WebSession> session;
-  private final DynamicSet<WebLoginListener> webLoginListeners;
+  private final PluginItemContext<WebSession> session;
+  private final PluginSetContext<WebLoginListener> webLoginListeners;
   private final Provider<CurrentUser> userProvider;
 
   public static ServletModule module() {
@@ -52,8 +53,8 @@
 
   @Inject
   public UniversalWebLoginFilter(
-      DynamicItem<WebSession> session,
-      DynamicSet<WebLoginListener> webLoginListeners,
+      PluginItemContext<WebSession> session,
+      PluginSetContext<WebLoginListener> webLoginListeners,
       Provider<CurrentUser> userProvider) {
     this.session = session;
     this.webLoginListeners = webLoginListeners;
@@ -75,20 +76,18 @@
     Optional<IdentifiedUser> loggedInUserAfter = loggedInUser();
 
     if (!loggedInUserBefore.isPresent() && loggedInUserAfter.isPresent()) {
-      for (WebLoginListener loginListener : webLoginListeners) {
-        loginListener.onLogin(loggedInUserAfter.get(), httpRequest, wrappedResponse);
-      }
+      webLoginListeners.runEach(
+          l -> l.onLogin(loggedInUserAfter.get(), httpRequest, wrappedResponse));
     } else if (loggedInUserBefore.isPresent() && !loggedInUserAfter.isPresent()) {
-      for (WebLoginListener loginListener : webLoginListeners) {
-        loginListener.onLogout(loggedInUserBefore.get(), httpRequest, wrappedResponse);
-      }
+      webLoginListeners.runEach(
+          l -> l.onLogout(loggedInUserBefore.get(), httpRequest, wrappedResponse));
     }
 
     wrappedResponse.play();
   }
 
   private Optional<IdentifiedUser> loggedInUser() {
-    return session.get().isSignedIn()
+    return session.call(s -> s.isSignedIn())
         ? Optional.of(userProvider.get().asIdentifiedUser())
         : Optional.empty();
   }
diff --git a/java/com/google/gerrit/httpd/init/SiteInitializer.java b/java/com/google/gerrit/httpd/init/SiteInitializer.java
index 67510cd..04a0ddd 100644
--- a/java/com/google/gerrit/httpd/init/SiteInitializer.java
+++ b/java/com/google/gerrit/httpd/init/SiteInitializer.java
@@ -47,7 +47,7 @@
       if (sitePath != null) {
         Path site = Paths.get(sitePath);
         logger.atInfo().log("Initializing site at %s", site.toRealPath().normalize());
-        new BaseInit(site, false, true, pluginsDistribution, pluginsToInstall).run();
+        new BaseInit(site, false, pluginsDistribution, pluginsToInstall).run();
         return;
       }
 
@@ -62,14 +62,7 @@
       }
       if (site != null) {
         logger.atInfo().log("Initializing site at %s", site.toRealPath().normalize());
-        new BaseInit(
-                site,
-                new ReviewDbDataSourceProvider(),
-                false,
-                false,
-                pluginsDistribution,
-                pluginsToInstall)
-            .run();
+        new BaseInit(site, false, pluginsDistribution, pluginsToInstall).run();
       }
     } catch (Exception e) {
       logger.atSevere().withCause(e).log("Site init failed");
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 8a3f618..8b113b2 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -85,13 +85,10 @@
 import com.google.gerrit.server.plugins.PluginModule;
 import com.google.gerrit.server.project.DefaultProjectNameLockManager;
 import com.google.gerrit.server.restapi.RestApiModule;
-import com.google.gerrit.server.schema.DataSourceModule;
-import com.google.gerrit.server.schema.DataSourceProvider;
-import com.google.gerrit.server.schema.DataSourceType;
 import com.google.gerrit.server.schema.DatabaseModule;
 import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
+import com.google.gerrit.server.schema.NoteDbSchemaVersionCheck;
 import com.google.gerrit.server.schema.ReviewDbSchemaModule;
-import com.google.gerrit.server.schema.ReviewDbSchemaVersionCheck;
 import com.google.gerrit.server.securestore.SecureStoreClassName;
 import com.google.gerrit.server.ssh.NoSshModule;
 import com.google.gerrit.server.ssh.SshAddressesModule;
@@ -266,28 +263,6 @@
 
       Module configModule = new GerritServerConfigModule();
       modules.add(configModule);
-
-      Injector cfgInjector = Guice.createInjector(sitePathModule, configModule, secureStore);
-      Config cfg = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
-      String dbType = cfg.getString("database", null, "type");
-
-      final DataSourceType dst =
-          Guice.createInjector(new DataSourceModule(), configModule, sitePathModule, secureStore)
-              .getInstance(Key.get(DataSourceType.class, Names.named(dbType.toLowerCase())));
-      modules.add(
-          new LifecycleModule() {
-            @Override
-            protected void configure() {
-              bind(DataSourceType.class).toInstance(dst);
-              bind(DataSourceProvider.Context.class)
-                  .toInstance(DataSourceProvider.Context.MULTI_USER);
-              bind(Key.get(DataSource.class, Names.named("ReviewDb")))
-                  .toProvider(DataSourceProvider.class)
-                  .in(SINGLETON);
-              listener().to(DataSourceProvider.class);
-            }
-          });
-
     } else {
       modules.add(
           new LifecycleModule() {
@@ -310,7 +285,7 @@
   private Injector createCfgInjector() {
     final List<Module> modules = new ArrayList<>();
     modules.add(new ReviewDbSchemaModule());
-    modules.add(ReviewDbSchemaVersionCheck.module());
+    modules.add(NoteDbSchemaVersionCheck.module());
     modules.add(new AuthConfigModule());
     return dbInjector.createChildInjector(modules);
   }
diff --git a/java/com/google/gerrit/httpd/raw/CatServlet.java b/java/com/google/gerrit/httpd/raw/CatServlet.java
index 4b5c227..051b33a 100644
--- a/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -143,7 +143,7 @@
           return;
         }
       } else {
-        PatchSet patchSet = psUtil.get(requestDb.get(), notes, patchKey.getParentKey());
+        PatchSet patchSet = psUtil.get(notes, patchKey.getParentKey());
         if (patchSet == null) {
           rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
           return;
diff --git a/java/com/google/gerrit/httpd/restapi/LogRedactUtil.java b/java/com/google/gerrit/httpd/restapi/LogRedactUtil.java
index 5a2a033..5a37b7b 100644
--- a/java/com/google/gerrit/httpd/restapi/LogRedactUtil.java
+++ b/java/com/google/gerrit/httpd/restapi/LogRedactUtil.java
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// WARNING: NoteDbUpdateManager cares about the package name RestApiServlet lives in.
-
 package com.google.gerrit.httpd.restapi;
 
 import static com.google.gerrit.httpd.restapi.RestApiServlet.XD_AUTHORIZATION;
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiQuotaEnforcer.java b/java/com/google/gerrit/httpd/restapi/RestApiQuotaEnforcer.java
index c7678c2..61132e7 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiQuotaEnforcer.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiQuotaEnforcer.java
@@ -70,8 +70,8 @@
       AccountResource accountResource = (AccountResource) rsrc;
       report = quotaBackend.currentUser().account(accountResource.getUser().getAccountId());
     } else if (rsrc instanceof ProjectResource) {
-      ProjectResource accountResource = (ProjectResource) rsrc;
-      report = quotaBackend.currentUser().account(accountResource.getUser().getAccountId());
+      ProjectResource projectResource = (ProjectResource) rsrc;
+      report = quotaBackend.currentUser().project(projectResource.getNameKey());
     }
 
     report.requestToken(quotaGroup(pathForQuotaReporting, req.getMethod())).throwOnError();
diff --git a/java/com/google/gerrit/launcher/GerritLauncher.java b/java/com/google/gerrit/launcher/GerritLauncher.java
index 5406d52..868c1d5 100644
--- a/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -94,7 +94,6 @@
       System.err.println("  init            Initialize a Gerrit installation");
       System.err.println("  reindex         Rebuild the secondary index");
       System.err.println("  daemon          Run the Gerrit network daemons");
-      System.err.println("  gsql            Run the interactive query console");
       System.err.println("  version         Display the build version number");
       System.err.println("  passwd          Set or change password in secure.config");
 
diff --git a/java/com/google/gerrit/lucene/LuceneVersionManager.java b/java/com/google/gerrit/lucene/LuceneVersionManager.java
index 63abea8..d3a1a24 100644
--- a/java/com/google/gerrit/lucene/LuceneVersionManager.java
+++ b/java/com/google/gerrit/lucene/LuceneVersionManager.java
@@ -16,7 +16,6 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.common.primitives.Ints;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.index.Index;
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.Schema;
@@ -25,6 +24,7 @@
 import com.google.gerrit.server.index.GerritIndexStatus;
 import com.google.gerrit.server.index.OnlineUpgradeListener;
 import com.google.gerrit.server.index.VersionManager;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -47,7 +47,7 @@
   LuceneVersionManager(
       @GerritServerConfig Config cfg,
       SitePaths sitePaths,
-      DynamicSet<OnlineUpgradeListener> listeners,
+      PluginSetContext<OnlineUpgradeListener> listeners,
       Collection<IndexDefinition<?, ?, ?>> defs) {
     super(sitePaths, listeners, defs, VersionManager.getOnlineUpgrade(cfg));
   }
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 33b3268..e7e8ea3 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -14,9 +14,7 @@
 
 package com.google.gerrit.pgm;
 
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
@@ -86,18 +84,15 @@
 import com.google.gerrit.server.mail.receive.MailReceiver;
 import com.google.gerrit.server.mail.send.SmtpEmailSender;
 import com.google.gerrit.server.mime.MimeUtil2Module;
-import com.google.gerrit.server.notedb.rebuild.NoteDbMigrator;
-import com.google.gerrit.server.notedb.rebuild.OnlineNoteDbMigrator;
 import com.google.gerrit.server.patch.DiffExecutorModule;
 import com.google.gerrit.server.permissions.DefaultPermissionBackendModule;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
 import com.google.gerrit.server.plugins.PluginModule;
 import com.google.gerrit.server.project.DefaultProjectNameLockManager;
 import com.google.gerrit.server.restapi.RestApiModule;
-import com.google.gerrit.server.schema.DataSourceProvider;
 import com.google.gerrit.server.schema.InMemoryAccountPatchReviewStore;
 import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
-import com.google.gerrit.server.schema.ReviewDbSchemaVersionCheck;
+import com.google.gerrit.server.schema.NoteDbSchemaVersionCheck;
 import com.google.gerrit.server.securestore.DefaultSecureStore;
 import com.google.gerrit.server.securestore.SecureStore;
 import com.google.gerrit.server.securestore.SecureStoreClassName;
@@ -128,7 +123,6 @@
 import javax.servlet.http.HttpServletRequest;
 import org.eclipse.jgit.lib.Config;
 import org.kohsuke.args4j.Option;
-import org.kohsuke.args4j.spi.ExplicitBooleanOptionHandler;
 
 /** Run SSH daemon portions of Gerrit. */
 public class Daemon extends SiteProgram {
@@ -177,15 +171,6 @@
   @Option(name = "--stop-only", usage = "Stop the daemon", hidden = true)
   private boolean stopOnly;
 
-  @Option(
-      name = "--migrate-to-note-db",
-      usage = "Automatically migrate changes to NoteDb",
-      handler = ExplicitBooleanOptionHandler.class)
-  private boolean migrateToNoteDb;
-
-  @Option(name = "--trial", usage = "(With --migrate-to-note-db) " + MigrateToNoteDb.TRIAL_USAGE)
-  private boolean trial;
-
   private final LifecycleManager manager = new LifecycleManager();
   private Injector dbInjector;
   private Injector cfgInjector;
@@ -289,8 +274,6 @@
       if (inspector) {
         JythonShell shell = new JythonShell();
         shell.set("m", manager);
-        shell.set("ds", dbInjector.getInstance(DataSourceProvider.class));
-        shell.set("schk", dbInjector.getInstance(ReviewDbSchemaVersionCheck.class));
         shell.set("d", this);
         shell.run();
       } else {
@@ -339,7 +322,7 @@
   @VisibleForTesting
   public void start() throws IOException {
     if (dbInjector == null) {
-      dbInjector = createDbInjector(true /* enableMetrics */, MULTI_USER);
+      dbInjector = createDbInjector(true /* enableMetrics */);
     }
     cfgInjector = createCfgInjector();
     config = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
@@ -397,7 +380,7 @@
 
   private Injector createSysInjector() {
     final List<Module> modules = new ArrayList<>();
-    modules.add(ReviewDbSchemaVersionCheck.module());
+    modules.add(NoteDbSchemaVersionCheck.module());
     modules.add(new DropWizardMetricMaker.RestModule());
     modules.add(new LogFileCompressor.Module());
 
@@ -496,9 +479,6 @@
       modules.add(new AccountDeactivator.Module());
       modules.add(new ChangeCleanupRunner.Module());
     }
-    if (migrateToNoteDb()) {
-      modules.add(new OnlineNoteDbMigrator.Module(trial));
-    }
     if (testSysModule != null) {
       modules.add(testSysModule);
     }
@@ -508,18 +488,11 @@
         ModuleOverloader.override(modules, LibModuleLoader.loadModules(cfgInjector)));
   }
 
-  private boolean migrateToNoteDb() {
-    return migrateToNoteDb || NoteDbMigrator.getAutoMigrate(requireNonNull(config));
-  }
-
   private Module createIndexModule() {
     if (luceneModule != null) {
       return luceneModule;
     }
-    boolean onlineUpgrade =
-        VersionManager.getOnlineUpgrade(config)
-            // Schema upgrade is handled by OnlineNoteDbMigrator in this case.
-            && !migrateToNoteDb();
+    boolean onlineUpgrade = VersionManager.getOnlineUpgrade(config);
     switch (indexType) {
       case LUCENE:
         return onlineUpgrade
diff --git a/java/com/google/gerrit/pgm/Gsql.java b/java/com/google/gerrit/pgm/Gsql.java
deleted file mode 100644
index 004486b..0000000
--- a/java/com/google/gerrit/pgm/Gsql.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (C) 2009 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.pgm;
-
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
-
-import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.pgm.util.RuntimeShutdown;
-import com.google.gerrit.pgm.util.SiteProgram;
-import com.google.gerrit.sshd.commands.QueryShell;
-import com.google.gerrit.sshd.commands.QueryShell.Factory;
-import com.google.inject.Injector;
-import java.io.IOException;
-import org.kohsuke.args4j.Option;
-
-/** Run Gerrit's SQL query tool */
-public class Gsql extends SiteProgram {
-  private final LifecycleManager manager = new LifecycleManager();
-  private Injector dbInjector;
-
-  @Option(name = "--format", usage = "Set output format")
-  private QueryShell.OutputFormat format = QueryShell.OutputFormat.PRETTY;
-
-  @Option(name = "-c", metaVar = "SQL QUERY", usage = "Query to execute")
-  private String query;
-
-  @Override
-  public int run() throws Exception {
-    mustHaveValidSite();
-
-    dbInjector = createDbInjector(SINGLE_USER);
-    manager.add(dbInjector);
-    manager.start();
-    RuntimeShutdown.add(
-        () -> {
-          try {
-            System.in.close();
-          } catch (IOException e) {
-            // Ignored
-          }
-          manager.stop();
-        });
-    final QueryShell shell = shellFactory().create(System.in, System.out);
-    shell.setOutputFormat(format);
-    if (query != null) {
-      shell.execute(query);
-    } else {
-      shell.run();
-    }
-    return 0;
-  }
-
-  private Factory shellFactory() {
-    return dbInjector
-        .createChildInjector(
-            new FactoryModule() {
-              @Override
-              protected void configure() {
-                factory(QueryShell.Factory.class);
-              }
-            })
-        .getInstance(QueryShell.Factory.class);
-  }
-}
diff --git a/java/com/google/gerrit/pgm/Init.java b/java/com/google/gerrit/pgm/Init.java
index 4ea31da..f31fb2c 100644
--- a/java/com/google/gerrit/pgm/Init.java
+++ b/java/com/google/gerrit/pgm/Init.java
@@ -95,7 +95,7 @@
   }
 
   public Init(Path sitePath) {
-    super(sitePath, true, true, new WarDistribution(), null);
+    super(sitePath, true, new WarDistribution(), null);
     batchMode = true;
     noAutoStart = true;
   }
diff --git a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
index 6ebd6a3..14a0b5d 100644
--- a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
+++ b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.pgm;
 
 import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT;
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
 
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.lifecycle.LifecycleManager;
@@ -29,7 +28,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
-import com.google.gerrit.server.schema.ReviewDbSchemaVersionCheck;
+import com.google.gerrit.server.schema.NoteDbSchemaVersionCheck;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
@@ -54,8 +53,8 @@
 
   @Override
   public int run() throws Exception {
-    Injector dbInjector = createDbInjector(MULTI_USER);
-    manager.add(dbInjector, dbInjector.createChildInjector(ReviewDbSchemaVersionCheck.module()));
+    Injector dbInjector = createDbInjector();
+    manager.add(dbInjector, dbInjector.createChildInjector(NoteDbSchemaVersionCheck.module()));
     manager.start();
     dbInjector
         .createChildInjector(
diff --git a/java/com/google/gerrit/pgm/MigrateAccountPatchReviewDb.java b/java/com/google/gerrit/pgm/MigrateAccountPatchReviewDb.java
index 4ace62b..e12bde2 100644
--- a/java/com/google/gerrit/pgm/MigrateAccountPatchReviewDb.java
+++ b/java/com/google/gerrit/pgm/MigrateAccountPatchReviewDb.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.config.ThreadSettingsConfig;
-import com.google.gerrit.server.schema.DataSourceProvider;
 import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
 import com.google.inject.Injector;
 import com.google.inject.Key;
@@ -49,7 +48,7 @@
 
   @Override
   public int run() throws Exception {
-    Injector dbInjector = createDbInjector(DataSourceProvider.Context.SINGLE_USER);
+    Injector dbInjector = createDbInjector();
     SitePaths sitePaths = new SitePaths(getSitePath());
     ThreadSettingsConfig threadSettingsConfig = dbInjector.getInstance(ThreadSettingsConfig.class);
     Config fakeCfg = new Config();
diff --git a/java/com/google/gerrit/pgm/MigrateToNoteDb.java b/java/com/google/gerrit/pgm/MigrateToNoteDb.java
deleted file mode 100644
index 61d7ed9..0000000
--- a/java/com/google/gerrit/pgm/MigrateToNoteDb.java
+++ /dev/null
@@ -1,222 +0,0 @@
-// Copyright (C) 2014 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.pgm;
-
-import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.stream.Collectors.joining;
-import static java.util.stream.Collectors.toList;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.pgm.util.BatchProgramModule;
-import com.google.gerrit.pgm.util.RuntimeShutdown;
-import com.google.gerrit.pgm.util.SiteProgram;
-import com.google.gerrit.pgm.util.ThreadLimiter;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.git.GarbageCollection;
-import com.google.gerrit.server.index.DummyIndexModule;
-import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
-import com.google.gerrit.server.notedb.rebuild.GcAllUsers;
-import com.google.gerrit.server.notedb.rebuild.NoteDbMigrator;
-import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
-import com.google.gerrit.server.schema.DataSourceType;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Provider;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import org.kohsuke.args4j.Option;
-import org.kohsuke.args4j.spi.ExplicitBooleanOptionHandler;
-
-public class MigrateToNoteDb extends SiteProgram {
-  static final String TRIAL_USAGE =
-      "Trial mode: migrate changes and turn on reading from NoteDb, but leave ReviewDb as the"
-          + " source of truth";
-
-  private static final int ISSUE_8022_THREAD_LIMIT = 4;
-
-  @Option(name = "--threads", usage = "Number of threads to use for rebuilding NoteDb")
-  private Integer threads;
-
-  @Option(
-      name = "--project",
-      usage =
-          "Only rebuild these projects, do no other migration; incompatible with --change;"
-              + " recommended for debugging only")
-  private List<String> projects = new ArrayList<>();
-
-  @Option(
-      name = "--change",
-      usage =
-          "Only rebuild these changes, do no other migration; incompatible with --project;"
-              + " recommended for debugging only")
-  private List<Integer> changes = new ArrayList<>();
-
-  @Option(
-      name = "--force",
-      usage =
-          "Force rebuilding changes where ReviewDb is still the source of truth, even if they"
-              + " were previously migrated")
-  private boolean force;
-
-  @Option(name = "--trial", usage = TRIAL_USAGE)
-  private boolean trial;
-
-  @Option(
-      name = "--sequence-gap",
-      usage =
-          "gap in change sequence numbers between last ReviewDb number and first NoteDb number;"
-              + " negative indicates using the value of noteDb.changes.initialSequenceGap (default"
-              + " 1000)")
-  private int sequenceGap;
-
-  @Option(
-      name = "--reindex",
-      usage =
-          "Reindex all changes after migration; defaults to false in trial mode, true otherwise",
-      handler = ExplicitBooleanOptionHandler.class)
-  private Boolean reindex;
-
-  private Injector dbInjector;
-  private Injector sysInjector;
-  private LifecycleManager dbManager;
-  private LifecycleManager sysManager;
-
-  @Inject private GcAllUsers gcAllUsers;
-  @Inject private Provider<NoteDbMigrator.Builder> migratorBuilderProvider;
-
-  @Override
-  public int run() throws Exception {
-    RuntimeShutdown.add(this::stop);
-    try {
-      mustHaveValidSite();
-      dbInjector = createDbInjector(MULTI_USER);
-
-      dbManager = new LifecycleManager();
-      dbManager.add(dbInjector);
-      dbManager.start();
-
-      threads = limitThreads();
-
-      sysInjector = createSysInjector();
-      sysInjector.injectMembers(this);
-      sysManager = new LifecycleManager();
-      sysManager.add(sysInjector);
-      sysInjector
-          .getInstance(PluginGuiceEnvironment.class)
-          .setDbCfgInjector(dbInjector, dbInjector);
-      sysManager.start();
-
-      try (NoteDbMigrator migrator =
-          migratorBuilderProvider
-              .get()
-              .setThreads(threads)
-              .setProgressOut(System.err)
-              .setProjects(projects.stream().map(Project.NameKey::new).collect(toList()))
-              .setChanges(changes.stream().map(Change.Id::new).collect(toList()))
-              .setTrialMode(trial)
-              .setForceRebuild(force)
-              .setSequenceGap(sequenceGap)
-              .build()) {
-        if (!projects.isEmpty() || !changes.isEmpty()) {
-          migrator.rebuild();
-        } else {
-          migrator.migrate();
-        }
-      }
-      try (PrintWriter w = new PrintWriter(new OutputStreamWriter(System.out, UTF_8), true)) {
-        gcAllUsers.run(w);
-      }
-    } finally {
-      stop();
-    }
-
-    boolean reindex = firstNonNull(this.reindex, !trial);
-    if (!reindex) {
-      return 0;
-    }
-    // Reindex all indices, to save the user from having to run yet another program by hand while
-    // their server is offline.
-    List<String> reindexArgs =
-        ImmutableList.of(
-            "--site-path",
-            getSitePath().toString(),
-            "--threads",
-            Integer.toString(threads),
-            "--index",
-            ChangeSchemaDefinitions.NAME);
-    System.out.println("Migration complete, reindexing changes with:");
-    System.out.println("  reindex " + reindexArgs.stream().collect(joining(" ")));
-    Reindex reindexPgm = new Reindex();
-    return reindexPgm.main(reindexArgs.stream().toArray(String[]::new));
-  }
-
-  private int limitThreads() {
-    if (threads != null) {
-      return threads;
-    }
-    int actualThreads;
-    int procs = Runtime.getRuntime().availableProcessors();
-    DataSourceType dsType = dbInjector.getInstance(DataSourceType.class);
-    if (dsType.getDriver().equals("org.h2.Driver") && procs > ISSUE_8022_THREAD_LIMIT) {
-      System.out.println(
-          "Not using more than "
-              + ISSUE_8022_THREAD_LIMIT
-              + " threads due to http://crbug.com/gerrit/8022");
-      System.out.println("Can be increased by passing --threads, but may cause errors");
-      actualThreads = ISSUE_8022_THREAD_LIMIT;
-    } else {
-      actualThreads = procs;
-    }
-    actualThreads = ThreadLimiter.limitThreads(dbInjector, actualThreads);
-    return actualThreads;
-  }
-
-  private Injector createSysInjector() {
-    return dbInjector.createChildInjector(
-        new FactoryModule() {
-          @Override
-          public void configure() {
-            install(dbInjector.getInstance(BatchProgramModule.class));
-            install(new DummyIndexModule());
-            factory(ChangeResource.Factory.class);
-            factory(GarbageCollection.Factory.class);
-          }
-        });
-  }
-
-  private void stop() {
-    try {
-      LifecycleManager m = sysManager;
-      sysManager = null;
-      if (m != null) {
-        m.stop();
-      }
-    } finally {
-      LifecycleManager m = dbManager;
-      dbManager = null;
-      if (m != null) {
-        m.stop();
-      }
-    }
-  }
-}
diff --git a/java/com/google/gerrit/pgm/ProtobufImport.java b/java/com/google/gerrit/pgm/ProtobufImport.java
index 0179b1d..0335cbb 100644
--- a/java/com/google/gerrit/pgm/ProtobufImport.java
+++ b/java/com/google/gerrit/pgm/ProtobufImport.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.pgm;
 
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
 import static java.util.Objects.requireNonNull;
 
 import com.google.auto.value.AutoValue;
@@ -63,6 +62,7 @@
  * <p><strong>Warning</strong>: This method blindly upserts data into the database. It should only
  * be used to restore a protobuf-formatted backup into a new, empty site.
  */
+// TODO(dborowitz): Delete this program.
 public class ProtobufImport extends SiteProgram {
   @Option(
       name = "--file",
@@ -81,7 +81,7 @@
   public int run() throws Exception {
     mustHaveValidSite();
 
-    Injector dbInjector = createDbInjector(SINGLE_USER);
+    Injector dbInjector = createDbInjector();
     manager.add(dbInjector);
     manager.start();
     RuntimeShutdown.add(manager::stop);
diff --git a/java/com/google/gerrit/pgm/Reindex.java b/java/com/google/gerrit/pgm/Reindex.java
index 52d3bd3..cb129a7 100644
--- a/java/com/google/gerrit/pgm/Reindex.java
+++ b/java/com/google/gerrit/pgm/Reindex.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.pgm;
 
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toSet;
 
@@ -29,7 +28,6 @@
 import com.google.gerrit.lucene.LuceneIndexModule;
 import com.google.gerrit.pgm.util.BatchProgramModule;
 import com.google.gerrit.pgm.util.SiteProgram;
-import com.google.gerrit.pgm.util.ThreadLimiter;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.index.IndexModule;
@@ -81,10 +79,9 @@
   @Override
   public int run() throws Exception {
     mustHaveValidSite();
-    dbInjector = createDbInjector(MULTI_USER);
+    dbInjector = createDbInjector();
     cfgInjector = dbInjector.createChildInjector();
     globalConfig = dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
-    threads = ThreadLimiter.limitThreads(dbInjector, threads);
     overrideConfig();
     LifecycleManager dbManager = new LifecycleManager();
     dbManager.add(dbInjector);
diff --git a/java/com/google/gerrit/pgm/Rulec.java b/java/com/google/gerrit/pgm/Rulec.java
index add06ef..aa72ae0 100644
--- a/java/com/google/gerrit/pgm/Rulec.java
+++ b/java/com/google/gerrit/pgm/Rulec.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.pgm;
 
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
-
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.pgm.rules.PrologCompiler;
@@ -60,7 +58,7 @@
 
   @Override
   public int run() throws Exception {
-    dbInjector = createDbInjector(SINGLE_USER);
+    dbInjector = createDbInjector();
     manager.add(dbInjector);
     manager.start();
     dbInjector
diff --git a/java/com/google/gerrit/pgm/SwitchSecureStore.java b/java/com/google/gerrit/pgm/SwitchSecureStore.java
index 3f10130..733c9d1 100644
--- a/java/com/google/gerrit/pgm/SwitchSecureStore.java
+++ b/java/com/google/gerrit/pgm/SwitchSecureStore.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.pgm;
 
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
-
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
@@ -86,7 +84,7 @@
     logger.atInfo().log(
         "Current secureStoreClass property (%s) will be replaced with %s",
         currentSecureStoreName, newSecureStore);
-    Injector dbInjector = createDbInjector(SINGLE_USER);
+    Injector dbInjector = createDbInjector();
     SecureStore currentStore = getSecureStore(currentSecureStoreName, dbInjector);
     SecureStore newStore = getSecureStore(newSecureStore, dbInjector);
 
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index 71957e1..7d9ac3f 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.pgm.init;
 
-import static com.google.gerrit.reviewdb.server.ReviewDbUtil.unwrapDb;
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
 import static com.google.inject.Scopes.SINGLETON;
 import static com.google.inject.Stage.PRODUCTION;
 
@@ -42,14 +40,12 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.plugins.JarScanner;
+import com.google.gerrit.server.schema.NoteDbSchemaUpdater;
 import com.google.gerrit.server.schema.ReviewDbFactory;
-import com.google.gerrit.server.schema.ReviewDbSchemaUpdater;
 import com.google.gerrit.server.schema.UpdateUI;
 import com.google.gerrit.server.securestore.SecureStore;
 import com.google.gerrit.server.securestore.SecureStoreClassName;
 import com.google.gerrit.server.securestore.SecureStoreProvider;
-import com.google.gwtorm.jdbc.JdbcExecutor;
-import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.gwtorm.server.StatementExecutor;
@@ -59,7 +55,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Module;
-import com.google.inject.Provider;
 import com.google.inject.TypeLiteral;
 import com.google.inject.spi.Message;
 import com.google.inject.util.Providers;
@@ -75,14 +70,12 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import javax.sql.DataSource;
 
 /** Initialize a new Gerrit installation. */
 public class BaseInit extends SiteProgram {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final boolean standalone;
-  private final boolean initDb;
   protected final PluginsDistribution pluginsDistribution;
   private final List<String> pluginsToInstall;
 
@@ -90,7 +83,6 @@
 
   protected BaseInit(PluginsDistribution pluginsDistribution, List<String> pluginsToInstall) {
     this.standalone = true;
-    this.initDb = true;
     this.pluginsDistribution = pluginsDistribution;
     this.pluginsToInstall = pluginsToInstall;
   }
@@ -98,22 +90,10 @@
   public BaseInit(
       Path sitePath,
       boolean standalone,
-      boolean initDb,
       PluginsDistribution pluginsDistribution,
       List<String> pluginsToInstall) {
-    this(sitePath, null, standalone, initDb, pluginsDistribution, pluginsToInstall);
-  }
-
-  public BaseInit(
-      Path sitePath,
-      final Provider<DataSource> dsProvider,
-      boolean standalone,
-      boolean initDb,
-      PluginsDistribution pluginsDistribution,
-      List<String> pluginsToInstall) {
-    super(sitePath, dsProvider);
+    super(sitePath);
     this.standalone = standalone;
-    this.initDb = initDb;
     this.pluginsDistribution = pluginsDistribution;
     this.pluginsToInstall = pluginsToInstall;
   }
@@ -256,7 +236,7 @@
     }
 
     m.add(new GerritServerConfigModule());
-    m.add(new InitModule(standalone, initDb));
+    m.add(new InitModule(standalone));
     m.add(
         new AbstractModule() {
           @Override
@@ -358,7 +338,7 @@
     public final ConsoleUI ui;
     public final SitePaths site;
     public final InitFlags flags;
-    final ReviewDbSchemaUpdater schemaUpdater;
+    final NoteDbSchemaUpdater noteDbSchemaUpdater;
     final SchemaFactory<ReviewDb> schema;
     final GitRepositoryManager repositoryManager;
 
@@ -367,80 +347,58 @@
         ConsoleUI ui,
         SitePaths site,
         InitFlags flags,
-        ReviewDbSchemaUpdater schemaUpdater,
+        NoteDbSchemaUpdater noteDbSchemaUpdater,
         @ReviewDbFactory SchemaFactory<ReviewDb> schema,
         GitRepositoryManager repositoryManager) {
       this.ui = ui;
       this.site = site;
       this.flags = flags;
-      this.schemaUpdater = schemaUpdater;
+      this.noteDbSchemaUpdater = noteDbSchemaUpdater;
       this.schema = schema;
       this.repositoryManager = repositoryManager;
     }
 
     void upgradeSchema() throws OrmException {
-      final List<String> pruneList = new ArrayList<>();
-      schemaUpdater.update(
-          new UpdateUI() {
-            @Override
-            public void message(String message) {
-              System.err.println(message);
-              System.err.flush();
-            }
+      noteDbSchemaUpdater.update(new UpdateUIImpl(ui));
+    }
 
-            @Override
-            public boolean yesno(boolean defaultValue, String message) {
-              return ui.yesno(defaultValue, message);
-            }
+    private static class UpdateUIImpl implements UpdateUI {
+      private final ConsoleUI consoleUi;
 
-            @Override
-            public void waitForUser() {
-              ui.waitForUser();
-            }
+      UpdateUIImpl(ConsoleUI consoleUi) {
+        this.consoleUi = consoleUi;
+      }
 
-            @Override
-            public String readString(
-                String defaultValue, Set<String> allowedValues, String message) {
-              return ui.readString(defaultValue, allowedValues, message);
-            }
+      @Override
+      public void message(String message) {
+        System.err.println(message);
+        System.err.flush();
+      }
 
-            @Override
-            public boolean isBatch() {
-              return ui.isBatch();
-            }
+      @Override
+      public boolean yesno(boolean defaultValue, String message) {
+        return consoleUi.yesno(defaultValue, message);
+      }
 
-            @Override
-            public void pruneSchema(StatementExecutor e, List<String> prune) {
-              for (String p : prune) {
-                if (!pruneList.contains(p)) {
-                  pruneList.add(p);
-                }
-              }
-            }
-          });
+      @Override
+      public void waitForUser() {
+        consoleUi.waitForUser();
+      }
 
-      if (!pruneList.isEmpty()) {
-        StringBuilder msg = new StringBuilder();
-        msg.append("Execute the following SQL to drop unused objects:\n");
-        msg.append("\n");
-        for (String sql : pruneList) {
-          msg.append("  ");
-          msg.append(sql);
-          msg.append(";\n");
-        }
+      @Override
+      public String readString(String defaultValue, Set<String> allowedValues, String message) {
+        return consoleUi.readString(defaultValue, allowedValues, message);
+      }
 
-        if (ui.isBatch()) {
-          System.err.print(msg);
-          System.err.flush();
+      @Override
+      public boolean isBatch() {
+        return consoleUi.isBatch();
+      }
 
-        } else if (ui.yesno(true, "%s\nExecute now", msg)) {
-          try (JdbcSchema db = (JdbcSchema) unwrapDb(schema.open());
-              JdbcExecutor e = new JdbcExecutor(db)) {
-            for (String sql : pruneList) {
-              e.execute(sql);
-            }
-          }
-        }
+      @Override
+      public void pruneSchema(StatementExecutor e, List<String> prune) {
+        // Do nothing in NoteDb.
+        // TODO(dborowitz): Remove this method in the base class.
       }
     }
   }
@@ -460,7 +418,7 @@
               bind(InitFlags.class).toInstance(init.flags);
             }
           });
-      Injector dbInjector = createDbInjector(SINGLE_USER);
+      Injector dbInjector = createDbInjector();
       switch (IndexModule.getIndexType(dbInjector)) {
         case LUCENE:
           modules.add(new LuceneIndexModuleOnInit());
diff --git a/java/com/google/gerrit/pgm/init/DB2Initializer.java b/java/com/google/gerrit/pgm/init/DB2Initializer.java
deleted file mode 100644
index 9dc1088..0000000
--- a/java/com/google/gerrit/pgm/init/DB2Initializer.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2015 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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.username;
-
-import com.google.gerrit.pgm.init.api.Section;
-
-public class DB2Initializer implements DatabaseConfigInitializer {
-
-  @Override
-  public void initConfig(Section databaseSection) {
-    final String defPort = "50001";
-    databaseSection.string("Server hostname", "hostname", "localhost");
-    databaseSection.string("Server port", "port", defPort, false);
-    databaseSection.string("Database name", "database", "gerrit");
-    databaseSection.string("Database username", "username", username());
-    databaseSection.password("username", "password");
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java b/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
deleted file mode 100644
index 2701957..0000000
--- a/java/com/google/gerrit/pgm/init/DatabaseConfigInitializer.java
+++ /dev/null
@@ -1,27 +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.pgm.init;
-
-import com.google.gerrit.pgm.init.api.Section;
-
-/** Abstraction of initializer for the database section */
-interface DatabaseConfigInitializer {
-
-  /**
-   * Performs database platform specific configuration steps and writes configuration parameters
-   * into the given database section
-   */
-  void initConfig(Section databaseSection);
-}
diff --git a/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java b/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
deleted file mode 100644
index 44f883a..0000000
--- a/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
+++ /dev/null
@@ -1,61 +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.pgm.init;
-
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.AbstractModule;
-import com.google.inject.name.Names;
-
-public class DatabaseConfigModule extends AbstractModule {
-
-  private final SitePaths site;
-
-  public DatabaseConfigModule(SitePaths site) {
-    this.site = site;
-  }
-
-  @Override
-  protected void configure() {
-    bind(SitePaths.class).toInstance(site);
-    bind(DatabaseConfigInitializer.class)
-        .annotatedWith(Names.named("db2"))
-        .to(DB2Initializer.class);
-    bind(DatabaseConfigInitializer.class)
-        .annotatedWith(Names.named("derby"))
-        .to(DerbyInitializer.class);
-    bind(DatabaseConfigInitializer.class).annotatedWith(Names.named("h2")).to(H2Initializer.class);
-    bind(DatabaseConfigInitializer.class)
-        .annotatedWith(Names.named("jdbc"))
-        .to(JDBCInitializer.class);
-    bind(DatabaseConfigInitializer.class)
-        .annotatedWith(Names.named("mariadb"))
-        .to(MariaDbInitializer.class);
-    bind(DatabaseConfigInitializer.class)
-        .annotatedWith(Names.named("mysql"))
-        .to(MySqlInitializer.class);
-    bind(DatabaseConfigInitializer.class)
-        .annotatedWith(Names.named("oracle"))
-        .to(OracleInitializer.class);
-    bind(DatabaseConfigInitializer.class)
-        .annotatedWith(Names.named("postgresql"))
-        .to(PostgreSQLInitializer.class);
-    bind(DatabaseConfigInitializer.class)
-        .annotatedWith(Names.named("maxdb"))
-        .to(MaxDbInitializer.class);
-    bind(DatabaseConfigInitializer.class)
-        .annotatedWith(Names.named("hana"))
-        .to(HANAInitializer.class);
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/DerbyInitializer.java b/java/com/google/gerrit/pgm/init/DerbyInitializer.java
deleted file mode 100644
index 3aad0f4..0000000
--- a/java/com/google/gerrit/pgm/init/DerbyInitializer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2015 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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.die;
-
-import com.google.gerrit.common.FileUtil;
-import com.google.gerrit.pgm.init.api.Section;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import java.nio.file.Path;
-
-class DerbyInitializer implements DatabaseConfigInitializer {
-
-  private final SitePaths site;
-
-  @Inject
-  DerbyInitializer(SitePaths site) {
-    this.site = site;
-  }
-
-  @Override
-  public void initConfig(Section databaseSection) {
-    String path = databaseSection.get("database");
-    Path db;
-    if (path == null) {
-      db = site.resolve("db").resolve("ReviewDB");
-      databaseSection.set("database", db.toString());
-    } else {
-      db = site.resolve(path);
-    }
-    if (db == null) {
-      throw die("database.database must be supplied for Derby");
-    }
-    db = db.getParent();
-    FileUtil.mkdirsOrDie(db, "cannot create database.database");
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/GroupsOnInit.java b/java/com/google/gerrit/pgm/init/GroupsOnInit.java
index 8e06aa1..584d8af 100644
--- a/java/com/google/gerrit/pgm/init/GroupsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/GroupsOnInit.java
@@ -25,7 +25,6 @@
 import com.google.gerrit.pgm.init.api.InitFlags;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.GerritPersonIdentProvider;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.GerritServerIdProvider;
@@ -37,7 +36,6 @@
 import com.google.gerrit.server.group.db.GroupConfig;
 import com.google.gerrit.server.group.db.GroupNameNotes;
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.File;
 import java.io.IOException;
@@ -54,9 +52,8 @@
 /**
  * A database accessor for calls related to groups.
  *
- * <p>All calls which read or write group related details to the database <strong>during
- * init</strong> (either ReviewDb or NoteDb) are gathered here. For non-init cases, use {@code
- * Groups} or {@code GroupsUpdate} instead.
+ * <p>All calls which read or write group related details to the NoteDb <strong>during init</strong>
+ * are gathered here. For non-init cases, use {@code Groups} or {@code GroupsUpdate} instead.
  *
  * <p>All methods of this class refer to <em>internal</em> groups.
  */
@@ -76,16 +73,14 @@
   /**
    * Returns the {@code AccountGroup} for the specified {@code GroupReference}.
    *
-   * @param db the {@code ReviewDb} instance to use for lookups
    * @param groupReference the {@code GroupReference} of the group
    * @return the {@code InternalGroup} represented by the {@code GroupReference}
-   * @throws OrmException if the group couldn't be retrieved from ReviewDb
    * @throws IOException if an error occurs while reading from NoteDb
    * @throws ConfigInvalidException if the data in NoteDb is in an incorrect format
    * @throws NoSuchGroupException if a group with such a name doesn't exist
    */
-  public InternalGroup getExistingGroup(ReviewDb db, GroupReference groupReference)
-      throws OrmException, NoSuchGroupException, IOException, ConfigInvalidException {
+  public InternalGroup getExistingGroup(GroupReference groupReference)
+      throws NoSuchGroupException, IOException, ConfigInvalidException {
     File allUsersRepoPath = getPathToAllUsersRepository();
     if (allUsersRepoPath != null) {
       try (Repository allUsersRepo = new FileRepository(allUsersRepoPath)) {
@@ -102,14 +97,11 @@
   /**
    * Returns {@code GroupReference}s for all internal groups.
    *
-   * @param db the {@code ReviewDb} instance to use for lookups
    * @return a stream of the {@code GroupReference}s of all internal groups
-   * @throws OrmException if an error occurs while reading from ReviewDb
    * @throws IOException if an error occurs while reading from NoteDb
    * @throws ConfigInvalidException if the data in NoteDb is in an incorrect format
    */
-  public Stream<GroupReference> getAllGroupReferences(ReviewDb db)
-      throws OrmException, IOException, ConfigInvalidException {
+  public Stream<GroupReference> getAllGroupReferences() throws IOException, ConfigInvalidException {
     File allUsersRepoPath = getPathToAllUsersRepository();
     if (allUsersRepoPath != null) {
       try (Repository allUsersRepo = new FileRepository(allUsersRepoPath)) {
@@ -126,14 +118,12 @@
    * <p><strong>Note</strong>: This method doesn't check whether the account exists! It also doesn't
    * update the account index!
    *
-   * @param db the {@code ReviewDb} instance to update
    * @param groupUuid the UUID of the group
    * @param account the account to add
-   * @throws OrmException if an error occurs while reading/writing from/to ReviewDb
    * @throws NoSuchGroupException if the specified group doesn't exist
    */
-  public void addGroupMember(ReviewDb db, AccountGroup.UUID groupUuid, Account account)
-      throws OrmException, NoSuchGroupException, IOException, ConfigInvalidException {
+  public void addGroupMember(AccountGroup.UUID groupUuid, Account account)
+      throws NoSuchGroupException, IOException, ConfigInvalidException {
     File allUsersRepoPath = getPathToAllUsersRepository();
     if (allUsersRepoPath != null) {
       try (Repository repository = new FileRepository(allUsersRepoPath)) {
diff --git a/java/com/google/gerrit/pgm/init/H2Initializer.java b/java/com/google/gerrit/pgm/init/H2Initializer.java
deleted file mode 100644
index 63aa6ec..0000000
--- a/java/com/google/gerrit/pgm/init/H2Initializer.java
+++ /dev/null
@@ -1,50 +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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.die;
-
-import com.google.gerrit.common.FileUtil;
-import com.google.gerrit.pgm.init.api.Section;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import java.nio.file.Path;
-
-class H2Initializer implements DatabaseConfigInitializer {
-
-  private final SitePaths site;
-
-  @Inject
-  H2Initializer(SitePaths site) {
-    this.site = site;
-  }
-
-  @Override
-  public void initConfig(Section databaseSection) {
-    String path = databaseSection.get("database");
-    Path db;
-    if (path == null) {
-      db = site.resolve("db").resolve("ReviewDB");
-      databaseSection.set("database", db.toString());
-    } else {
-      db = site.resolve(path);
-    }
-    if (db == null) {
-      throw die("database.database must be supplied for H2");
-    }
-    db = db.getParent();
-    FileUtil.mkdirsOrDie(db, "cannot create database.database");
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/HANAInitializer.java b/java/com/google/gerrit/pgm/init/HANAInitializer.java
deleted file mode 100644
index 713392d..0000000
--- a/java/com/google/gerrit/pgm/init/HANAInitializer.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2016 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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.username;
-
-import com.google.gerrit.pgm.init.api.Section;
-
-public class HANAInitializer implements DatabaseConfigInitializer {
-
-  @Override
-  public void initConfig(Section databaseSection) {
-    final String defPort = "(hana default)";
-    databaseSection.string("Server hostname", "hostname", "localhost");
-    databaseSection.string("Server port", "port", defPort, true);
-    databaseSection.string("Database name", "database", null);
-    databaseSection.string("Database username", "username", username());
-    databaseSection.password("username", "password");
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/InitAdminUser.java b/java/com/google/gerrit/pgm/init/InitAdminUser.java
index f12fa50..f19cf39 100644
--- a/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -26,7 +26,6 @@
 import com.google.gerrit.pgm.init.api.InitStep;
 import com.google.gerrit.pgm.init.api.SequencesOnInit;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountSshKey;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
@@ -37,7 +36,6 @@
 import com.google.gerrit.server.index.group.GroupIndex;
 import com.google.gerrit.server.index.group.GroupIndexCollection;
 import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -57,7 +55,6 @@
   private final ExternalIdsOnInit externalIds;
   private final SequencesOnInit sequencesOnInit;
   private final GroupsOnInit groupsOnInit;
-  private SchemaFactory<ReviewDb> dbFactory;
   private AccountIndexCollection accountIndexCollection;
   private GroupIndexCollection groupIndexCollection;
 
@@ -84,11 +81,6 @@
   @Override
   public void run() {}
 
-  @Inject(optional = true)
-  void set(SchemaFactory<ReviewDb> dbFactory) {
-    this.dbFactory = dbFactory;
-  }
-
   @Inject
   void set(AccountIndexCollection accountIndexCollection) {
     this.accountIndexCollection = accountIndexCollection;
@@ -106,58 +98,56 @@
       return;
     }
 
-    try (ReviewDb db = dbFactory.open()) {
-      if (!accounts.hasAnyAccount()) {
-        ui.header("Gerrit Administrator");
-        if (ui.yesno(true, "Create administrator user")) {
-          Account.Id id = new Account.Id(sequencesOnInit.nextAccountId(db));
-          String username = ui.readString("admin", "username");
-          String name = ui.readString("Administrator", "name");
-          String httpPassword = ui.readString("secret", "HTTP password");
-          AccountSshKey sshKey = readSshKey(id);
-          String email = readEmail(sshKey);
+    if (!accounts.hasAnyAccount()) {
+      ui.header("Gerrit Administrator");
+      if (ui.yesno(true, "Create administrator user")) {
+        Account.Id id = new Account.Id(sequencesOnInit.nextAccountId());
+        String username = ui.readString("admin", "username");
+        String name = ui.readString("Administrator", "name");
+        String httpPassword = ui.readString("secret", "HTTP password");
+        AccountSshKey sshKey = readSshKey(id);
+        String email = readEmail(sshKey);
 
-          List<ExternalId> extIds = new ArrayList<>(2);
-          extIds.add(ExternalId.createUsername(username, id, httpPassword));
+        List<ExternalId> extIds = new ArrayList<>(2);
+        extIds.add(ExternalId.createUsername(username, id, httpPassword));
 
-          if (email != null) {
-            extIds.add(ExternalId.createEmail(id, email));
-          }
-          externalIds.insert("Add external IDs for initial admin user", extIds);
+        if (email != null) {
+          extIds.add(ExternalId.createEmail(id, email));
+        }
+        externalIds.insert("Add external IDs for initial admin user", extIds);
 
-          Account a = new Account(id, TimeUtil.nowTs());
-          a.setFullName(name);
-          a.setPreferredEmail(email);
-          accounts.insert(a);
+        Account a = new Account(id, TimeUtil.nowTs());
+        a.setFullName(name);
+        a.setPreferredEmail(email);
+        accounts.insert(a);
 
-          // Only two groups should exist at this point in time and hence iterating over all of them
-          // is cheap.
-          Optional<GroupReference> adminGroupReference =
-              groupsOnInit
-                  .getAllGroupReferences(db)
-                  .filter(group -> group.getName().equals("Administrators"))
-                  .findAny();
-          if (!adminGroupReference.isPresent()) {
-            throw new NoSuchGroupException("Administrators");
-          }
-          GroupReference adminGroup = adminGroupReference.get();
-          groupsOnInit.addGroupMember(db, adminGroup.getUUID(), a);
+        // Only two groups should exist at this point in time and hence iterating over all of them
+        // is cheap.
+        Optional<GroupReference> adminGroupReference =
+            groupsOnInit
+                .getAllGroupReferences()
+                .filter(group -> group.getName().equals("Administrators"))
+                .findAny();
+        if (!adminGroupReference.isPresent()) {
+          throw new NoSuchGroupException("Administrators");
+        }
+        GroupReference adminGroup = adminGroupReference.get();
+        groupsOnInit.addGroupMember(adminGroup.getUUID(), a);
 
-          if (sshKey != null) {
-            VersionedAuthorizedKeysOnInit authorizedKeys = authorizedKeysFactory.create(id).load();
-            authorizedKeys.addKey(sshKey.sshPublicKey());
-            authorizedKeys.save("Add SSH key for initial admin user\n");
-          }
+        if (sshKey != null) {
+          VersionedAuthorizedKeysOnInit authorizedKeys = authorizedKeysFactory.create(id).load();
+          authorizedKeys.addKey(sshKey.sshPublicKey());
+          authorizedKeys.save("Add SSH key for initial admin user\n");
+        }
 
-          AccountState as = AccountState.forAccount(new AllUsersName(allUsers.get()), a, extIds);
-          for (AccountIndex accountIndex : accountIndexCollection.getWriteIndexes()) {
-            accountIndex.replace(as);
-          }
+        AccountState as = AccountState.forAccount(new AllUsersName(allUsers.get()), a, extIds);
+        for (AccountIndex accountIndex : accountIndexCollection.getWriteIndexes()) {
+          accountIndex.replace(as);
+        }
 
-          InternalGroup adminInternalGroup = groupsOnInit.getExistingGroup(db, adminGroup);
-          for (GroupIndex groupIndex : groupIndexCollection.getWriteIndexes()) {
-            groupIndex.replace(adminInternalGroup);
-          }
+        InternalGroup adminInternalGroup = groupsOnInit.getExistingGroup(adminGroup);
+        for (GroupIndex groupIndex : groupIndexCollection.getWriteIndexes()) {
+          groupIndex.replace(adminInternalGroup);
         }
       }
     }
diff --git a/java/com/google/gerrit/pgm/init/InitDatabase.java b/java/com/google/gerrit/pgm/init/InitDatabase.java
deleted file mode 100644
index 558716c..0000000
--- a/java/com/google/gerrit/pgm/init/InitDatabase.java
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright (C) 2009 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.pgm.init;
-
-import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
-import static com.google.gerrit.server.notedb.NotesMigration.SECTION_NOTE_DB;
-import static com.google.inject.Stage.PRODUCTION;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.Sets;
-import com.google.gerrit.pgm.init.api.ConsoleUI;
-import com.google.gerrit.pgm.init.api.InitFlags;
-import com.google.gerrit.pgm.init.api.InitStep;
-import com.google.gerrit.pgm.init.api.Section;
-import com.google.gerrit.server.config.GerritServerIdProvider;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.notedb.NotesMigrationState;
-import com.google.inject.Binding;
-import com.google.inject.Guice;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
-import com.google.inject.name.Named;
-import com.google.inject.name.Names;
-import java.lang.annotation.Annotation;
-import java.util.List;
-import java.util.Set;
-import org.eclipse.jgit.lib.Config;
-
-/** Initialize the {@code database} configuration section. */
-@Singleton
-class InitDatabase implements InitStep {
-  private final ConsoleUI ui;
-  private final SitePaths site;
-  private final Libraries libraries;
-  private final InitFlags flags;
-  private final Section database;
-  private final Section idSection;
-  private final Section noteDbChanges;
-
-  @Inject
-  InitDatabase(
-      ConsoleUI ui,
-      SitePaths site,
-      Libraries libraries,
-      InitFlags flags,
-      Section.Factory sections) {
-    this.ui = ui;
-    this.site = site;
-    this.libraries = libraries;
-    this.flags = flags; // Don't grab any flags yet; they aren't initialized until BaseInit#run.
-    this.database = sections.get("database", null);
-    this.idSection = sections.get(GerritServerIdProvider.SECTION, null);
-    this.noteDbChanges = sections.get(SECTION_NOTE_DB, CHANGES.key());
-  }
-
-  @Override
-  public void run() {
-    initSqlDb();
-    if (flags.isNew) {
-      initNoteDb();
-    }
-  }
-
-  private void initSqlDb() {
-    ui.header("SQL Database");
-
-    Set<String> allowedValues = Sets.newTreeSet();
-    Injector i = Guice.createInjector(PRODUCTION, new DatabaseConfigModule(site));
-    List<Binding<DatabaseConfigInitializer>> dbConfigBindings =
-        i.findBindingsByType(new TypeLiteral<DatabaseConfigInitializer>() {});
-    for (Binding<DatabaseConfigInitializer> binding : dbConfigBindings) {
-      Annotation annotation = binding.getKey().getAnnotation();
-      if (annotation instanceof Named) {
-        allowedValues.add(((Named) annotation).value());
-      }
-    }
-
-    if (!Strings.isNullOrEmpty(database.get("url"))
-        && Strings.isNullOrEmpty(database.get("type"))) {
-      database.set("type", "jdbc");
-    }
-
-    String dbType = database.select("Database server type", "type", "h2", allowedValues);
-
-    DatabaseConfigInitializer dci =
-        i.getInstance(Key.get(DatabaseConfigInitializer.class, Names.named(dbType.toLowerCase())));
-
-    if (dci instanceof MySqlInitializer) {
-      libraries.mysqlDriver.downloadRequired();
-    } else if (dci instanceof MariaDbInitializer) {
-      libraries.mariadbDriver.downloadRequired();
-    } else if (dci instanceof OracleInitializer) {
-      libraries.oracleDriver.downloadRequired();
-    } else if (dci instanceof DB2Initializer) {
-      libraries.db2Driver.downloadRequired();
-    } else if (dci instanceof HANAInitializer) {
-      libraries.hanaDriver.downloadRequired();
-    }
-
-    dci.initConfig(database);
-
-    // Initialize UUID for NoteDb on first init.
-    String id = idSection.get(GerritServerIdProvider.KEY);
-    if (Strings.isNullOrEmpty(id)) {
-      idSection.set(GerritServerIdProvider.KEY, GerritServerIdProvider.generate());
-    }
-  }
-
-  private void initNoteDb() {
-    ui.header("NoteDb Database");
-    ui.message(
-        "Use NoteDb for change metadata?\n"
-            + "  See documentation:\n"
-            + "  https://gerrit-review.googlesource.com/Documentation/note-db.html\n");
-    if (!ui.yesno(true, "Enable")) {
-      return;
-    }
-
-    Config defaultConfig = new Config();
-    NotesMigrationState.FINAL.setConfigValues(defaultConfig);
-    for (String name : defaultConfig.getNames(SECTION_NOTE_DB, CHANGES.key())) {
-      noteDbChanges.set(name, defaultConfig.getString(SECTION_NOTE_DB, CHANGES.key(), name));
-    }
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/InitModule.java b/java/com/google/gerrit/pgm/init/InitModule.java
index f677ceb..75145de 100644
--- a/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/java/com/google/gerrit/pgm/init/InitModule.java
@@ -26,11 +26,9 @@
 public class InitModule extends FactoryModule {
 
   private final boolean standalone;
-  private final boolean initDb;
 
-  public InitModule(boolean standalone, boolean initDb) {
+  public InitModule(boolean standalone) {
     this.standalone = standalone;
-    this.initDb = initDb;
   }
 
   @Override
@@ -43,12 +41,8 @@
 
     // Steps are executed in the order listed here.
     //
-    step().to(UpgradeFrom2_0_x.class);
-
     step().to(InitGitManager.class);
-    if (initDb) {
-      step().to(InitDatabase.class);
-    }
+    step().to(InitNoteDb.class);
     step().to(InitLogging.class);
     step().to(InitIndex.class);
     step().to(InitAuth.class);
diff --git a/java/com/google/gerrit/pgm/init/InitNoteDb.java b/java/com/google/gerrit/pgm/init/InitNoteDb.java
new file mode 100644
index 0000000..dc52868
--- /dev/null
+++ b/java/com/google/gerrit/pgm/init/InitNoteDb.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2018 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.pgm.init;
+
+import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
+import static com.google.gerrit.server.notedb.NotesMigration.SECTION_NOTE_DB;
+
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
+import com.google.gerrit.server.notedb.NotesMigrationState;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.Config;
+
+/** Initialize the NoteDb in gerrit site. */
+@Singleton
+class InitNoteDb implements InitStep {
+
+  private final Section noteDbChanges;
+
+  @Inject
+  InitNoteDb(Section.Factory sections) {
+    this.noteDbChanges = sections.get(SECTION_NOTE_DB, CHANGES.key());
+  }
+
+  @Override
+  public void run() {
+    initNoteDb();
+  }
+
+  private void initNoteDb() {
+    Config defaultConfig = new Config();
+    NotesMigrationState.FINAL.setConfigValues(defaultConfig);
+    for (String name : defaultConfig.getNames(SECTION_NOTE_DB, CHANGES.key())) {
+      noteDbChanges.set(name, defaultConfig.getString(SECTION_NOTE_DB, CHANGES.key(), name));
+    }
+  }
+}
diff --git a/java/com/google/gerrit/pgm/init/JDBCInitializer.java b/java/com/google/gerrit/pgm/init/JDBCInitializer.java
deleted file mode 100644
index e3a1d66..0000000
--- a/java/com/google/gerrit/pgm/init/JDBCInitializer.java
+++ /dev/null
@@ -1,49 +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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.username;
-
-import com.google.common.base.Strings;
-import com.google.gerrit.pgm.init.api.Section;
-
-class JDBCInitializer implements DatabaseConfigInitializer {
-  @Override
-  public void initConfig(Section database) {
-    boolean hasUrl = Strings.emptyToNull(database.get("url")) != null;
-    database.string("URL", "url", null);
-    guessDriver(database);
-    database.string("Driver class name", "driver", null);
-    database.string("Database username", "username", hasUrl ? null : username());
-    database.password("username", "password");
-  }
-
-  private void guessDriver(Section database) {
-    String url = Strings.emptyToNull(database.get("url"));
-    if (url != null && Strings.isNullOrEmpty(database.get("driver"))) {
-      if (url.startsWith("jdbc:derby:")) {
-        database.set("driver", "org.apache.derby.jdbc.EmbeddedDriver");
-      } else if (url.startsWith("jdbc:h2:")) {
-        database.set("driver", "org.h2.Driver");
-      } else if (url.startsWith("jdbc:mariadb:")) {
-        database.set("driver", "org.mariadb.jdbc.Driver");
-      } else if (url.startsWith("jdbc:mysql:")) {
-        database.set("driver", "com.mysql.jdbc.Driver");
-      } else if (url.startsWith("jdbc:postgresql:")) {
-        database.set("driver", "org.postgresql.Driver");
-      }
-    }
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/MariaDbInitializer.java b/java/com/google/gerrit/pgm/init/MariaDbInitializer.java
deleted file mode 100644
index db32113..0000000
--- a/java/com/google/gerrit/pgm/init/MariaDbInitializer.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2017 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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.username;
-
-import com.google.gerrit.pgm.init.api.Section;
-
-class MariaDbInitializer implements DatabaseConfigInitializer {
-
-  @Override
-  public void initConfig(Section databaseSection) {
-    final String defPort = "(mariadb default)";
-    databaseSection.string("Server hostname", "hostname", "localhost");
-    databaseSection.string("Server port", "port", defPort, true);
-    databaseSection.string("Database name", "database", "reviewdb");
-    databaseSection.string("Database username", "username", username());
-    databaseSection.password("username", "password");
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/MaxDbInitializer.java b/java/com/google/gerrit/pgm/init/MaxDbInitializer.java
deleted file mode 100644
index 0f696b7..0000000
--- a/java/com/google/gerrit/pgm/init/MaxDbInitializer.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2014 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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.username;
-
-import com.google.gerrit.pgm.init.api.Section;
-
-public class MaxDbInitializer implements DatabaseConfigInitializer {
-
-  @Override
-  public void initConfig(Section databaseSection) {
-    final String defPort = "(maxdb default)";
-    databaseSection.string("Server hostname", "hostname", "localhost");
-    databaseSection.string("Server port", "port", defPort, true);
-    databaseSection.string("Database name", "database", "reviewdb");
-    databaseSection.string("Database username", "username", username());
-    databaseSection.password("username", "password");
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/MySqlInitializer.java b/java/com/google/gerrit/pgm/init/MySqlInitializer.java
deleted file mode 100644
index 037b52b..0000000
--- a/java/com/google/gerrit/pgm/init/MySqlInitializer.java
+++ /dev/null
@@ -1,32 +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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.username;
-
-import com.google.gerrit.pgm.init.api.Section;
-
-class MySqlInitializer implements DatabaseConfigInitializer {
-
-  @Override
-  public void initConfig(Section databaseSection) {
-    final String defPort = "(mysql default)";
-    databaseSection.string("Server hostname", "hostname", "localhost");
-    databaseSection.string("Server port", "port", defPort, true);
-    databaseSection.string("Database name", "database", "reviewdb");
-    databaseSection.string("Database username", "username", username());
-    databaseSection.password("username", "password");
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/OracleInitializer.java b/java/com/google/gerrit/pgm/init/OracleInitializer.java
deleted file mode 100644
index ffbaf34..0000000
--- a/java/com/google/gerrit/pgm/init/OracleInitializer.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2013 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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.username;
-
-import com.google.gerrit.pgm.init.api.Section;
-
-public class OracleInitializer implements DatabaseConfigInitializer {
-
-  @Override
-  public void initConfig(Section databaseSection) {
-    final String defPort = "1521";
-    databaseSection.string("Server hostname", "hostname", "localhost");
-    databaseSection.string("Server port", "port", defPort, false);
-    databaseSection.string("Instance name", "instance", "xe");
-    databaseSection.string("Database username", "username", username());
-    databaseSection.password("username", "password");
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java b/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
deleted file mode 100644
index 65a66de..0000000
--- a/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
+++ /dev/null
@@ -1,32 +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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.username;
-
-import com.google.gerrit.pgm.init.api.Section;
-
-class PostgreSQLInitializer implements DatabaseConfigInitializer {
-
-  @Override
-  public void initConfig(Section databaseSection) {
-    final String defPort = "(postgresql default)";
-    databaseSection.string("Server hostname", "hostname", "localhost");
-    databaseSection.string("Server port", "port", defPort, true);
-    databaseSection.string("Database name", "database", "reviewdb");
-    databaseSection.string("Database username", "username", username());
-    databaseSection.password("username", "password");
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
deleted file mode 100644
index 95ff8d7..0000000
--- a/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
+++ /dev/null
@@ -1,291 +0,0 @@
-// Copyright (C) 2009 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.pgm.init;
-
-import static com.google.gerrit.pgm.init.api.InitUtil.die;
-import static com.google.gerrit.pgm.init.api.InitUtil.savePublic;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.base.Splitter;
-import com.google.gerrit.pgm.init.api.ConsoleUI;
-import com.google.gerrit.pgm.init.api.InitFlags;
-import com.google.gerrit.pgm.init.api.InitStep;
-import com.google.gerrit.pgm.init.api.Section;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.securestore.SecureStore;
-import com.google.gerrit.server.util.SocketUtil;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.InetSocketAddress;
-import java.net.URLDecoder;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Properties;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-
-/** Upgrade from a 2.0.x site to a 2.1 site. */
-@Singleton
-class UpgradeFrom2_0_x implements InitStep {
-  static final String[] etcFiles = {
-    "gerrit.config", //
-    "secure.config", //
-    "replication.config", //
-    "ssh_host_rsa_key", //
-    "ssh_host_rsa_key.pub", //
-    "ssh_host_dsa_key", //
-    "ssh_host_dsa_key.pub", //
-    "ssh_host_key", //
-    "contact_information.pub", //
-    "gitweb_config.perl", //
-    "keystore", //
-    "GerritSite.css", //
-    "GerritSiteFooter.html", //
-    "GerritSiteHeader.html", //
-  };
-
-  private final ConsoleUI ui;
-
-  private final FileBasedConfig cfg;
-  private final SecureStore sec;
-  private final Path site_path;
-  private final Path etc_dir;
-  private final Section.Factory sections;
-
-  @Inject
-  UpgradeFrom2_0_x(
-      final SitePaths site,
-      final InitFlags flags,
-      final ConsoleUI ui,
-      final Section.Factory sections) {
-    this.ui = ui;
-    this.sections = sections;
-
-    this.cfg = flags.cfg;
-    this.sec = flags.sec;
-    this.site_path = site.site_path;
-    this.etc_dir = site.etc_dir;
-  }
-
-  boolean isNeedUpgrade() {
-    for (String name : etcFiles) {
-      if (Files.exists(site_path.resolve(name))) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  @Override
-  public void run() throws IOException, ConfigInvalidException {
-    if (!isNeedUpgrade()) {
-      return;
-    }
-
-    if (!ui.yesno(true, "Upgrade '%s'", site_path.toAbsolutePath())) {
-      throw die("aborted by user");
-    }
-
-    for (String name : etcFiles) {
-      Path src = site_path.resolve(name);
-      Path dst = etc_dir.resolve(name);
-      if (Files.exists(src)) {
-        if (Files.exists(dst)) {
-          throw die("File " + src + " would overwrite " + dst);
-        }
-        try {
-          Files.move(src, dst);
-        } catch (IOException e) {
-          throw die("Cannot rename " + src + " to " + dst, e);
-        }
-      }
-    }
-
-    // We have to reload the configuration after the rename as
-    // the initial load pulled up an non-existent (and thus
-    // believed to be empty) file.
-    //
-    cfg.load();
-
-    final Properties oldprop = readGerritServerProperties();
-    if (oldprop != null) {
-      final Section database = sections.get("database", null);
-
-      String url = oldprop.getProperty("url");
-      if (url != null && !convertUrl(database, url)) {
-        database.set("type", "jdbc");
-        database.set("driver", oldprop.getProperty("driver"));
-        database.set("url", url);
-      }
-
-      String username = oldprop.getProperty("user");
-      if (username == null || username.isEmpty()) {
-        username = oldprop.getProperty("username");
-      }
-      if (username != null && !username.isEmpty()) {
-        cfg.setString("database", null, "username", username);
-      }
-
-      String password = oldprop.getProperty("password");
-      if (password != null && !password.isEmpty()) {
-        sec.set("database", null, "password", password);
-      }
-    }
-
-    String[] values;
-
-    values = cfg.getStringList("ldap", null, "password");
-    cfg.unset("ldap", null, "password");
-    sec.setList("ldap", null, "password", Arrays.asList(values));
-
-    values = cfg.getStringList("sendemail", null, "smtpPass");
-    cfg.unset("sendemail", null, "smtpPass");
-    sec.setList("sendemail", null, "smtpPass", Arrays.asList(values));
-
-    savePublic(cfg);
-  }
-
-  private boolean convertUrl(Section database, String url) throws UnsupportedEncodingException {
-    String username = null;
-    String password = null;
-
-    if (url.contains("?")) {
-      final int q = url.indexOf('?');
-      for (String pair : Splitter.on('&').split(url.substring(q + 1))) {
-        final int eq = pair.indexOf('=');
-        if (0 < eq) {
-          return false;
-        }
-
-        String n = URLDecoder.decode(pair.substring(0, eq), UTF_8.name());
-        String v = URLDecoder.decode(pair.substring(eq + 1), UTF_8.name());
-
-        if ("user".equals(n) || "username".equals(n)) {
-          username = v;
-
-        } else if ("password".equals(n)) {
-          password = v;
-
-        } else {
-          // There is a parameter setting we don't recognize, use the
-          // JDBC URL format instead to preserve the configuration.
-          //
-          return false;
-        }
-      }
-      url = url.substring(0, q);
-    }
-
-    if (url.startsWith("jdbc:h2:file:")) {
-      url = url.substring("jdbc:h2:file:".length());
-      database.set("type", "h2");
-      database.set("database", url);
-      return true;
-    }
-
-    if (url.startsWith("jdbc:postgresql://")) {
-      url = url.substring("jdbc:postgresql://".length());
-      final int sl = url.indexOf('/');
-      if (sl < 0) {
-        return false;
-      }
-
-      final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0);
-      database.set("type", "postgresql");
-      sethost(database, addr);
-      database.set("database", url.substring(sl + 1));
-      setuser(database, username, password);
-      return true;
-    }
-
-    if (url.startsWith("jdbc:postgresql:")) {
-      url = url.substring("jdbc:postgresql:".length());
-      database.set("type", "postgresql");
-      database.set("hostname", "localhost");
-      database.set("database", url);
-      setuser(database, username, password);
-      return true;
-    }
-
-    if (url.startsWith("jdbc:mysql://")) {
-      url = url.substring("jdbc:mysql://".length());
-      final int sl = url.indexOf('/');
-      if (sl < 0) {
-        return false;
-      }
-
-      final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0);
-      database.set("type", "mysql");
-      sethost(database, addr);
-      database.set("database", url.substring(sl + 1));
-      setuser(database, username, password);
-      return true;
-    }
-
-    return false;
-  }
-
-  private void sethost(Section database, InetSocketAddress addr) {
-    database.set("hostname", SocketUtil.hostname(addr));
-    if (0 < addr.getPort()) {
-      database.set("port", String.valueOf(addr.getPort()));
-    }
-  }
-
-  private void setuser(Section database, String username, String password) {
-    if (username != null && !username.isEmpty()) {
-      database.set("username", username);
-    }
-    if (password != null && !password.isEmpty()) {
-      sec.set("database", null, "password", password);
-    }
-  }
-
-  private Properties readGerritServerProperties() throws IOException {
-    final Properties srvprop = new Properties();
-    final String name = System.getProperty("GerritServer");
-    Path path;
-    if (name != null) {
-      path = Paths.get(name);
-    } else {
-      path = site_path.resolve("GerritServer.properties");
-      if (!Files.exists(path)) {
-        path = Paths.get("GerritServer.properties");
-      }
-    }
-    if (Files.exists(path)) {
-      try (InputStream in = Files.newInputStream(path)) {
-        srvprop.load(in);
-      } catch (IOException e) {
-        throw new IOException("Cannot read " + name, e);
-      }
-      final Properties dbprop = new Properties();
-      for (Map.Entry<Object, Object> e : srvprop.entrySet()) {
-        final String key = (String) e.getKey();
-        if (key.startsWith("database.")) {
-          dbprop.put(key.substring("database.".length()), e.getValue());
-        }
-      }
-      return dbprop;
-    }
-    return null;
-  }
-}
diff --git a/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java b/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
index c9c3a64..1716a3c 100644
--- a/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
@@ -35,16 +35,14 @@
     this.allUsersName = allUsersName;
   }
 
-  public int nextAccountId(ReviewDb db) throws OrmException {
-    @SuppressWarnings("deprecation")
-    RepoSequence.Seed accountSeed = db::nextAccountId;
+  public int nextAccountId() throws OrmException {
     RepoSequence accountSeq =
         new RepoSequence(
             repoManager,
             GitReferenceUpdated.DISABLED,
             new Project.NameKey(allUsersName.get()),
             Sequences.NAME_ACCOUNTS,
-            accountSeed,
+            () -> ReviewDb.FIRST_ACCOUNT_ID,
             1);
     return accountSeq.next();
   }
diff --git a/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java b/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
index b417d05..0d73089 100644
--- a/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
+++ b/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
@@ -30,7 +30,6 @@
 import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
 import com.google.gerrit.server.index.account.AllAccountsIndexer;
 import com.google.gerrit.server.index.group.AllGroupsIndexer;
-import com.google.gerrit.server.index.group.GroupIndexCollection;
 import com.google.gerrit.server.index.group.GroupIndexDefinition;
 import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
 import com.google.inject.AbstractModule;
@@ -75,8 +74,6 @@
     // during init, hence we don't need AllGroupsIndexer.
     bind(AllGroupsIndexer.class).toProvider(Providers.of(null));
 
-    bind(GroupIndexCollection.class);
-
     bind(new TypeLiteral<Map<String, Integer>>() {})
         .annotatedWith(Names.named(SingleVersionModule.SINGLE_VERSIONS))
         .toInstance(ImmutableMap.<String, Integer>of());
diff --git a/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index 540c5f3..ca4b6f3 100644
--- a/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -50,7 +50,6 @@
 import com.google.gerrit.server.config.DefaultUrlFormatter;
 import com.google.gerrit.server.config.DisableReverseDnsLookup;
 import com.google.gerrit.server.config.DisableReverseDnsLookupProvider;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.GitReceivePackGroups;
 import com.google.gerrit.server.config.GitUploadPackGroups;
 import com.google.gerrit.server.config.SysExecutorModule;
@@ -87,21 +86,13 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import org.eclipse.jgit.lib.Config;
 
-/**
- * Module for programs that perform batch operations on a site.
- *
- * <p>Any program that requires this module likely also requires using {@link ThreadLimiter} to
- * limit the number of threads accessing the database concurrently.
- */
+/** Module for programs that perform batch operations on a site. */
 public class BatchProgramModule extends FactoryModule {
-  private final Config cfg;
   private final Module reviewDbModule;
 
   @Inject
-  BatchProgramModule(@GerritServerConfig Config cfg, PerThreadReviewDbModule reviewDbModule) {
-    this.cfg = cfg;
+  BatchProgramModule(PerThreadReviewDbModule reviewDbModule) {
     this.reviewDbModule = reviewDbModule;
   }
 
@@ -173,7 +164,7 @@
     install(new H2CacheModule());
     install(new ExternalIdModule());
     install(new GroupModule());
-    install(new NoteDbModule(cfg));
+    install(new NoteDbModule());
     install(AccountCacheImpl.module());
     install(GroupCacheImpl.module());
     install(GroupIncludeCacheImpl.module());
diff --git a/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java b/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
deleted file mode 100644
index 1c7dc52..0000000
--- a/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (C) 2013 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.pgm.util;
-
-import com.google.gerrit.common.SiteLibraryLoaderUtil;
-import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.config.ThreadSettingsConfig;
-import com.google.gerrit.server.schema.DataSourceProvider;
-import com.google.gerrit.server.schema.DataSourceType;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.nio.file.Path;
-import javax.sql.DataSource;
-import org.eclipse.jgit.lib.Config;
-
-/** Loads the site library if not yet loaded. */
-@Singleton
-public class SiteLibraryBasedDataSourceProvider extends DataSourceProvider {
-  private final Path libdir;
-  private boolean init;
-
-  @Inject
-  SiteLibraryBasedDataSourceProvider(
-      SitePaths site,
-      @GerritServerConfig Config cfg,
-      MetricMaker metrics,
-      ThreadSettingsConfig tsc,
-      DataSourceProvider.Context ctx,
-      DataSourceType dst) {
-    super(cfg, metrics, tsc, ctx, dst);
-    libdir = site.lib_dir;
-  }
-
-  @Override
-  public synchronized DataSource get() {
-    if (!init) {
-      SiteLibraryLoaderUtil.loadSiteLib(libdir);
-      init = true;
-    }
-    return super.get();
-  }
-}
diff --git a/java/com/google/gerrit/pgm/util/SiteProgram.java b/java/com/google/gerrit/pgm/util/SiteProgram.java
index 65feeab..e273c02 100644
--- a/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -15,52 +15,34 @@
 package com.google.gerrit.pgm.util;
 
 import static com.google.gerrit.server.config.GerritServerConfigModule.getSecureStoreClassName;
-import static com.google.inject.Scopes.SINGLETON;
 import static com.google.inject.Stage.PRODUCTION;
 
 import com.google.gerrit.common.Die;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.metrics.DisabledMetricMaker;
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
 import com.google.gerrit.server.config.GerritRuntime;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.GerritServerConfigModule;
 import com.google.gerrit.server.config.SitePath;
 import com.google.gerrit.server.git.GitRepositoryManagerModule;
 import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.server.schema.DataSourceModule;
-import com.google.gerrit.server.schema.DataSourceProvider;
-import com.google.gerrit.server.schema.DataSourceType;
 import com.google.gerrit.server.schema.DatabaseModule;
 import com.google.gerrit.server.schema.ReviewDbSchemaModule;
 import com.google.gerrit.server.securestore.SecureStoreClassName;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.AbstractModule;
-import com.google.inject.Binding;
 import com.google.inject.CreationException;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
-import com.google.inject.Key;
 import com.google.inject.Module;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-import com.google.inject.TypeLiteral;
-import com.google.inject.name.Named;
-import com.google.inject.name.Names;
 import com.google.inject.spi.Message;
 import com.google.inject.util.Providers;
-import java.lang.annotation.Annotation;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.List;
-import javax.sql.DataSource;
-import org.eclipse.jgit.lib.Config;
 import org.kohsuke.args4j.Option;
 
 public abstract class SiteProgram extends AbstractProgram {
@@ -74,8 +56,6 @@
     sitePath = Paths.get(path);
   }
 
-  protected Provider<DataSource> dsProvider;
-
   private Path sitePath = Paths.get(".");
 
   protected SiteProgram() {}
@@ -84,11 +64,6 @@
     this.sitePath = sitePath;
   }
 
-  protected SiteProgram(Path sitePath, Provider<DataSource> dsProvider) {
-    this.sitePath = sitePath;
-    this.dsProvider = dsProvider;
-  }
-
   /** @return the site path specified on the command line. */
   protected Path getSitePath() {
     return sitePath;
@@ -102,12 +77,12 @@
   }
 
   /** @return provides database connectivity and site path. */
-  protected Injector createDbInjector(DataSourceProvider.Context context) {
-    return createDbInjector(false, context);
+  protected Injector createDbInjector() {
+    return createDbInjector(false);
   }
 
   /** @return provides database connectivity and site path. */
-  protected Injector createDbInjector(boolean enableMetrics, DataSourceProvider.Context context) {
+  protected Injector createDbInjector(boolean enableMetrics) {
     List<Module> modules = new ArrayList<>();
 
     Module sitePathModule =
@@ -134,26 +109,6 @@
           });
     }
 
-    modules.add(
-        new LifecycleModule() {
-          @Override
-          protected void configure() {
-            bind(DataSourceProvider.Context.class).toInstance(context);
-            if (dsProvider != null) {
-              bind(Key.get(DataSource.class, Names.named("ReviewDb")))
-                  .toProvider(dsProvider)
-                  .in(SINGLETON);
-              if (LifecycleListener.class.isAssignableFrom(dsProvider.getClass())) {
-                listener().toInstance((LifecycleListener) dsProvider);
-              }
-            } else {
-              bind(Key.get(DataSource.class, Names.named("ReviewDb")))
-                  .toProvider(SiteLibraryBasedDataSourceProvider.class)
-                  .in(SINGLETON);
-              listener().to(SiteLibraryBasedDataSourceProvider.class);
-            }
-          }
-        });
     Module configModule = new GerritServerConfigModule();
     modules.add(configModule);
     modules.add(
@@ -164,29 +119,7 @@
           }
         });
     Injector cfgInjector = Guice.createInjector(sitePathModule, configModule);
-    Config cfg = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
-    String dbType;
-    if (dsProvider != null) {
-      dbType = getDbType(dsProvider);
-    } else {
-      dbType = cfg.getString("database", null, "type");
-    }
 
-    if (dbType == null) {
-      throw new ProvisionException("database.type must be defined");
-    }
-
-    DataSourceType dst =
-        Guice.createInjector(new DataSourceModule(), configModule, sitePathModule)
-            .getInstance(Key.get(DataSourceType.class, Names.named(dbType.toLowerCase())));
-
-    modules.add(
-        new AbstractModule() {
-          @Override
-          protected void configure() {
-            bind(DataSourceType.class).toInstance(dst);
-          }
-        });
     modules.add(new DatabaseModule());
     modules.add(new ReviewDbSchemaModule());
     modules.add(cfgInjector.getInstance(GitRepositoryManagerModule.class));
@@ -236,41 +169,6 @@
     return getSecureStoreClassName(sitePath);
   }
 
-  private String getDbType(Provider<DataSource> dsProvider) {
-    String dbProductName;
-    try (Connection conn = dsProvider.get().getConnection()) {
-      dbProductName = conn.getMetaData().getDatabaseProductName().toLowerCase();
-    } catch (SQLException e) {
-      throw new RuntimeException(e);
-    }
-
-    List<Module> modules = new ArrayList<>();
-    modules.add(
-        new AbstractModule() {
-          @Override
-          protected void configure() {
-            bind(Path.class).annotatedWith(SitePath.class).toInstance(getSitePath());
-          }
-        });
-    modules.add(new GerritServerConfigModule());
-    modules.add(new DataSourceModule());
-    Injector i = Guice.createInjector(modules);
-    List<Binding<DataSourceType>> dsTypeBindings =
-        i.findBindingsByType(new TypeLiteral<DataSourceType>() {});
-    for (Binding<DataSourceType> binding : dsTypeBindings) {
-      Annotation annotation = binding.getKey().getAnnotation();
-      if (annotation instanceof Named) {
-        Named named = (Named) annotation;
-        if (named.value().toLowerCase().contains(dbProductName)) {
-          return named.value();
-        }
-      }
-    }
-    throw new IllegalStateException(
-        String.format(
-            "Cannot guess database type from the database product name '%s'", dbProductName));
-  }
-
   @SuppressWarnings("deprecation")
   private static boolean isCannotCreatePoolException(Throwable why) {
     return why instanceof org.apache.commons.dbcp.SQLNestedException
diff --git a/java/com/google/gerrit/pgm/util/ThreadLimiter.java b/java/com/google/gerrit/pgm/util/ThreadLimiter.java
deleted file mode 100644
index 64f703bd..0000000
--- a/java/com/google/gerrit/pgm/util/ThreadLimiter.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2014 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.pgm.util;
-
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.ThreadSettingsConfig;
-import com.google.gerrit.server.schema.DataSourceType;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import org.eclipse.jgit.lib.Config;
-
-// TODO(dborowitz): Not necessary once we switch to NoteDb.
-/** Utility to limit threads used by a batch program. */
-public class ThreadLimiter {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  public static int limitThreads(Injector dbInjector, int threads) {
-    return limitThreads(
-        dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class)),
-        dbInjector.getInstance(DataSourceType.class),
-        dbInjector.getInstance(ThreadSettingsConfig.class),
-        threads);
-  }
-
-  private static int limitThreads(
-      Config cfg, DataSourceType dst, ThreadSettingsConfig threadSettingsConfig, int threads) {
-    boolean usePool = cfg.getBoolean("database", "connectionpool", dst.usePool());
-    int poolLimit = threadSettingsConfig.getDatabasePoolLimit();
-    if (usePool && threads > poolLimit) {
-      logger.atWarning().log("Limiting program to %d threads due to database.poolLimit", poolLimit);
-      return poolLimit;
-    }
-    return threads;
-  }
-
-  private ThreadLimiter() {}
-}
diff --git a/java/com/google/gerrit/reviewdb/BUILD b/java/com/google/gerrit/reviewdb/BUILD
index a4c6601..76e35a1 100644
--- a/java/com/google/gerrit/reviewdb/BUILD
+++ b/java/com/google/gerrit/reviewdb/BUILD
@@ -5,9 +5,8 @@
 java_library(
     name = "server",
     srcs = glob(["**/*.java"]),
-    resource_strip_prefix = "resources",
-    resources = ["//resources/com/google/gerrit/reviewdb"],
     deps = [
+        "//java/com/google/gerrit/common:annotations",
         "//java/com/google/gerrit/extensions:api",
         "//lib:guava",
         "//lib:gwtorm",
diff --git a/java/com/google/gerrit/reviewdb/client/AccountGroup.java b/java/com/google/gerrit/reviewdb/client/AccountGroup.java
index c7dc420..5ec98fc 100644
--- a/java/com/google/gerrit/reviewdb/client/AccountGroup.java
+++ b/java/com/google/gerrit/reviewdb/client/AccountGroup.java
@@ -18,6 +18,7 @@
 import com.google.gwtorm.client.IntKey;
 import com.google.gwtorm.client.StringKey;
 import java.sql.Timestamp;
+import java.time.Instant;
 import java.util.Objects;
 
 /** Named group of one or more accounts, typically used for access controls. */
@@ -26,12 +27,10 @@
    * Time when the audit subsystem was implemented, used as the default value for {@link #createdOn}
    * when one couldn't be determined from the audit log.
    */
-  // Can't use Instant here because GWT. This is verified against a readable time in the tests,
-  // which don't need to compile under GWT.
-  private static final long AUDIT_CREATION_INSTANT_MS = 1244489460000L;
+  private static final Instant AUDIT_CREATION_INSTANT_MS = Instant.ofEpochMilli(1244489460000L);
 
   public static Timestamp auditCreationInstantTs() {
-    return new Timestamp(AUDIT_CREATION_INSTANT_MS);
+    return Timestamp.from(AUDIT_CREATION_INSTANT_MS);
   }
 
   /** Group name key */
diff --git a/java/com/google/gerrit/reviewdb/client/RefNames.java b/java/com/google/gerrit/reviewdb/client/RefNames.java
index fd2fb56..5e45088 100644
--- a/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -49,6 +49,9 @@
   /** Sequence counters in NoteDb. */
   public static final String REFS_SEQUENCES = "refs/sequences/";
 
+  /** NoteDb schema version number. */
+  public static final String REFS_VERSION = "refs/meta/version";
+
   /**
    * Prefix applied to merge commit base nodes.
    *
diff --git a/java/com/google/gerrit/reviewdb/server/DisallowReadFromChangesReviewDbWrapper.java b/java/com/google/gerrit/reviewdb/server/DisallowedReviewDb.java
similarity index 97%
rename from java/com/google/gerrit/reviewdb/server/DisallowReadFromChangesReviewDbWrapper.java
rename to java/com/google/gerrit/reviewdb/server/DisallowedReviewDb.java
index fdf3d6c..60c3c95 100644
--- a/java/com/google/gerrit/reviewdb/server/DisallowReadFromChangesReviewDbWrapper.java
+++ b/java/com/google/gerrit/reviewdb/server/DisallowedReviewDb.java
@@ -23,7 +23,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 
-public class DisallowReadFromChangesReviewDbWrapper extends ReviewDbWrapper {
+public class DisallowedReviewDb extends ReviewDbWrapper {
   private static final String MSG = "This table has been migrated to NoteDb";
 
   private final Changes changes;
@@ -32,7 +32,7 @@
   private final PatchSets patchSets;
   private final PatchLineComments patchComments;
 
-  public DisallowReadFromChangesReviewDbWrapper(ReviewDb db) {
+  public DisallowedReviewDb(ReviewDb db) {
     super(db);
     changes = new Changes(delegate.changes());
     patchSetApprovals = new PatchSetApprovals(delegate.patchSetApprovals());
diff --git a/java/com/google/gerrit/reviewdb/server/ReviewDb.java b/java/com/google/gerrit/reviewdb/server/ReviewDb.java
index 8e4292c..5ff4afa 100644
--- a/java/com/google/gerrit/reviewdb/server/ReviewDb.java
+++ b/java/com/google/gerrit/reviewdb/server/ReviewDb.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.reviewdb.server;
 
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.Relation;
@@ -28,15 +26,11 @@
  * <p>Root entities that are at the top level of some important data graph:
  *
  * <ul>
- *   <li>{@link Account}: Per-user account registration, preferences, identity.
  *   <li>{@link Change}: All review information about a single proposed change.
  * </ul>
  */
 public interface ReviewDb extends Schema {
-  /* If you change anything, update ReviewDbSchemaVersion.C to use a new version. */
-
-  @Relation(id = 1)
-  SchemaVersionAccess schemaVersion();
+  // Deleted @Relation(id = 1)
 
   // Deleted @Relation(id = 2)
 
@@ -91,22 +85,8 @@
 
   int FIRST_ACCOUNT_ID = 1000000;
 
-  /**
-   * Next unique id for a {@link Account}.
-   *
-   * @deprecated use {@link com.google.gerrit.server.Sequences#nextAccountId()}.
-   */
-  @Sequence(startWith = FIRST_ACCOUNT_ID)
-  @Deprecated
-  int nextAccountId() throws OrmException;
-
   int FIRST_GROUP_ID = 1;
 
-  /** Next unique id for a {@link AccountGroup}. */
-  @Sequence(startWith = FIRST_GROUP_ID)
-  @Deprecated
-  int nextAccountGroupId() throws OrmException;
-
   int FIRST_CHANGE_ID = 1;
 
   /**
diff --git a/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java b/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
index aed9778..1f0f12f 100644
--- a/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
+++ b/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
@@ -50,8 +50,8 @@
   }
 
   public static ReviewDb unwrapDb(ReviewDb db) {
-    if (db instanceof DisallowReadFromChangesReviewDbWrapper) {
-      return unwrapDb(((DisallowReadFromChangesReviewDbWrapper) db).unsafeGetDelegate());
+    if (db instanceof DisallowedReviewDb) {
+      return unwrapDb(((DisallowedReviewDb) db).unsafeGetDelegate());
     }
     return db;
   }
diff --git a/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java b/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java
index 202729e..5a77e01 100644
--- a/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java
+++ b/java/com/google/gerrit/reviewdb/server/ReviewDbWrapper.java
@@ -105,11 +105,6 @@
   }
 
   @Override
-  public SchemaVersionAccess schemaVersion() {
-    return delegate.schemaVersion();
-  }
-
-  @Override
   public ChangeAccess changes() {
     return delegate.changes();
   }
@@ -136,18 +131,6 @@
 
   @Override
   @SuppressWarnings("deprecation")
-  public int nextAccountId() throws OrmException {
-    return delegate.nextAccountId();
-  }
-
-  @Override
-  @SuppressWarnings("deprecation")
-  public int nextAccountGroupId() throws OrmException {
-    return delegate.nextAccountGroupId();
-  }
-
-  @Override
-  @SuppressWarnings("deprecation")
   public int nextChangeId() throws OrmException {
     return delegate.nextChangeId();
   }
diff --git a/java/com/google/gerrit/server/ApprovalCopier.java b/java/com/google/gerrit/server/ApprovalCopier.java
index c3d3b60..7083804 100644
--- a/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/java/com/google/gerrit/server/ApprovalCopier.java
@@ -137,7 +137,7 @@
       @Nullable Config repoConfig,
       Iterable<PatchSetApproval> dontCopy)
       throws OrmException {
-    PatchSet ps = psUtil.get(db, notes, psId);
+    PatchSet ps = psUtil.get(notes, psId);
     if (ps == null) {
       return Collections.emptyList();
     }
diff --git a/java/com/google/gerrit/server/ApprovalsUtil.java b/java/com/google/gerrit/server/ApprovalsUtil.java
index 3625de6..95b92af 100644
--- a/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -21,7 +21,6 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
@@ -44,7 +43,6 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.LabelPermission;
@@ -106,7 +104,6 @@
     return Iterables.filter(psas, a -> Objects.equals(a.getAccountId(), accountId));
   }
 
-  private final NotesMigration migration;
   private final ApprovalCopier copier;
   private final PermissionBackend permissionBackend;
   private final ProjectCache projectCache;
@@ -114,11 +111,7 @@
   @VisibleForTesting
   @Inject
   public ApprovalsUtil(
-      NotesMigration migration,
-      ApprovalCopier copier,
-      PermissionBackend permissionBackend,
-      ProjectCache projectCache) {
-    this.migration = migration;
+      ApprovalCopier copier, PermissionBackend permissionBackend, ProjectCache projectCache) {
     this.copier = copier;
     this.permissionBackend = permissionBackend;
     this.projectCache = projectCache;
@@ -127,15 +120,11 @@
   /**
    * Get all reviewers for a change.
    *
-   * @param db review database.
    * @param notes change notes.
    * @return reviewers for the change.
    * @throws OrmException if reviewers for the change could not be read.
    */
-  public ReviewerSet getReviewers(ReviewDb db, ChangeNotes notes) throws OrmException {
-    if (!migration.readChanges()) {
-      return ReviewerSet.fromApprovals(db.patchSetApprovals().byChange(notes.getChangeId()));
-    }
+  public ReviewerSet getReviewers(ChangeNotes notes) throws OrmException {
     return notes.load().getReviewers();
   }
 
@@ -148,23 +137,17 @@
    */
   public ReviewerSet getReviewers(ChangeNotes notes, Iterable<PatchSetApproval> allApprovals)
       throws OrmException {
-    if (!migration.readChanges()) {
-      return ReviewerSet.fromApprovals(allApprovals);
-    }
     return notes.load().getReviewers();
   }
 
   /**
-   * Get updates to reviewer set. Always returns empty list for ReviewDb.
+   * Get updates to reviewer set.
    *
    * @param notes change notes.
    * @return reviewer updates for the change.
    * @throws OrmException if reviewer updates for the change could not be read.
    */
   public List<ReviewerStatusUpdate> getReviewerUpdates(ChangeNotes notes) throws OrmException {
-    if (!migration.readChanges()) {
-      return ImmutableList.of();
-    }
     return notes.load().getReviewerUpdates();
   }
 
@@ -200,13 +183,7 @@
       throws OrmException {
     PatchSet.Id psId = change.currentPatchSetId();
     Collection<Account.Id> existingReviewers;
-    if (migration.readChanges()) {
-      // If using NoteDB, we only want reviewers in the REVIEWER state.
-      existingReviewers = notes.load().getReviewers().byState(REVIEWER);
-    } else {
-      // Prior to NoteDB, we gather all reviewers regardless of state.
-      existingReviewers = getReviewers(db, notes).all();
-    }
+    existingReviewers = notes.load().getReviewers().byState(REVIEWER);
     // Existing reviewers should include pending additions in the REVIEWER
     // state, taken from ChangeUpdate.
     existingReviewers = Lists.newArrayList(existingReviewers);
@@ -308,7 +285,7 @@
   }
 
   /**
-   * Adds approvals to ChangeUpdate for a new patch set, and writes to ReviewDb.
+   * Adds approvals to ChangeUpdate for a new patch set, and writes to NoteDb.
    *
    * @param db review database.
    * @param update change update.
@@ -346,7 +323,6 @@
     for (PatchSetApproval psa : cells) {
       update.putApproval(psa.getLabel(), psa.getValue());
     }
-    db.patchSetApprovals().insert(cells);
     return cells;
   }
 
@@ -377,16 +353,8 @@
     }
   }
 
-  public ListMultimap<PatchSet.Id, PatchSetApproval> byChange(ReviewDb db, ChangeNotes notes)
+  public ListMultimap<PatchSet.Id, PatchSetApproval> byChange(ChangeNotes notes)
       throws OrmException {
-    if (!migration.readChanges()) {
-      ImmutableListMultimap.Builder<PatchSet.Id, PatchSetApproval> result =
-          ImmutableListMultimap.builder();
-      for (PatchSetApproval psa : db.patchSetApprovals().byChange(notes.getChangeId())) {
-        result.put(psa.getPatchSetId(), psa);
-      }
-      return result.build();
-    }
     return notes.load().getApprovals();
   }
 
@@ -397,9 +365,6 @@
       @Nullable RevWalk rw,
       @Nullable Config repoConfig)
       throws OrmException {
-    if (!migration.readChanges()) {
-      return sortApprovals(db.patchSetApprovals().byPatchSet(psId));
-    }
     return copier.getForPatchSet(db, notes, psId, rw, repoConfig);
   }
 
@@ -411,19 +376,16 @@
       @Nullable RevWalk rw,
       @Nullable Config repoConfig)
       throws OrmException {
-    if (!migration.readChanges()) {
-      return sortApprovals(db.patchSetApprovals().byPatchSetUser(psId, accountId));
-    }
     return filterApprovals(byPatchSet(db, notes, psId, rw, repoConfig), accountId);
   }
 
-  public PatchSetApproval getSubmitter(ReviewDb db, ChangeNotes notes, PatchSet.Id c) {
+  public PatchSetApproval getSubmitter(ChangeNotes notes, PatchSet.Id c) {
     if (c == null) {
       return null;
     }
     try {
       // Submit approval is never copied, so bypass expensive byPatchSet call.
-      return getSubmitter(c, byChange(db, notes).get(c));
+      return getSubmitter(c, byChange(notes).get(c));
     } catch (OrmException e) {
       return null;
     }
diff --git a/java/com/google/gerrit/server/ChangeMessagesUtil.java b/java/com/google/gerrit/server/ChangeMessagesUtil.java
index 969cf38..c2b6b23 100644
--- a/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -15,37 +15,24 @@
 package com.google.gerrit.server;
 
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.reviewdb.server.ReviewDbUtil.unwrapDb;
 import static java.util.Objects.requireNonNull;
 
-import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.server.update.BatchUpdateReviewDb;
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.sql.Timestamp;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
-/**
- * Utility functions to manipulate ChangeMessages.
- *
- * <p>These methods either query for and update ChangeMessages in the NoteDb or ReviewDb, depending
- * on the state of the NotesMigration.
- */
+/** Utility functions to manipulate ChangeMessages. */
 @Singleton
 public class ChangeMessagesUtil {
   public static final String AUTOGENERATED_TAG_PREFIX = "autogenerated:";
@@ -100,27 +87,11 @@
     return workInProgress ? TAG_UPLOADED_WIP_PATCH_SET : TAG_UPLOADED_PATCH_SET;
   }
 
-  private static List<ChangeMessage> sortChangeMessages(Iterable<ChangeMessage> changeMessage) {
-    return ChangeNotes.MESSAGE_BY_TIME.sortedCopy(changeMessage);
-  }
-
-  private final NotesMigration migration;
-
-  @VisibleForTesting
-  @Inject
-  public ChangeMessagesUtil(NotesMigration migration) {
-    this.migration = migration;
-  }
-
-  public List<ChangeMessage> byChange(ReviewDb db, ChangeNotes notes) throws OrmException {
-    if (!migration.readChanges()) {
-      return sortChangeMessages(db.changeMessages().byChange(notes.getChangeId()));
-    }
+  public List<ChangeMessage> byChange(ChangeNotes notes) throws OrmException {
     return notes.load().getChangeMessages();
   }
 
-  public void addChangeMessage(ReviewDb db, ChangeUpdate update, ChangeMessage changeMessage)
-      throws OrmException {
+  public void addChangeMessage(ChangeUpdate update, ChangeMessage changeMessage) {
     checkState(
         Objects.equals(changeMessage.getAuthor(), update.getNullableAccountId()),
         "cannot store change message by %s in update by %s",
@@ -128,7 +99,6 @@
         update.getNullableAccountId());
     update.setChangeMessage(changeMessage.getMessage());
     update.setTag(changeMessage.getTag());
-    db.changeMessages().insert(Collections.singleton(changeMessage));
   }
 
   /**
@@ -139,63 +109,14 @@
    * deleted from both NoteDb and ReviewDb, the index of the change message must be used rather than
    * its ID.
    *
-   * @param db the {@code ReviewDb} instance to update.
    * @param update change update.
    * @param targetMessageIdx the index of the target change message.
    * @param newMessage the new message which is going to replace the old.
-   * @throws OrmException
    */
-  public void replaceChangeMessage(
-      ReviewDb db, ChangeUpdate update, int targetMessageIdx, String newMessage)
-      throws OrmException {
-    if (PrimaryStorage.of(update.getChange()).equals(PrimaryStorage.REVIEW_DB)) {
-      if (db instanceof BatchUpdateReviewDb) {
-        db = ((BatchUpdateReviewDb) db).unsafeGetDelegate();
-      }
-      db = unwrapDb(db);
-
-      List<ChangeMessage> messagesInReviewDb =
-          sortChangeMessages(db.changeMessages().byChange(update.getId()));
-      if (migration.readChanges()) {
-        sanityCheckForChangeMessages(messagesInReviewDb, update.getNotes().getChangeMessages());
-      }
-      ChangeMessage targetMessage = messagesInReviewDb.get(targetMessageIdx);
-      targetMessage.setMessage(newMessage);
-      db.changeMessages().upsert(Collections.singleton(targetMessage));
-    }
-
+  public void replaceChangeMessage(ChangeUpdate update, int targetMessageIdx, String newMessage) {
     update.deleteChangeMessageByRewritingHistory(targetMessageIdx, newMessage);
   }
 
-  private static void sanityCheckForChangeMessages(
-      List<ChangeMessage> messagesInReviewDb, List<ChangeMessage> messagesInNoteDb) {
-    String message =
-        String.format(
-            "Change messages in ReivewDb and NoteDb don't match: NoteDb %s; ReviewDb %s",
-            messagesInNoteDb, messagesInReviewDb);
-    if (messagesInReviewDb.size() != messagesInNoteDb.size()) {
-      throw new IllegalStateException(message);
-    }
-
-    for (int i = 0; i < messagesInReviewDb.size(); i++) {
-      ChangeMessage messageInReviewDb = messagesInReviewDb.get(i);
-      ChangeMessage messageInNoteDb = messagesInNoteDb.get(i);
-
-      // Don't compare the keys because they are different for the same change message in NoteDb and
-      // ReviewDb.
-      boolean isEqual =
-          Objects.equals(messageInReviewDb.getAuthor(), messageInNoteDb.getAuthor())
-              && Objects.equals(messageInReviewDb.getWrittenOn(), messageInNoteDb.getWrittenOn())
-              && Objects.equals(messageInReviewDb.getMessage(), messageInNoteDb.getMessage())
-              && Objects.equals(messageInReviewDb.getPatchSetId(), messageInNoteDb.getPatchSetId())
-              && Objects.equals(messageInReviewDb.getTag(), messageInNoteDb.getTag())
-              && Objects.equals(messageInReviewDb.getRealAuthor(), messageInNoteDb.getRealAuthor());
-      if (!isEqual) {
-        throw new IllegalStateException(message);
-      }
-    }
-  }
-
   /**
    * @param tag value of a tag, or null.
    * @return whether the tag starts with the autogenerated prefix.
diff --git a/java/com/google/gerrit/server/CommentsUtil.java b/java/com/google/gerrit/server/CommentsUtil.java
index 99dfbbb..b3812a0 100644
--- a/java/com/google/gerrit/server/CommentsUtil.java
+++ b/java/com/google/gerrit/server/CommentsUtil.java
@@ -16,16 +16,12 @@
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.reviewdb.client.PatchLineComment.Status.PUBLISHED;
 import static java.util.stream.Collectors.toList;
 
 import com.google.common.collect.ComparisonChain;
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
-import com.google.common.collect.Streams;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.common.CommentInfo;
@@ -35,31 +31,23 @@
 import com.google.gerrit.reviewdb.client.Comment;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RobotComment;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.GerritServerId;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
-import com.google.gerrit.server.update.BatchUpdateReviewDb;
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import org.eclipse.jgit.lib.BatchRefUpdate;
@@ -70,12 +58,7 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
 
-/**
- * Utility functions to manipulate Comments.
- *
- * <p>These methods either query for and update Comments in the NoteDb or ReviewDb, depending on the
- * state of the NotesMigration.
- */
+/** Utility functions to manipulate Comments. */
 @Singleton
 public class CommentsUtil {
   public static final Ordering<Comment> COMMENT_ORDER =
@@ -127,18 +110,13 @@
 
   private final GitRepositoryManager repoManager;
   private final AllUsersName allUsers;
-  private final NotesMigration migration;
   private final String serverId;
 
   @Inject
   CommentsUtil(
-      GitRepositoryManager repoManager,
-      AllUsersName allUsers,
-      NotesMigration migration,
-      @GerritServerId String serverId) {
+      GitRepositoryManager repoManager, AllUsersName allUsers, @GerritServerId String serverId) {
     this.repoManager = repoManager;
     this.allUsers = allUsers;
-    this.migration = migration;
     this.serverId = serverId;
   }
 
@@ -158,7 +136,7 @@
       } else {
         // Inherit unresolved value from inReplyTo comment if not specified.
         Comment.Key key = new Comment.Key(parentUuid, path, psId.patchSetId);
-        Optional<Comment> parent = getPublished(ctx.getDb(), ctx.getNotes(), key);
+        Optional<Comment> parent = getPublished(ctx.getNotes(), key);
         if (!parent.isPresent()) {
           throw new UnprocessableEntityException("Invalid parentUuid supplied for comment");
         }
@@ -201,119 +179,64 @@
     return c;
   }
 
-  public Optional<Comment> getPublished(ReviewDb db, ChangeNotes notes, Comment.Key key)
-      throws OrmException {
-    if (!migration.readChanges()) {
-      return getReviewDb(db, notes, key);
-    }
-    return publishedByChange(db, notes).stream().filter(c -> key.equals(c.key)).findFirst();
+  public Optional<Comment> getPublished(ChangeNotes notes, Comment.Key key) throws OrmException {
+    return publishedByChange(notes).stream().filter(c -> key.equals(c.key)).findFirst();
   }
 
-  public Optional<Comment> getDraft(
-      ReviewDb db, ChangeNotes notes, IdentifiedUser user, Comment.Key key) throws OrmException {
-    if (!migration.readChanges()) {
-      Optional<Comment> c = getReviewDb(db, notes, key);
-      if (c.isPresent() && !c.get().author.getId().equals(user.getAccountId())) {
-        throw new OrmException(
-            String.format(
-                "Expected draft %s to belong to account %s, but it belongs to %s",
-                key, user.getAccountId(), c.get().author.getId()));
-      }
-      return c;
-    }
-    return draftByChangeAuthor(db, notes, user.getAccountId())
+  public Optional<Comment> getDraft(ChangeNotes notes, IdentifiedUser user, Comment.Key key)
+      throws OrmException {
+    return draftByChangeAuthor(notes, user.getAccountId())
         .stream()
         .filter(c -> key.equals(c.key))
         .findFirst();
   }
 
-  private Optional<Comment> getReviewDb(ReviewDb db, ChangeNotes notes, Comment.Key key)
-      throws OrmException {
-    return Optional.ofNullable(
-            db.patchComments().get(PatchLineComment.Key.from(notes.getChangeId(), key)))
-        .map(plc -> plc.asComment(serverId));
-  }
-
-  public List<Comment> publishedByChange(ReviewDb db, ChangeNotes notes) throws OrmException {
-    if (!migration.readChanges()) {
-      return sort(byCommentStatus(db.patchComments().byChange(notes.getChangeId()), PUBLISHED));
-    }
-
+  public List<Comment> publishedByChange(ChangeNotes notes) throws OrmException {
     notes.load();
     return sort(Lists.newArrayList(notes.getComments().values()));
   }
 
   public List<RobotComment> robotCommentsByChange(ChangeNotes notes) throws OrmException {
-    if (!migration.readChanges()) {
-      return ImmutableList.of();
-    }
-
     notes.load();
     return sort(Lists.newArrayList(notes.getRobotComments().values()));
   }
 
-  public List<Comment> draftByChange(ReviewDb db, ChangeNotes notes) throws OrmException {
-    if (!migration.readChanges()) {
-      return sort(byCommentStatus(db.patchComments().byChange(notes.getChangeId()), Status.DRAFT));
-    }
-
+  public List<Comment> draftByChange(ChangeNotes notes) throws OrmException {
     List<Comment> comments = new ArrayList<>();
     for (Ref ref : getDraftRefs(notes.getChangeId())) {
       Account.Id account = Account.Id.fromRefSuffix(ref.getName());
       if (account != null) {
-        comments.addAll(draftByChangeAuthor(db, notes, account));
+        comments.addAll(draftByChangeAuthor(notes, account));
       }
     }
     return sort(comments);
   }
 
-  private List<Comment> byCommentStatus(
-      ResultSet<PatchLineComment> comments, PatchLineComment.Status status) {
-    return toComments(
-        serverId, Lists.newArrayList(Iterables.filter(comments, c -> c.getStatus() == status)));
-  }
-
-  public List<Comment> byPatchSet(ReviewDb db, ChangeNotes notes, PatchSet.Id psId)
-      throws OrmException {
-    if (!migration.readChanges()) {
-      return sort(toComments(serverId, db.patchComments().byPatchSet(psId).toList()));
-    }
+  public List<Comment> byPatchSet(ChangeNotes notes, PatchSet.Id psId) throws OrmException {
     List<Comment> comments = new ArrayList<>();
-    comments.addAll(publishedByPatchSet(db, notes, psId));
+    comments.addAll(publishedByPatchSet(notes, psId));
 
     for (Ref ref : getDraftRefs(notes.getChangeId())) {
       Account.Id account = Account.Id.fromRefSuffix(ref.getName());
       if (account != null) {
-        comments.addAll(draftByPatchSetAuthor(db, psId, account, notes));
+        comments.addAll(draftByPatchSetAuthor(psId, account, notes));
       }
     }
     return sort(comments);
   }
 
-  public List<Comment> publishedByChangeFile(
-      ReviewDb db, ChangeNotes notes, Change.Id changeId, String file) throws OrmException {
-    if (!migration.readChanges()) {
-      return sort(
-          toComments(serverId, db.patchComments().publishedByChangeFile(changeId, file).toList()));
-    }
+  public List<Comment> publishedByChangeFile(ChangeNotes notes, String file) throws OrmException {
     return commentsOnFile(notes.load().getComments().values(), file);
   }
 
-  public List<Comment> publishedByPatchSet(ReviewDb db, ChangeNotes notes, PatchSet.Id psId)
+  public List<Comment> publishedByPatchSet(ChangeNotes notes, PatchSet.Id psId)
       throws OrmException {
-    if (!migration.readChanges()) {
-      return removeCommentsOnAncestorOfCommitMessage(
-          sort(toComments(serverId, db.patchComments().publishedByPatchSet(psId).toList())));
-    }
     return removeCommentsOnAncestorOfCommitMessage(
         commentsOnPatchSet(notes.load().getComments().values(), psId));
   }
 
   public List<RobotComment> robotCommentsByPatchSet(ChangeNotes notes, PatchSet.Id psId)
       throws OrmException {
-    if (!migration.readChanges()) {
-      return ImmutableList.of();
-    }
     return commentsOnPatchSet(notes.load().getRobotComments().values(), psId);
   }
 
@@ -330,49 +253,28 @@
         .collect(toList());
   }
 
-  public List<Comment> draftByPatchSetAuthor(
-      ReviewDb db, PatchSet.Id psId, Account.Id author, ChangeNotes notes) throws OrmException {
-    if (!migration.readChanges()) {
-      return sort(
-          toComments(serverId, db.patchComments().draftByPatchSetAuthor(psId, author).toList()));
-    }
+  public List<Comment> draftByPatchSetAuthor(PatchSet.Id psId, Account.Id author, ChangeNotes notes)
+      throws OrmException {
     return commentsOnPatchSet(notes.load().getDraftComments(author).values(), psId);
   }
 
-  public List<Comment> draftByChangeFileAuthor(
-      ReviewDb db, ChangeNotes notes, String file, Account.Id author) throws OrmException {
-    if (!migration.readChanges()) {
-      return sort(
-          toComments(
-              serverId,
-              db.patchComments()
-                  .draftByChangeFileAuthor(notes.getChangeId(), file, author)
-                  .toList()));
-    }
+  public List<Comment> draftByChangeFileAuthor(ChangeNotes notes, String file, Account.Id author)
+      throws OrmException {
     return commentsOnFile(notes.load().getDraftComments(author).values(), file);
   }
 
-  public List<Comment> draftByChangeAuthor(ReviewDb db, ChangeNotes notes, Account.Id author)
+  public List<Comment> draftByChangeAuthor(ChangeNotes notes, Account.Id author)
       throws OrmException {
-    if (!migration.readChanges()) {
-      return Streams.stream(db.patchComments().draftByAuthor(author))
-          .filter(c -> c.getPatchSetId().getParentKey().equals(notes.getChangeId()))
-          .map(plc -> plc.asComment(serverId))
-          .sorted(COMMENT_ORDER)
-          .collect(toList());
-    }
     List<Comment> comments = new ArrayList<>();
     comments.addAll(notes.getDraftComments(author).values());
     return sort(comments);
   }
 
   public void putComments(
-      ReviewDb db, ChangeUpdate update, PatchLineComment.Status status, Iterable<Comment> comments)
-      throws OrmException {
+      ChangeUpdate update, PatchLineComment.Status status, Iterable<Comment> comments) {
     for (Comment c : comments) {
       update.putComment(status, c);
     }
-    db.patchComments().upsert(toPatchLineComments(update.getId(), status, comments));
   }
 
   public void putRobotComments(ChangeUpdate update, Iterable<RobotComment> comments) {
@@ -381,37 +283,14 @@
     }
   }
 
-  public void deleteComments(ReviewDb db, ChangeUpdate update, Iterable<Comment> comments)
-      throws OrmException {
+  public void deleteComments(ChangeUpdate update, Iterable<Comment> comments) {
     for (Comment c : comments) {
       update.deleteComment(c);
     }
-    db.patchComments()
-        .delete(toPatchLineComments(update.getId(), PatchLineComment.Status.DRAFT, comments));
   }
 
   public void deleteCommentByRewritingHistory(
-      ReviewDb db, ChangeUpdate update, Comment.Key commentKey, PatchSet.Id psId, String newMessage)
-      throws OrmException {
-    if (PrimaryStorage.of(update.getChange()).equals(PrimaryStorage.REVIEW_DB)) {
-      PatchLineComment.Key key =
-          new PatchLineComment.Key(new Patch.Key(psId, commentKey.filename), commentKey.uuid);
-
-      if (db instanceof BatchUpdateReviewDb) {
-        db = ((BatchUpdateReviewDb) db).unsafeGetDelegate();
-      }
-      db = ReviewDbUtil.unwrapDb(db);
-
-      PatchLineComment patchLineComment = db.patchComments().get(key);
-
-      if (!patchLineComment.getStatus().equals(PUBLISHED)) {
-        throw new OrmException(String.format("comment %s is not published", key));
-      }
-
-      patchLineComment.setMessage(newMessage);
-      db.patchComments().upsert(Collections.singleton(patchLineComment));
-    }
-
+      ChangeUpdate update, Comment.Key commentKey, String newMessage) {
     update.deleteCommentByRewritingHistory(commentKey.uuid, newMessage);
   }
 
diff --git a/java/com/google/gerrit/server/PatchSetUtil.java b/java/com/google/gerrit/server/PatchSetUtil.java
index f6c7abc..9405486 100644
--- a/java/com/google/gerrit/server/PatchSetUtil.java
+++ b/java/com/google/gerrit/server/PatchSetUtil.java
@@ -15,17 +15,11 @@
 package com.google.gerrit.server;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.ImmutableMap.toImmutableMap;
-import static com.google.gerrit.server.ChangeUtil.PS_ID_ORDER;
-import static com.google.gerrit.server.notedb.PatchSetState.PUBLISHED;
 import static java.util.Objects.requireNonNull;
-import static java.util.function.Function.identity;
 
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
 import com.google.gerrit.common.data.LabelFunction;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -38,7 +32,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gwtorm.server.OrmException;
@@ -47,7 +40,6 @@
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.sql.Timestamp;
-import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import org.eclipse.jgit.lib.ObjectId;
@@ -58,7 +50,6 @@
 /** Utilities for manipulating patch sets. */
 @Singleton
 public class PatchSetUtil {
-  private final NotesMigration migration;
   private final Provider<ApprovalsUtil> approvalsUtilProvider;
   private final ProjectCache projectCache;
   private final Provider<ReviewDb> dbProvider;
@@ -66,62 +57,38 @@
 
   @Inject
   PatchSetUtil(
-      NotesMigration migration,
       Provider<ApprovalsUtil> approvalsUtilProvider,
       ProjectCache projectCache,
       Provider<ReviewDb> dbProvider,
       GitRepositoryManager repoManager) {
-    this.migration = migration;
     this.approvalsUtilProvider = approvalsUtilProvider;
     this.projectCache = projectCache;
     this.dbProvider = dbProvider;
     this.repoManager = repoManager;
   }
 
-  public PatchSet current(ReviewDb db, ChangeNotes notes) throws OrmException {
-    return get(db, notes, notes.getChange().currentPatchSetId());
+  public PatchSet current(ChangeNotes notes) throws OrmException {
+    return get(notes, notes.getChange().currentPatchSetId());
   }
 
-  public PatchSet get(ReviewDb db, ChangeNotes notes, PatchSet.Id psId) throws OrmException {
-    if (!migration.readChanges()) {
-      return db.patchSets().get(psId);
-    }
+  public PatchSet get(ChangeNotes notes, PatchSet.Id psId) throws OrmException {
     return notes.load().getPatchSets().get(psId);
   }
 
-  public ImmutableCollection<PatchSet> byChange(ReviewDb db, ChangeNotes notes)
-      throws OrmException {
-    if (!migration.readChanges()) {
-      return PS_ID_ORDER.immutableSortedCopy(db.patchSets().byChange(notes.getChangeId()));
-    }
+  public ImmutableCollection<PatchSet> byChange(ChangeNotes notes) throws OrmException {
     return notes.load().getPatchSets().values();
   }
 
-  public ImmutableMap<PatchSet.Id, PatchSet> byChangeAsMap(ReviewDb db, ChangeNotes notes)
-      throws OrmException {
-    if (!migration.readChanges()) {
-      ImmutableMap.Builder<PatchSet.Id, PatchSet> result = ImmutableMap.builder();
-      for (PatchSet ps : PS_ID_ORDER.sortedCopy(db.patchSets().byChange(notes.getChangeId()))) {
-        result.put(ps.getId(), ps);
-      }
-      return result.build();
-    }
+  public ImmutableMap<PatchSet.Id, PatchSet> byChangeAsMap(ChangeNotes notes) throws OrmException {
     return notes.load().getPatchSets();
   }
 
   public ImmutableMap<PatchSet.Id, PatchSet> getAsMap(
-      ReviewDb db, ChangeNotes notes, Set<PatchSet.Id> patchSetIds) throws OrmException {
-    if (!migration.readChanges()) {
-      patchSetIds = Sets.filter(patchSetIds, p -> p.getParentKey().equals(notes.getChangeId()));
-      return Streams.stream(db.patchSets().get(patchSetIds))
-          .sorted(PS_ID_ORDER)
-          .collect(toImmutableMap(PatchSet::getId, identity()));
-    }
+      ChangeNotes notes, Set<PatchSet.Id> patchSetIds) throws OrmException {
     return ImmutableMap.copyOf(Maps.filterKeys(notes.load().getPatchSets(), patchSetIds::contains));
   }
 
   public PatchSet insert(
-      ReviewDb db,
       RevWalk rw,
       ChangeUpdate update,
       PatchSet.Id psId,
@@ -129,10 +96,14 @@
       List<String> groups,
       String pushCertificate,
       String description)
-      throws OrmException, IOException {
+      throws IOException {
     requireNonNull(groups, "groups may not be null");
     ensurePatchSetMatches(psId, update);
 
+    update.setCommit(rw, commit, pushCertificate);
+    update.setPsDescription(description);
+    update.setGroups(groups);
+
     PatchSet ps = new PatchSet(psId);
     ps.setRevision(new RevId(commit.name()));
     ps.setUploader(update.getAccountId());
@@ -140,21 +111,9 @@
     ps.setGroups(groups);
     ps.setPushCertificate(pushCertificate);
     ps.setDescription(description);
-    db.patchSets().insert(Collections.singleton(ps));
-
-    update.setCommit(rw, commit, pushCertificate);
-    update.setPsDescription(description);
-    update.setGroups(groups);
-
     return ps;
   }
 
-  public void publish(ReviewDb db, ChangeUpdate update, PatchSet ps) throws OrmException {
-    ensurePatchSetMatches(ps.getId(), update);
-    update.setPatchSetState(PUBLISHED);
-    db.patchSets().update(Collections.singleton(ps));
-  }
-
   private static void ensurePatchSetMatches(PatchSet.Id psId, ChangeUpdate update) {
     Change.Id changeId = update.getChange().getId();
     checkArgument(
@@ -173,11 +132,9 @@
     }
   }
 
-  public void setGroups(ReviewDb db, ChangeUpdate update, PatchSet ps, List<String> groups)
-      throws OrmException {
+  public void setGroups(ChangeUpdate update, PatchSet ps, List<String> groups) {
     ps.setGroups(groups);
     update.setGroups(groups);
-    db.patchSets().update(Collections.singleton(ps));
   }
 
   /** Check if the current patch set of the change is locked. */
diff --git a/java/com/google/gerrit/server/PublishCommentUtil.java b/java/com/google/gerrit/server/PublishCommentUtil.java
index a90f3e7..25db8d9 100644
--- a/java/com/google/gerrit/server/PublishCommentUtil.java
+++ b/java/com/google/gerrit/server/PublishCommentUtil.java
@@ -56,8 +56,7 @@
     }
 
     Map<Id, PatchSet> patchSets =
-        psUtil.getAsMap(
-            ctx.getDb(), notes, drafts.stream().map(d -> psId(notes, d)).collect(toSet()));
+        psUtil.getAsMap(notes, drafts.stream().map(d -> psId(notes, d)).collect(toSet()));
     for (Comment d : drafts) {
       PatchSet ps = patchSets.get(psId(notes, d));
       if (ps == null) {
@@ -74,7 +73,7 @@
         throw new OrmException(e);
       }
     }
-    commentsUtil.putComments(ctx.getDb(), ctx.getUpdate(psId), PUBLISHED, drafts);
+    commentsUtil.putComments(ctx.getUpdate(psId), PUBLISHED, drafts);
   }
 
   private static PatchSet.Id psId(ChangeNotes notes, Comment c) {
diff --git a/java/com/google/gerrit/server/Sequences.java b/java/com/google/gerrit/server/Sequences.java
index fcf0759..8381b5c 100644
--- a/java/com/google/gerrit/server/Sequences.java
+++ b/java/com/google/gerrit/server/Sequences.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.metrics.Description;
@@ -29,14 +27,10 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.notedb.RepoSequence;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
-import java.util.ArrayList;
-import java.util.List;
 import org.eclipse.jgit.lib.Config;
 
 @Singleton
@@ -55,8 +49,6 @@
     GROUPS;
   }
 
-  private final Provider<ReviewDb> db;
-  private final NotesMigration migration;
   private final RepoSequence accountSeq;
   private final RepoSequence changeSeq;
   private final RepoSequence groupSeq;
@@ -65,15 +57,11 @@
   @Inject
   public Sequences(
       @GerritServerConfig Config cfg,
-      Provider<ReviewDb> db,
-      NotesMigration migration,
       GitRepositoryManager repoManager,
       GitReferenceUpdated gitRefUpdated,
       AllProjectsName allProjects,
       AllUsersName allUsers,
       MetricMaker metrics) {
-    this.db = db;
-    this.migration = migration;
 
     int accountBatchSize = cfg.getInt("noteDb", "accounts", "sequenceBatchSize", 1);
     accountSeq =
@@ -85,19 +73,25 @@
             () -> ReviewDb.FIRST_ACCOUNT_ID,
             accountBatchSize);
 
-    int gap = getChangeSequenceGap(cfg);
-    @SuppressWarnings("deprecation")
-    RepoSequence.Seed changeSeed = () -> db.get().nextChangeId() + gap;
     int changeBatchSize = cfg.getInt("noteDb", "changes", "sequenceBatchSize", 20);
     changeSeq =
         new RepoSequence(
-            repoManager, gitRefUpdated, allProjects, NAME_CHANGES, changeSeed, changeBatchSize);
+            repoManager,
+            gitRefUpdated,
+            allProjects,
+            NAME_CHANGES,
+            () -> ReviewDb.FIRST_CHANGE_ID,
+            changeBatchSize);
 
-    RepoSequence.Seed groupSeed = () -> nextGroupId(db.get());
     int groupBatchSize = 1;
     groupSeq =
         new RepoSequence(
-            repoManager, gitRefUpdated, allUsers, NAME_GROUPS, groupSeed, groupBatchSize);
+            repoManager,
+            gitRefUpdated,
+            allUsers,
+            NAME_GROUPS,
+            () -> ReviewDb.FIRST_GROUP_ID,
+            groupBatchSize);
 
     nextIdLatency =
         metrics.newTimer(
@@ -116,31 +110,15 @@
   }
 
   public int nextChangeId() throws OrmException {
-    if (!migration.readChangeSequence()) {
-      return nextChangeId(db.get());
-    }
     try (Timer2.Context timer = nextIdLatency.start(SequenceType.CHANGES, false)) {
       return changeSeq.next();
     }
   }
 
   public ImmutableList<Integer> nextChangeIds(int count) throws OrmException {
-    if (migration.readChangeSequence()) {
-      try (Timer2.Context timer = nextIdLatency.start(SequenceType.CHANGES, count > 1)) {
-        return changeSeq.next(count);
-      }
+    try (Timer2.Context timer = nextIdLatency.start(SequenceType.CHANGES, count > 1)) {
+      return changeSeq.next(count);
     }
-
-    if (count == 0) {
-      return ImmutableList.of();
-    }
-    checkArgument(count > 0, "count is negative: %s", count);
-    List<Integer> ids = new ArrayList<>(count);
-    ReviewDb db = this.db.get();
-    for (int i = 0; i < count; i++) {
-      ids.add(nextChangeId(db));
-    }
-    return ImmutableList.copyOf(ids);
   }
 
   public int nextGroupId() throws OrmException {
@@ -153,14 +131,4 @@
   public RepoSequence getChangeIdRepoSequence() {
     return changeSeq;
   }
-
-  @SuppressWarnings("deprecation")
-  private static int nextChangeId(ReviewDb db) throws OrmException {
-    return db.nextChangeId();
-  }
-
-  @SuppressWarnings("deprecation")
-  static int nextGroupId(ReviewDb db) throws OrmException {
-    return db.nextAccountGroupId();
-  }
 }
diff --git a/java/com/google/gerrit/server/WebLinks.java b/java/com/google/gerrit/server/WebLinks.java
index 39a2328..589344c 100644
--- a/java/com/google/gerrit/server/WebLinks.java
+++ b/java/com/google/gerrit/server/WebLinks.java
@@ -19,7 +19,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.FluentIterable;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.data.WebLinkInfoCommon;
 import com.google.gerrit.extensions.common.DiffWebLinkInfo;
 import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.registration.DynamicSet;
@@ -54,17 +53,6 @@
         return true;
       };
 
-  private static final Predicate<WebLinkInfoCommon> INVALID_WEBLINK_COMMON =
-      link -> {
-        if (link == null) {
-          return false;
-        } else if (Strings.isNullOrEmpty(link.name) || Strings.isNullOrEmpty(link.url)) {
-          logger.atWarning().log("%s is missing name and/or url", link.getClass().getName());
-          return false;
-        }
-        return true;
-      };
-
   private final DynamicSet<PatchSetWebLink> patchSetLinks;
   private final DynamicSet<ParentWebLink> parentLinks;
   private final DynamicSet<FileWebLink> fileLinks;
@@ -130,25 +118,13 @@
    * @param file File name.
    * @return Links for file history
    */
-  public List<WebLinkInfoCommon> getFileHistoryLinks(String project, String revision, String file) {
+  public List<WebLinkInfo> getFileHistoryLinks(String project, String revision, String file) {
     if (Patch.isMagic(file)) {
       return Collections.emptyList();
     }
     return FluentIterable.from(fileHistoryLinks)
-        .transform(
-            webLink -> {
-              WebLinkInfo info = webLink.getFileHistoryWebLink(project, revision, file);
-              if (info == null) {
-                return null;
-              }
-              WebLinkInfoCommon commonInfo = new WebLinkInfoCommon();
-              commonInfo.name = info.name;
-              commonInfo.imageUrl = info.imageUrl;
-              commonInfo.url = info.url;
-              commonInfo.target = info.target;
-              return commonInfo;
-            })
-        .filter(INVALID_WEBLINK_COMMON)
+        .transform(webLink -> webLink.getFileHistoryWebLink(project, revision, file))
+        .filter(INVALID_WEBLINK)
         .toList();
   }
 
diff --git a/java/com/google/gerrit/server/auth/UniversalAuthBackend.java b/java/com/google/gerrit/server/auth/UniversalAuthBackend.java
index 94faeef..4e93ff2 100644
--- a/java/com/google/gerrit/server/auth/UniversalAuthBackend.java
+++ b/java/com/google/gerrit/server/auth/UniversalAuthBackend.java
@@ -16,7 +16,7 @@
 
 import static java.util.Objects.requireNonNull;
 
-import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.util.ArrayList;
@@ -25,10 +25,10 @@
 /** Universal implementation of the AuthBackend that works with the injected set of AuthBackends. */
 @Singleton
 public final class UniversalAuthBackend implements AuthBackend {
-  private final DynamicSet<AuthBackend> authBackends;
+  private final PluginSetContext<AuthBackend> authBackends;
 
   @Inject
-  UniversalAuthBackend(DynamicSet<AuthBackend> authBackends) {
+  UniversalAuthBackend(PluginSetContext<AuthBackend> authBackends) {
     this.authBackends = authBackends;
   }
 
@@ -36,15 +36,16 @@
   public AuthUser authenticate(AuthRequest request) throws AuthException {
     List<AuthUser> authUsers = new ArrayList<>();
     List<AuthException> authExs = new ArrayList<>();
-    for (AuthBackend backend : authBackends) {
-      try {
-        authUsers.add(requireNonNull(backend.authenticate(request)));
-      } catch (MissingCredentialsException ex) {
-        // Not handled by this backend.
-      } catch (AuthException ex) {
-        authExs.add(ex);
-      }
-    }
+    authBackends.runEach(
+        backend -> {
+          try {
+            authUsers.add(requireNonNull(backend.authenticate(request)));
+          } catch (MissingCredentialsException ex) {
+            // Not handled by this backend.
+          } catch (AuthException ex) {
+            authExs.add(ex);
+          }
+        });
 
     // Handle the valid responses
     if (authUsers.size() == 1) {
diff --git a/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java b/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
index a7fdbbd..ee672cd 100644
--- a/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
+++ b/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
@@ -17,8 +17,8 @@
 import com.google.common.base.Strings;
 import com.google.common.cache.RemovalListener;
 import com.google.common.cache.RemovalNotification;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.registration.PluginName;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -35,13 +35,13 @@
     ForwardingRemovalListener create(String cacheName);
   }
 
-  private final DynamicSet<CacheRemovalListener> listeners;
+  private final PluginSetContext<CacheRemovalListener> listeners;
   private final String cacheName;
   private String pluginName = PluginName.GERRIT;
 
   @Inject
   ForwardingRemovalListener(
-      DynamicSet<CacheRemovalListener> listeners, @Assisted String cacheName) {
+      PluginSetContext<CacheRemovalListener> listeners, @Assisted String cacheName) {
     this.listeners = listeners;
     this.cacheName = cacheName;
   }
@@ -56,8 +56,6 @@
   @Override
   @SuppressWarnings("unchecked")
   public void onRemoval(RemovalNotification<K, V> notification) {
-    for (CacheRemovalListener<K, V> l : listeners) {
-      l.onRemoval(pluginName, cacheName, notification);
-    }
+    listeners.runEach(l -> l.onRemoval(pluginName, cacheName, notification));
   }
 }
diff --git a/java/com/google/gerrit/server/change/AbandonOp.java b/java/com/google/gerrit/server/change/AbandonOp.java
index 5affd5c..3999955 100644
--- a/java/com/google/gerrit/server/change/AbandonOp.java
+++ b/java/com/google/gerrit/server/change/AbandonOp.java
@@ -99,13 +99,13 @@
     if (!change.getStatus().isOpen()) {
       throw new ResourceConflictException("change is " + ChangeUtil.status(change));
     }
-    patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+    patchSet = psUtil.get(ctx.getNotes(), psId);
     change.setStatus(Change.Status.ABANDONED);
     change.setLastUpdatedOn(ctx.getWhen());
 
     update.setStatus(change.getStatus());
     message = newMessage(ctx);
-    cmUtil.addChangeMessage(ctx.getDb(), update, message);
+    cmUtil.addChangeMessage(update, message);
     return true;
   }
 
diff --git a/java/com/google/gerrit/server/change/AddReviewersOp.java b/java/com/google/gerrit/server/change/AddReviewersOp.java
index 947dead..cba6954 100644
--- a/java/com/google/gerrit/server/change/AddReviewersOp.java
+++ b/java/com/google/gerrit/server/change/AddReviewersOp.java
@@ -213,7 +213,7 @@
     checkAdded();
 
     if (patchSet == null) {
-      patchSet = requireNonNull(psUtil.current(ctx.getDb(), ctx.getNotes()));
+      patchSet = requireNonNull(psUtil.current(ctx.getNotes()));
     }
     return true;
   }
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index 33c7f73..d967a46 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -412,14 +412,7 @@
     }
     patchSet =
         psUtil.insert(
-            ctx.getDb(),
-            ctx.getRevWalk(),
-            update,
-            psId,
-            commitId,
-            newGroups,
-            pushCert,
-            patchSetDescription);
+            ctx.getRevWalk(), update, psId, commitId, newGroups, pushCert, patchSetDescription);
 
     /* TODO: fixStatus is used here because the tests
      * (byStatusClosed() in AbstractQueryChangesTest)
@@ -459,7 +452,7 @@
               patchSet.getCreatedOn(),
               message,
               ChangeMessagesUtil.uploadedPatchSetTag(workInProgress));
-      cmUtil.addChangeMessage(db, update, changeMessage);
+      cmUtil.addChangeMessage(update, changeMessage);
     }
     return true;
   }
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index b7049a7..889a20c 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -667,7 +667,7 @@
   }
 
   private Collection<ChangeMessageInfo> messages(ChangeData cd) throws OrmException {
-    List<ChangeMessage> messages = cmUtil.byChange(db.get(), cd.notes());
+    List<ChangeMessage> messages = cmUtil.byChange(cd.notes());
     if (messages.isEmpty()) {
       return Collections.emptyList();
     }
diff --git a/java/com/google/gerrit/server/change/ChangeResource.java b/java/com/google/gerrit/server/change/ChangeResource.java
index ef8b2f9..55dbe87 100644
--- a/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/java/com/google/gerrit/server/change/ChangeResource.java
@@ -154,11 +154,7 @@
       accounts.add(getChange().getAssignee());
     }
     try {
-      patchSetUtil
-          .byChange(db.get(), notes)
-          .stream()
-          .map(PatchSet::getUploader)
-          .forEach(accounts::add);
+      patchSetUtil.byChange(notes).stream().map(PatchSet::getUploader).forEach(accounts::add);
 
       // It's intentional to include the states for *all* reviewers into the ETag computation.
       // We need the states of all current reviewers and CCs because they are part of ChangeInfo.
@@ -167,7 +163,7 @@
       // set of accounts that posted a message is too expensive. However everyone who posts a
       // message is automatically added as reviewer. Hence if we include removed reviewers we can
       // be sure that we have all accounts that posted messages on the change.
-      accounts.addAll(approvalUtil.getReviewers(db.get(), notes).all());
+      accounts.addAll(approvalUtil.getReviewers(notes).all());
     } catch (OrmException e) {
       // This ETag will be invalidated if it loads next time.
     }
diff --git a/java/com/google/gerrit/server/change/ConsistencyChecker.java b/java/com/google/gerrit/server/change/ConsistencyChecker.java
index a379f2c..b29f42a 100644
--- a/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -52,7 +52,6 @@
 import com.google.gerrit.server.plugincontext.PluginItemContext;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
-import com.google.gerrit.server.update.BatchUpdateReviewDb;
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gerrit.server.update.RepoContext;
 import com.google.gerrit.server.update.RetryHelper;
@@ -231,7 +230,7 @@
 
   private void checkCurrentPatchSetEntity() {
     try {
-      currPs = psUtil.current(db.get(), notes);
+      currPs = psUtil.current(notes);
       if (currPs == null) {
         problem(
             String.format("Current patch set %d not found", change().currentPatchSetId().get()));
@@ -259,7 +258,7 @@
     List<PatchSet> all;
     try {
       // Iterate in descending order.
-      all = PS_ID_ORDER.sortedCopy(psUtil.byChange(db.get(), notes));
+      all = PS_ID_ORDER.sortedCopy(psUtil.byChange(notes));
     } catch (OrmException e) {
       return error("Failed to look up patch sets", e);
     }
@@ -503,7 +502,7 @@
     List<ProblemInfo> currProblems = new ArrayList<>(3);
     currProblems.add(notFound);
     if (deleteOldPatchSetProblem != null) {
-      currProblems.add(insertPatchSetProblem);
+      currProblems.add(deleteOldPatchSetProblem);
     }
     currProblems.add(insertPatchSetProblem);
 
@@ -667,15 +666,9 @@
     public boolean updateChange(ChangeContext ctx)
         throws OrmException, PatchSetInfoNotAvailableException {
       // Delete dangling key references.
-      ReviewDb db = BatchUpdateReviewDb.unwrap(ctx.getDb());
       accountPatchReviewStore.run(s -> s.clearReviewed(psId), OrmException.class);
-      db.changeMessages().delete(db.changeMessages().byChange(psId.getParentKey()));
-      db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(psId));
-      db.patchComments().delete(db.patchComments().byPatchSet(psId));
-      db.patchSets().deleteKeys(Collections.singleton(psId));
 
-      // NoteDb requires no additional fiddling; setting the state to deleted is
-      // sufficient to filter everything else out.
+      // For NoteDb setting the state to deleted is sufficient to filter everything out.
       ctx.getUpdate(psId).setPatchSetState(PatchSetState.DELETED);
 
       p.status = Status.FIXED;
@@ -712,7 +705,7 @@
       // Doesn't make any assumptions about the order in which deletes happen
       // and whether they are seen by this op; we are already given the full set
       // of patch sets that will eventually be deleted in this update.
-      for (PatchSet ps : psUtil.byChange(ctx.getDb(), ctx.getNotes())) {
+      for (PatchSet ps : psUtil.byChange(ctx.getNotes())) {
         if (!toDelete.contains(ps.getId())) {
           all.add(ps.getId());
         }
@@ -721,8 +714,7 @@
         throw new NoPatchSetsWouldRemainException();
       }
       PatchSet.Id latest = ReviewDbUtil.intKeyOrdering().max(all);
-      ctx.getChange()
-          .setCurrentPatchSet(patchSetInfoFactory.get(ctx.getDb(), ctx.getNotes(), latest));
+      ctx.getChange().setCurrentPatchSet(patchSetInfoFactory.get(ctx.getNotes(), latest));
       return true;
     }
   }
diff --git a/java/com/google/gerrit/server/change/PatchSetInserter.java b/java/com/google/gerrit/server/change/PatchSetInserter.java
index 24c4237..ca2f6a2 100644
--- a/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -233,24 +233,17 @@
 
     List<String> newGroups = groups;
     if (newGroups.isEmpty()) {
-      PatchSet prevPs = psUtil.current(db, ctx.getNotes());
+      PatchSet prevPs = psUtil.current(ctx.getNotes());
       if (prevPs != null) {
         newGroups = prevPs.getGroups();
       }
     }
     patchSet =
         psUtil.insert(
-            db,
-            ctx.getRevWalk(),
-            ctx.getUpdate(psId),
-            psId,
-            commitId,
-            newGroups,
-            null,
-            description);
+            ctx.getRevWalk(), ctx.getUpdate(psId), psId, commitId, newGroups, null, description);
 
     if (notify != NotifyHandling.NONE) {
-      oldReviewers = approvalsUtil.getReviewers(db, ctx.getNotes());
+      oldReviewers = approvalsUtil.getReviewers(ctx.getNotes());
     }
 
     if (message != null) {
@@ -275,7 +268,7 @@
           db, ctx.getNotes(), patchSet, ctx.getRevWalk(), ctx.getRepoView().getConfig());
     }
     if (changeMessage != null) {
-      cmUtil.addChangeMessage(db, update, changeMessage);
+      cmUtil.addChangeMessage(update, changeMessage);
     }
     return true;
   }
diff --git a/java/com/google/gerrit/server/change/PureRevert.java b/java/com/google/gerrit/server/change/PureRevert.java
index ddc9661..14829ea 100644
--- a/java/com/google/gerrit/server/change/PureRevert.java
+++ b/java/com/google/gerrit/server/change/PureRevert.java
@@ -70,7 +70,7 @@
 
   public PureRevertInfo get(ChangeNotes notes, @Nullable String claimedOriginal)
       throws OrmException, IOException, BadRequestException, ResourceConflictException {
-    PatchSet currentPatchSet = psUtil.current(dbProvider.get(), notes);
+    PatchSet currentPatchSet = psUtil.current(notes);
     if (currentPatchSet == null) {
       throw new ResourceConflictException("current revision is missing");
     }
@@ -81,7 +81,6 @@
       }
       PatchSet ps =
           psUtil.current(
-              dbProvider.get(),
               notesFactory.createChecked(
                   dbProvider.get(), notes.getProjectName(), notes.getChange().getRevertOf()));
       claimedOriginal = ps.getRevision().get();
diff --git a/java/com/google/gerrit/server/change/RebaseUtil.java b/java/com/google/gerrit/server/change/RebaseUtil.java
index 22f98b8..8b7c36e 100644
--- a/java/com/google/gerrit/server/change/RebaseUtil.java
+++ b/java/com/google/gerrit/server/change/RebaseUtil.java
@@ -89,8 +89,6 @@
   }
 
   public Base parseBase(RevisionResource rsrc, String base) throws OrmException {
-    ReviewDb db = dbProvider.get();
-
     // Try parsing the base as a ref string.
     PatchSet.Id basePatchSetId = PatchSet.Id.fromRef(base);
     if (basePatchSetId != null) {
@@ -98,8 +96,7 @@
       ChangeNotes baseNotes = notesFor(rsrc, baseChangeId);
       if (baseNotes != null) {
         return Base.create(
-            notesFor(rsrc, basePatchSetId.getParentKey()),
-            psUtil.get(db, baseNotes, basePatchSetId));
+            notesFor(rsrc, basePatchSetId.getParentKey()), psUtil.get(baseNotes, basePatchSetId));
       }
     }
 
@@ -108,7 +105,7 @@
     if (baseChangeId != null) {
       ChangeNotes baseNotes = notesFor(rsrc, new Change.Id(baseChangeId));
       if (baseNotes != null) {
-        return Base.create(baseNotes, psUtil.current(db, baseNotes));
+        return Base.create(baseNotes, psUtil.current(baseNotes));
       }
     }
 
diff --git a/java/com/google/gerrit/server/change/ReviewerAdder.java b/java/com/google/gerrit/server/change/ReviewerAdder.java
index adbfe54..477db12 100644
--- a/java/com/google/gerrit/server/change/ReviewerAdder.java
+++ b/java/com/google/gerrit/server/change/ReviewerAdder.java
@@ -30,6 +30,7 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Streams;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.extensions.api.changes.AddReviewerInput;
@@ -89,6 +90,8 @@
 import org.eclipse.jgit.lib.Config;
 
 public class ReviewerAdder {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
   public static final int DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK = 10;
   public static final int DEFAULT_MAX_REVIEWERS = 20;
 
@@ -357,6 +360,8 @@
     // reviewers
     int maxAllowed = cfg.getInt("addreviewer", "maxAllowed", DEFAULT_MAX_REVIEWERS);
     if (maxAllowed > 0 && members.size() > maxAllowed) {
+      logger.atFine().log(
+          "Adding %d group members is not allowed (maxAllowed = %d)", members.size(), maxAllowed);
       return fail(
           input,
           FailureType.OTHER,
@@ -367,6 +372,9 @@
     int maxWithoutConfirmation =
         cfg.getInt("addreviewer", "maxWithoutConfirmation", DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK);
     if (!confirmed && maxWithoutConfirmation > 0 && members.size() > maxWithoutConfirmation) {
+      logger.atFine().log(
+          "Adding %d group members as reviewer requires confirmation (maxWithoutConfirmation = %d)",
+          members.size(), maxWithoutConfirmation);
       return fail(
           input,
           FailureType.OTHER,
diff --git a/java/com/google/gerrit/server/change/ReviewerJson.java b/java/com/google/gerrit/server/change/ReviewerJson.java
index 6502569..ef2c926 100644
--- a/java/com/google/gerrit/server/change/ReviewerJson.java
+++ b/java/com/google/gerrit/server/change/ReviewerJson.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.extensions.api.changes.ReviewerInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
@@ -77,12 +78,15 @@
       if (cd == null || !cd.getId().equals(rsrc.getChangeId())) {
         cd = changeDataFactory.create(db.get(), rsrc.getChangeResource().getNotes());
       }
-      ReviewerInfo info =
-          format(
-              new ReviewerInfo(rsrc.getReviewerUser().getAccountId().get()),
-              rsrc.getReviewerUser().getAccountId(),
-              cd);
-      loader.put(info);
+      ReviewerInfo info;
+      if (rsrc.isByEmail()) {
+        Address address = rsrc.getReviewerByEmail();
+        info = ReviewerInfo.byEmail(address.getName(), address.getEmail());
+      } else {
+        Account.Id reviewerAccountId = rsrc.getReviewerUser().getAccountId();
+        info = format(new ReviewerInfo(reviewerAccountId.get()), reviewerAccountId, cd);
+        loader.put(info);
+      }
       infos.add(info);
     }
     loader.fill();
@@ -94,19 +98,21 @@
     return format(ImmutableList.<ReviewerResource>of(rsrc));
   }
 
-  public ReviewerInfo format(ReviewerInfo out, Account.Id reviewer, ChangeData cd)
+  public ReviewerInfo format(ReviewerInfo out, Account.Id reviewerAccountId, ChangeData cd)
       throws OrmException, PermissionBackendException {
     PatchSet.Id psId = cd.change().currentPatchSetId();
     return format(
         out,
-        reviewer,
+        reviewerAccountId,
         cd,
-        approvalsUtil.byPatchSetUser(
-            db.get(), cd.notes(), psId, new Account.Id(out._accountId), null, null));
+        approvalsUtil.byPatchSetUser(db.get(), cd.notes(), psId, reviewerAccountId, null, null));
   }
 
   public ReviewerInfo format(
-      ReviewerInfo out, Account.Id reviewer, ChangeData cd, Iterable<PatchSetApproval> approvals)
+      ReviewerInfo out,
+      Account.Id reviewerAccountId,
+      ChangeData cd,
+      Iterable<PatchSetApproval> approvals)
       throws OrmException, PermissionBackendException {
     LabelTypes labelTypes = cd.getLabelTypes();
 
@@ -123,7 +129,7 @@
     PatchSet ps = cd.currentPatchSet();
     if (ps != null) {
       PermissionBackend.ForChange perm =
-          permissionBackend.absentUser(reviewer).database(db).change(cd);
+          permissionBackend.absentUser(reviewerAccountId).database(db).change(cd);
 
       for (SubmitRecord rec : submitRuleEvaluator.evaluate(cd)) {
         if (rec.labels == null) {
diff --git a/java/com/google/gerrit/server/change/SetAssigneeOp.java b/java/com/google/gerrit/server/change/SetAssigneeOp.java
index f61e95f..dd24ff6 100644
--- a/java/com/google/gerrit/server/change/SetAssigneeOp.java
+++ b/java/com/google/gerrit/server/change/SetAssigneeOp.java
@@ -99,7 +99,7 @@
     return true;
   }
 
-  private void addMessage(ChangeContext ctx, ChangeUpdate update) throws OrmException {
+  private void addMessage(ChangeContext ctx, ChangeUpdate update) {
     StringBuilder msg = new StringBuilder();
     msg.append("Assignee ");
     if (oldAssignee == null) {
@@ -113,7 +113,7 @@
     }
     ChangeMessage cmsg =
         ChangeMessagesUtil.newMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_SET_ASSIGNEE);
-    cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
+    cmUtil.addChangeMessage(update, cmsg);
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/change/SetHashtagsOp.java b/java/com/google/gerrit/server/change/SetHashtagsOp.java
index d11b2df..2c180ed 100644
--- a/java/com/google/gerrit/server/change/SetHashtagsOp.java
+++ b/java/com/google/gerrit/server/change/SetHashtagsOp.java
@@ -125,13 +125,13 @@
     }
   }
 
-  private void addMessage(ChangeContext ctx, ChangeUpdate update) throws OrmException {
+  private void addMessage(ChangeContext ctx, ChangeUpdate update) {
     StringBuilder msg = new StringBuilder();
     appendHashtagMessage(msg, "added", toAdd);
     appendHashtagMessage(msg, "removed", toRemove);
     ChangeMessage cmsg =
         ChangeMessagesUtil.newMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_SET_HASHTAGS);
-    cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
+    cmUtil.addChangeMessage(update, cmsg);
   }
 
   private void appendHashtagMessage(StringBuilder b, String action, Set<String> hashtags) {
diff --git a/java/com/google/gerrit/server/change/WorkInProgressOp.java b/java/com/google/gerrit/server/change/WorkInProgressOp.java
index 35b4e6e..1da6d16 100644
--- a/java/com/google/gerrit/server/change/WorkInProgressOp.java
+++ b/java/com/google/gerrit/server/change/WorkInProgressOp.java
@@ -123,7 +123,7 @@
   public boolean updateChange(ChangeContext ctx) throws OrmException {
     change = ctx.getChange();
     notes = ctx.getNotes();
-    ps = psUtil.get(ctx.getDb(), ctx.getNotes(), change.currentPatchSetId());
+    ps = psUtil.get(ctx.getNotes(), change.currentPatchSetId());
     ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
     change.setWorkInProgress(workInProgress);
     if (!change.hasReviewStarted() && !workInProgress) {
@@ -135,7 +135,7 @@
     return true;
   }
 
-  private void addMessage(ChangeContext ctx, ChangeUpdate update) throws OrmException {
+  private void addMessage(ChangeContext ctx, ChangeUpdate update) {
     Change c = ctx.getChange();
     StringBuilder buf =
         new StringBuilder(c.isWorkInProgress() ? "Set Work In Progress" : "Set Ready For Review");
@@ -154,7 +154,7 @@
                 ? ChangeMessagesUtil.TAG_SET_WIP
                 : ChangeMessagesUtil.TAG_SET_READY);
 
-    cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
+    cmUtil.addChangeMessage(update, cmsg);
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/config/DefaultUrlFormatter.java b/java/com/google/gerrit/server/config/DefaultUrlFormatter.java
index 70fb465..060ee3f 100644
--- a/java/com/google/gerrit/server/config/DefaultUrlFormatter.java
+++ b/java/com/google/gerrit/server/config/DefaultUrlFormatter.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.config;
 
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -27,7 +28,8 @@
   public static class Module extends AbstractModule {
     @Override
     protected void configure() {
-      bind(UrlFormatter.class).to(DefaultUrlFormatter.class);
+      DynamicItem.itemOf(binder(), UrlFormatter.class);
+      DynamicItem.bind(binder(), UrlFormatter.class).to(DefaultUrlFormatter.class);
     }
   }
 
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index b26e875..6eb4ad9 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -246,7 +246,7 @@
     install(new GitModule());
     install(new GroupDbModule());
     install(new GroupModule());
-    install(new NoteDbModule(cfg));
+    install(new NoteDbModule());
     install(new PrologModule());
     install(new DefaultSubmitRule.Module());
     install(new IgnoreSelfApprovalRule.Module());
diff --git a/java/com/google/gerrit/server/config/GerritServerConfigReloader.java b/java/com/google/gerrit/server/config/GerritServerConfigReloader.java
index 09c10740..5ecf6ed 100644
--- a/java/com/google/gerrit/server/config/GerritServerConfigReloader.java
+++ b/java/com/google/gerrit/server/config/GerritServerConfigReloader.java
@@ -17,9 +17,9 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.config.ConfigUpdatedEvent.ConfigUpdateEntry;
 import com.google.gerrit.server.config.ConfigUpdatedEvent.UpdateResult;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
@@ -29,11 +29,12 @@
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final GerritServerConfigProvider configProvider;
-  private final DynamicSet<GerritConfigListener> configListeners;
+  private final PluginSetContext<GerritConfigListener> configListeners;
 
   @Inject
   GerritServerConfigReloader(
-      GerritServerConfigProvider configProvider, DynamicSet<GerritConfigListener> configListeners) {
+      GerritServerConfigProvider configProvider,
+      PluginSetContext<GerritConfigListener> configListeners) {
     this.configProvider = configProvider;
     this.configListeners = configListeners;
   }
@@ -53,9 +54,7 @@
   public Multimap<UpdateResult, ConfigUpdateEntry> fireUpdatedConfigEvent(
       ConfigUpdatedEvent event) {
     Multimap<UpdateResult, ConfigUpdateEntry> updates = ArrayListMultimap.create();
-    for (GerritConfigListener configListener : configListeners) {
-      updates.putAll(configListener.configUpdated(event));
-    }
+    configListeners.runEach(l -> updates.putAll(l.configUpdated(event)));
     return updates;
   }
 }
diff --git a/java/com/google/gerrit/server/config/GerritServerIdProvider.java b/java/com/google/gerrit/server/config/GerritServerIdProvider.java
index c609cc4..4898f55 100644
--- a/java/com/google/gerrit/server/config/GerritServerIdProvider.java
+++ b/java/com/google/gerrit/server/config/GerritServerIdProvider.java
@@ -48,9 +48,7 @@
 
     // We're not generally supposed to do work in provider constructors, but this is a bit of a
     // special case because we really need to have the ID available by the time the dbInjector
-    // is created. This even applies during MigrateToNoteDb, which otherwise would have been a
-    // reasonable place to do the ID generation. Fortunately, it's not much work, and it happens
-    // once.
+    // is created. Fortunately, it's not much work, and it happens once.
     id = generate();
     Config newCfg = readGerritConfig(sitePaths);
     newCfg.setString(SECTION, null, KEY, id);
diff --git a/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index ce359a9..d11f61d 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -168,7 +168,7 @@
   }
 
   private void rebase(Repository repository, ChangeEdit changeEdit, PatchSet currentPatchSet)
-      throws IOException, MergeConflictException, InvalidChangeOperationException, OrmException {
+      throws IOException, MergeConflictException, InvalidChangeOperationException {
     RevCommit currentEditCommit = changeEdit.getEditCommit();
     if (currentEditCommit.getParentCount() == 0) {
       throw new InvalidChangeOperationException(
@@ -457,7 +457,7 @@
   }
 
   private PatchSet lookupCurrentPatchSet(ChangeNotes notes) throws OrmException {
-    return patchSetUtil.current(reviewDb.get(), notes);
+    return patchSetUtil.current(notes);
   }
 
   private static boolean isBasedOn(ChangeEdit changeEdit, PatchSet patchSet) {
@@ -543,7 +543,7 @@
       PatchSet basePatchSet,
       ObjectId newEditCommitId,
       Timestamp timestamp)
-      throws IOException, OrmException {
+      throws IOException {
     Change change = notes.getChange();
     String editRefName = getEditRefName(change, basePatchSet);
     updateReference(repository, editRefName, ObjectId.zeroId(), newEditCommitId, timestamp);
@@ -560,7 +560,7 @@
 
   private ChangeEdit updateEdit(
       Repository repository, ChangeEdit changeEdit, ObjectId newEditCommitId, Timestamp timestamp)
-      throws IOException, OrmException {
+      throws IOException {
     String editRefName = changeEdit.getRefName();
     RevCommit currentEditCommit = changeEdit.getEditCommit();
     updateReference(repository, editRefName, currentEditCommit, newEditCommitId, timestamp);
@@ -627,7 +627,7 @@
     return user.newRefLogIdent(timestamp, tz);
   }
 
-  private void reindex(Change change) throws IOException, OrmException {
+  private void reindex(Change change) throws IOException {
     indexer.index(reviewDb.get(), change);
   }
 }
diff --git a/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index d5add76..9e267ac 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -238,8 +238,7 @@
       int pos = ref.getName().lastIndexOf('/');
       checkArgument(pos > 0, "invalid edit ref: %s", ref.getName());
       String psId = ref.getName().substring(pos + 1);
-      return psUtil.get(
-          db.get(), notes, new PatchSet.Id(notes.getChange().getId(), Integer.parseInt(psId)));
+      return psUtil.get(notes, new PatchSet.Id(notes.getChange().getId(), Integer.parseInt(psId)));
     } catch (OrmException | NumberFormatException e) {
       throw new IOException(e);
     }
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 40ad144..25b96c8 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -222,9 +222,8 @@
    * @param a
    * @param notes
    */
-  public void addAllReviewers(ReviewDb db, ChangeAttribute a, ChangeNotes notes)
-      throws OrmException {
-    Collection<Account.Id> reviewers = approvalsUtil.getReviewers(db, notes).all();
+  public void addAllReviewers(ChangeAttribute a, ChangeNotes notes) throws OrmException {
+    Collection<Account.Id> reviewers = approvalsUtil.getReviewers(notes).all();
     if (!reviewers.isEmpty()) {
       a.allReviewers = Lists.newArrayListWithCapacity(reviewers.size());
       for (Account.Id id : reviewers) {
diff --git a/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 972266a..04abb96 100644
--- a/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -45,7 +45,6 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.data.AccountAttribute;
 import com.google.gerrit.server.data.ApprovalAttribute;
@@ -60,7 +59,6 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.util.Collection;
@@ -117,7 +115,6 @@
   }
 
   private final PluginItemContext<EventDispatcher> dispatcher;
-  private final Provider<ReviewDb> db;
   private final EventFactory eventFactory;
   private final ProjectCache projectCache;
   private final GitRepositoryManager repoManager;
@@ -127,14 +124,12 @@
   @Inject
   StreamEventsApiListener(
       PluginItemContext<EventDispatcher> dispatcher,
-      Provider<ReviewDb> db,
       EventFactory eventFactory,
       ProjectCache projectCache,
       GitRepositoryManager repoManager,
       PatchSetUtil psUtil,
       ChangeNotes.Factory changeNotesFactory) {
     this.dispatcher = dispatcher;
-    this.db = db;
     this.eventFactory = eventFactory;
     this.projectCache = projectCache;
     this.repoManager = repoManager;
@@ -151,7 +146,7 @@
   }
 
   private PatchSet getPatchSet(ChangeNotes notes, RevisionInfo info) throws OrmException {
-    return psUtil.get(db.get(), notes, PatchSet.Id.fromRef(info.ref));
+    return psUtil.get(notes, PatchSet.Id.fromRef(info.ref));
   }
 
   private Supplier<ChangeAttribute> changeAttributeSupplier(Change change, ChangeNotes notes) {
@@ -311,7 +306,7 @@
       Change change = notes.getChange();
       ReviewerDeletedEvent event = new ReviewerDeletedEvent(change);
       event.change = changeAttributeSupplier(change, notes);
-      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
+      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
       event.reviewer = accountAttributeSupplier(ev.getReviewer());
       event.remover = accountAttributeSupplier(ev.getWho());
       event.comment = ev.getComment();
@@ -332,7 +327,7 @@
       ReviewerAddedEvent event = new ReviewerAddedEvent(change);
 
       event.change = changeAttributeSupplier(change, notes);
-      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
+      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
       for (AccountInfo reviewer : ev.getReviewers()) {
         event.reviewer = accountAttributeSupplier(reviewer);
         dispatcher.run(d -> d.postEvent(event));
@@ -420,7 +415,7 @@
 
       event.change = changeAttributeSupplier(change, notes);
       event.restorer = accountAttributeSupplier(ev.getWho());
-      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
+      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
       event.reason = ev.getReason();
 
       dispatcher.run(d -> d.postEvent(change, event));
@@ -438,7 +433,7 @@
 
       event.change = changeAttributeSupplier(change, notes);
       event.submitter = accountAttributeSupplier(ev.getWho());
-      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
+      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
       event.newRev = ev.getNewRevisionId();
 
       dispatcher.run(d -> d.postEvent(change, event));
@@ -456,7 +451,7 @@
 
       event.change = changeAttributeSupplier(change, notes);
       event.abandoner = accountAttributeSupplier(ev.getWho());
-      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
+      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
       event.reason = ev.getReason();
 
       dispatcher.run(d -> d.postEvent(change, event));
@@ -509,7 +504,7 @@
       VoteDeletedEvent event = new VoteDeletedEvent(change);
 
       event.change = changeAttributeSupplier(change, notes);
-      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes));
+      event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
       event.comment = ev.getMessage();
       event.reviewer = accountAttributeSupplier(ev.getReviewer());
       event.remover = accountAttributeSupplier(ev.getWho());
diff --git a/java/com/google/gerrit/server/extensions/events/PluginEvent.java b/java/com/google/gerrit/server/extensions/events/PluginEvent.java
index 8680ab1..60d27c9 100644
--- a/java/com/google/gerrit/server/extensions/events/PluginEvent.java
+++ b/java/com/google/gerrit/server/extensions/events/PluginEvent.java
@@ -15,16 +15,16 @@
 package com.google.gerrit.server.extensions.events;
 
 import com.google.gerrit.extensions.events.PluginEventListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 @Singleton
 public class PluginEvent {
-  private final DynamicSet<PluginEventListener> listeners;
+  private final PluginSetContext<PluginEventListener> listeners;
 
   @Inject
-  PluginEvent(DynamicSet<PluginEventListener> listeners) {
+  PluginEvent(PluginSetContext<PluginEventListener> listeners) {
     this.listeners = listeners;
   }
 
@@ -33,9 +33,7 @@
       return;
     }
     Event e = new Event(pluginName, type, data);
-    for (PluginEventListener l : listeners) {
-      l.onPluginEvent(e);
-    }
+    listeners.runEach(l -> l.onPluginEvent(e));
   }
 
   private static class Event extends AbstractNoNotifyEvent implements PluginEventListener.Event {
diff --git a/java/com/google/gerrit/server/git/DefaultAdvertiseRefsHook.java b/java/com/google/gerrit/server/git/DefaultAdvertiseRefsHook.java
index be8fcdb..f255ea2 100644
--- a/java/com/google/gerrit/server/git/DefaultAdvertiseRefsHook.java
+++ b/java/com/google/gerrit/server/git/DefaultAdvertiseRefsHook.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.git;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.eclipse.jgit.lib.Ref;
@@ -49,13 +49,12 @@
       if (prefixes.isEmpty() || prefixes.get(0).isEmpty()) {
         refs = repo.getAllRefs();
       } else {
-        ImmutableMap.Builder<String, Ref> b = new ImmutableMap.Builder<>();
+        refs = new HashMap<>();
         for (String prefix : prefixes) {
           for (Ref ref : repo.getRefDatabase().getRefsByPrefix(prefix)) {
-            b.put(ref.getName(), ref);
+            refs.put(ref.getName(), ref);
           }
         }
-        refs = b.build();
       }
       return perm.filter(refs, repo, opts);
     } catch (IOException | PermissionBackendException e) {
diff --git a/java/com/google/gerrit/server/git/GarbageCollection.java b/java/com/google/gerrit/server/git/GarbageCollection.java
index 3624695..75c9012 100644
--- a/java/com/google/gerrit/server/git/GarbageCollection.java
+++ b/java/com/google/gerrit/server/git/GarbageCollection.java
@@ -18,10 +18,10 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.data.GarbageCollectionResult;
 import com.google.gerrit.extensions.events.GarbageCollectorListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.GcConfig;
 import com.google.gerrit.server.extensions.events.AbstractNoNotifyEvent;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import java.io.PrintWriter;
 import java.util.List;
@@ -43,7 +43,7 @@
   private final GitRepositoryManager repoManager;
   private final GarbageCollectionQueue gcQueue;
   private final GcConfig gcConfig;
-  private final DynamicSet<GarbageCollectorListener> listeners;
+  private final PluginSetContext<GarbageCollectorListener> listeners;
 
   public interface Factory {
     GarbageCollection create();
@@ -54,7 +54,7 @@
       GitRepositoryManager repoManager,
       GarbageCollectionQueue gcQueue,
       GcConfig config,
-      DynamicSet<GarbageCollectorListener> listeners) {
+      PluginSetContext<GarbageCollectorListener> listeners) {
     this.repoManager = repoManager;
     this.gcQueue = gcQueue;
     this.gcConfig = config;
@@ -113,13 +113,7 @@
       return;
     }
     Event event = new Event(p, statistics);
-    for (GarbageCollectorListener l : listeners) {
-      try {
-        l.onGarbageCollected(event);
-      } catch (RuntimeException e) {
-        logger.atWarning().withCause(e).log("Failure in GarbageCollectorListener");
-      }
-    }
+    listeners.runEach(l -> l.onGarbageCollected(event));
   }
 
   private static void logGcInfo(Project.NameKey projectName, String msg) {
diff --git a/java/com/google/gerrit/server/git/GroupCollector.java b/java/com/google/gerrit/server/git/GroupCollector.java
index bb65fa8..88632e6 100644
--- a/java/com/google/gerrit/server/git/GroupCollector.java
+++ b/java/com/google/gerrit/server/git/GroupCollector.java
@@ -117,7 +117,7 @@
           public List<String> lookup(PatchSet.Id psId) throws OrmException {
             // TODO(dborowitz): Reuse open repository from caller.
             ChangeNotes notes = notesFactory.createChecked(db, project, psId.getParentKey());
-            PatchSet ps = psUtil.get(db, notes, psId);
+            PatchSet ps = psUtil.get(notes, psId);
             return ps != null ? ps.getGroups() : null;
           }
         });
diff --git a/java/com/google/gerrit/server/git/MergedByPushOp.java b/java/com/google/gerrit/server/git/MergedByPushOp.java
index b3a1d72..06d4e82 100644
--- a/java/com/google/gerrit/server/git/MergedByPushOp.java
+++ b/java/com/google/gerrit/server/git/MergedByPushOp.java
@@ -120,7 +120,7 @@
     } else {
       patchSet =
           requireNonNull(
-              psUtil.get(ctx.getDb(), ctx.getNotes(), psId),
+              psUtil.get(ctx.getNotes(), psId),
               () -> String.format("patch set %s not found", psId));
     }
     info = getPatchSetInfo(ctx);
@@ -151,7 +151,7 @@
     ChangeMessage msg =
         ChangeMessagesUtil.newMessage(
             psId, ctx.getUser(), ctx.getWhen(), msgBuf.toString(), ChangeMessagesUtil.TAG_MERGED);
-    cmUtil.addChangeMessage(ctx.getDb(), update, msg);
+    cmUtil.addChangeMessage(update, msg);
 
     PatchSetApproval submitter =
         ApprovalsUtil.newApproval(
diff --git a/java/com/google/gerrit/server/git/meta/MetaDataUpdate.java b/java/com/google/gerrit/server/git/meta/MetaDataUpdate.java
index bbe0c62..97beefd 100644
--- a/java/com/google/gerrit/server/git/meta/MetaDataUpdate.java
+++ b/java/com/google/gerrit/server/git/meta/MetaDataUpdate.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 import com.google.inject.assistedinject.Assisted;
 import java.io.IOException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -33,21 +34,22 @@
 
 /** Helps with the updating of a {@link VersionedMetaData}. */
 public class MetaDataUpdate implements AutoCloseable {
+  @Singleton
   public static class User {
     private final InternalFactory factory;
     private final GitRepositoryManager mgr;
-    private final PersonIdent serverIdent;
+    private final Provider<PersonIdent> serverIdentProvider;
     private final Provider<IdentifiedUser> identifiedUser;
 
     @Inject
     User(
         InternalFactory factory,
         GitRepositoryManager mgr,
-        @GerritPersonIdent PersonIdent serverIdent,
+        @GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
         Provider<IdentifiedUser> identifiedUser) {
       this.factory = factory;
       this.mgr = mgr;
-      this.serverIdent = serverIdent;
+      this.serverIdentProvider = serverIdentProvider;
       this.identifiedUser = identifiedUser;
     }
 
@@ -126,29 +128,31 @@
     public MetaDataUpdate create(
         Project.NameKey name, Repository repository, IdentifiedUser user, BatchRefUpdate batch) {
       MetaDataUpdate md = factory.create(name, repository, batch);
-      md.getCommitBuilder().setCommitter(serverIdent);
+      md.getCommitBuilder().setCommitter(serverIdentProvider.get());
       md.setAuthor(user);
       return md;
     }
 
     private PersonIdent createPersonIdent(IdentifiedUser user) {
+      PersonIdent serverIdent = serverIdentProvider.get();
       return user.newCommitterIdent(serverIdent.getWhen(), serverIdent.getTimeZone());
     }
   }
 
+  @Singleton
   public static class Server {
     private final InternalFactory factory;
     private final GitRepositoryManager mgr;
-    private final PersonIdent serverIdent;
+    private final Provider<PersonIdent> serverIdentProvider;
 
     @Inject
     Server(
         InternalFactory factory,
         GitRepositoryManager mgr,
-        @GerritPersonIdent PersonIdent serverIdent) {
+        @GerritPersonIdent Provider<PersonIdent> serverIdentProvider) {
       this.factory = factory;
       this.mgr = mgr;
-      this.serverIdent = serverIdent;
+      this.serverIdentProvider = serverIdentProvider;
     }
 
     public MetaDataUpdate create(Project.NameKey name)
@@ -162,6 +166,7 @@
       Repository repo = mgr.openRepository(name);
       MetaDataUpdate md = factory.create(name, repo, batch);
       md.setCloseRepository(true);
+      PersonIdent serverIdent = serverIdentProvider.get();
       md.getCommitBuilder().setAuthor(serverIdent);
       md.getCommitBuilder().setCommitter(serverIdent);
       return md;
diff --git a/java/com/google/gerrit/server/git/receive/LazyPostReceiveHookChain.java b/java/com/google/gerrit/server/git/receive/LazyPostReceiveHookChain.java
index 7adb21b..0f081be 100644
--- a/java/com/google/gerrit/server/git/receive/LazyPostReceiveHookChain.java
+++ b/java/com/google/gerrit/server/git/receive/LazyPostReceiveHookChain.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.git.receive;
 
-import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import java.util.Collection;
 import org.eclipse.jgit.transport.PostReceiveHook;
@@ -22,17 +22,15 @@
 import org.eclipse.jgit.transport.ReceivePack;
 
 class LazyPostReceiveHookChain implements PostReceiveHook {
-  private final DynamicSet<PostReceiveHook> hooks;
+  private final PluginSetContext<PostReceiveHook> hooks;
 
   @Inject
-  LazyPostReceiveHookChain(DynamicSet<PostReceiveHook> hooks) {
+  LazyPostReceiveHookChain(PluginSetContext<PostReceiveHook> hooks) {
     this.hooks = hooks;
   }
 
   @Override
   public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
-    for (PostReceiveHook h : hooks) {
-      h.onPostReceive(rp, commands);
-    }
+    hooks.runEach(h -> h.onPostReceive(rp, commands));
   }
 }
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index c747533..32fbd36 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -18,6 +18,7 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.flogger.LazyArgs.lazy;
 import static com.google.gerrit.common.FooterConstants.CHANGE_ID;
 import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
 import static com.google.gerrit.reviewdb.client.RefNames.isConfigRef;
@@ -610,14 +611,10 @@
       newProgress.end();
       replaceProgress.end();
       queueSuccessMessages(newChanges);
-      refsPublishDeprecationWarning();
-    }
-  }
 
-  private void refsPublishDeprecationWarning() {
-    // TODO(xchangcheng): remove after migrating tools which are using this magic branch.
-    if (magicBranch != null && magicBranch.publish) {
-      addMessage("Pushing to refs/publish/* is deprecated, use refs/for/* instead.");
+      logger.atFine().log(
+          "Command results: %s",
+          lazy(() -> commands.stream().map(ReceiveCommits::commandToString).collect(joining(","))));
     }
   }
 
@@ -644,7 +641,7 @@
         ObjectInserter ins = repo.newObjectInserter();
         ObjectReader reader = ins.newReader();
         RevWalk rw = new RevWalk(reader)) {
-      bu.setRepository(repo, rw, ins).updateChangesInParallel();
+      bu.setRepository(repo, rw, ins);
       bu.setRefLogMessage("push");
 
       int added = 0;
@@ -797,7 +794,7 @@
         ObjectInserter ins = repo.newObjectInserter();
         ObjectReader reader = ins.newReader();
         RevWalk rw = new RevWalk(reader)) {
-      bu.setRepository(repo, rw, ins).updateChangesInParallel();
+      bu.setRepository(repo, rw, ins);
       bu.setRefLogMessage("push");
 
       logger.atFine().log("Adding %d replace requests", newChanges.size());
@@ -1357,8 +1354,6 @@
                 + "for new changes and '--edit' for existing changes")
     boolean draft;
 
-    boolean publish;
-
     @Option(name = "--private", usage = "mark new/updated change as private")
     boolean isPrivate;
 
@@ -1496,7 +1491,6 @@
       this.deprecatedTopicSeen = false;
       this.cmd = cmd;
       this.draft = cmd.getRefName().startsWith(MagicBranch.NEW_DRAFT_CHANGE);
-      this.publish = cmd.getRefName().startsWith(MagicBranch.NEW_PUBLISH_CHANGE);
       this.labelTypes = labelTypes;
       this.notesMigration = notesMigration;
       GeneralPreferencesInfo prefs = user.state().getGeneralPreferences();
@@ -1670,6 +1664,11 @@
       logger.atFine().log("Handling %s", RefNames.REFS_USERS_SELF);
       ref = RefNames.refsUsers(user.getAccountId());
     }
+    // Pushing changes for review usually requires that the target branch exists, but there is an
+    // exception for the branch to which HEAD points to and for refs/meta/config. Pushing for
+    // review to these branches is allowed even if the branch does not exist yet. This allows to
+    // push initial code for review to an empty repository and to review an initial project
+    // configuration.
     if (!receivePack.getAdvertisedRefs().containsKey(ref)
         && !ref.equals(readHEAD(repo))
         && !ref.equals(RefNames.REFS_CONFIG)) {
@@ -1753,9 +1752,10 @@
           reject(cmd, "cannot use merged with base");
           return;
         }
-        RevCommit branchTip = readBranchTip(cmd, magicBranch.dest);
+        RevCommit branchTip = readBranchTip(magicBranch.dest);
         if (branchTip == null) {
-          return; // readBranchTip already rejected cmd.
+          reject(cmd, magicBranch.dest.get() + " not found");
+          return;
         }
         if (!walk.isMergedInto(tip, branchTip)) {
           reject(cmd, "not merged into branch");
@@ -1793,12 +1793,21 @@
           }
         }
       } else if (newChangeForAllNotInTarget) {
-        RevCommit branchTip = readBranchTip(cmd, magicBranch.dest);
-        if (branchTip == null) {
-          return; // readBranchTip already rejected cmd.
+        RevCommit branchTip = readBranchTip(magicBranch.dest);
+        if (branchTip != null) {
+          magicBranch.baseCommit = Collections.singletonList(branchTip);
+          logger.atFine().log("Set baseCommit = %s", magicBranch.baseCommit.get(0).name());
+        } else {
+          // The target branch does not exist. Usually pushing changes for review requires that the
+          // target branch exists, but there is an exception for the branch to which HEAD points to
+          // and for refs/meta/config. Pushing for review to these branches is allowed even if the
+          // branch does not exist yet. This allows to push initial code for review to an empty
+          // repository and to review an initial project configuration.
+          if (!ref.equals(readHEAD(repo)) && !ref.equals(RefNames.REFS_CONFIG)) {
+            reject(cmd, magicBranch.dest.get() + " not found");
+            return;
+          }
         }
-        magicBranch.baseCommit = Collections.singletonList(branchTip);
-        logger.atFine().log("Set baseCommit = %s", magicBranch.baseCommit.get(0).name());
       }
     } catch (IOException ex) {
       logger.atWarning().withCause(ex).log(
@@ -1863,17 +1872,18 @@
 
   private static String readHEAD(Repository repo) {
     try {
-      return repo.getFullBranch();
+      String head = repo.getFullBranch();
+      logger.atFine().log("HEAD = %s", head);
+      return head;
     } catch (IOException e) {
       logger.atSevere().withCause(e).log("Cannot read HEAD symref");
       return null;
     }
   }
 
-  private RevCommit readBranchTip(ReceiveCommand cmd, Branch.NameKey branch) throws IOException {
+  private RevCommit readBranchTip(Branch.NameKey branch) throws IOException {
     Ref r = allRefs().get(branch.get());
     if (r == null) {
-      reject(cmd, branch.get() + " not found");
       return null;
     }
     return receivePack.getRevWalk().parseCommit(r.getObjectId());
@@ -2725,10 +2735,13 @@
 
     /** prints a warning if the new PS has the same tree as the previous commit. */
     private void sameTreeWarning() throws IOException {
-      RevCommit newCommit = receivePack.getRevWalk().parseCommit(newCommitId);
+      RevWalk rw = receivePack.getRevWalk();
+      RevCommit newCommit = rw.parseCommit(newCommitId);
       RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
 
       if (newCommit.getTree().equals(priorCommit.getTree())) {
+        rw.parseBody(newCommit);
+        rw.parseBody(priorCommit);
         boolean messageEq =
             Objects.equals(newCommit.getFullMessage(), priorCommit.getFullMessage());
         boolean parentsEq = parentsEqual(newCommit, priorCommit);
@@ -2868,7 +2881,7 @@
           new BatchUpdateOp() {
             @Override
             public boolean updateChange(ChangeContext ctx) throws OrmException {
-              PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+              PatchSet ps = psUtil.get(ctx.getNotes(), psId);
               List<String> oldGroups = ps.getGroups();
               if (oldGroups == null) {
                 if (groups == null) {
@@ -2877,7 +2890,7 @@
               } else if (sameGroups(oldGroups, groups)) {
                 return false;
               }
-              psUtil.setGroups(ctx.getDb(), ctx.getUpdate(psId), ps, groups);
+              psUtil.setGroups(ctx.getUpdate(psId), ps, groups);
               return true;
             }
           });
@@ -3101,7 +3114,7 @@
                 ObjectInserter ins = repo.newObjectInserter();
                 ObjectReader reader = ins.newReader();
                 RevWalk rw = new RevWalk(reader)) {
-              bu.setRepository(repo, rw, ins).updateChangesInParallel();
+              bu.setRepository(repo, rw, ins);
               // TODO(dborowitz): Teach BatchUpdate to ignore missing changes.
 
               RevCommit newTip = rw.parseCommit(cmd.getNewId());
@@ -3258,4 +3271,15 @@
   private static boolean isConfig(ReceiveCommand cmd) {
     return cmd.getRefName().equals(RefNames.REFS_CONFIG);
   }
+
+  private static String commandToString(ReceiveCommand cmd) {
+    StringBuilder b = new StringBuilder();
+    b.append(cmd);
+    b.append("  (").append(cmd.getResult());
+    if (cmd.getMessage() != null) {
+      b.append(": ").append(cmd.getMessage());
+    }
+    b.append(")\n");
+    return b.toString();
+  }
 }
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index a580cf6..d2ef12c 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -255,7 +255,7 @@
       return false;
     }
     if (groups.isEmpty()) {
-      PatchSet prevPs = psUtil.current(ctx.getDb(), notes);
+      PatchSet prevPs = psUtil.current(notes);
       groups = prevPs != null ? prevPs.getGroups() : ImmutableList.<String>of();
     }
 
@@ -305,7 +305,6 @@
 
     newPatchSet =
         psUtil.insert(
-            ctx.getDb(),
             ctx.getRevWalk(),
             update,
             patchSetId,
@@ -353,7 +352,7 @@
     }
 
     msg = createChangeMessage(ctx, reviewMessage);
-    cmUtil.addChangeMessage(ctx.getDb(), update, msg);
+    cmUtil.addChangeMessage(update, msg);
 
     if (mergedByPushOp == null) {
       resetChange(ctx);
@@ -512,7 +511,7 @@
   private List<Comment> publishComments(ChangeContext ctx, boolean workInProgress)
       throws OrmException {
     List<Comment> comments =
-        commentsUtil.draftByChangeAuthor(ctx.getDb(), ctx.getNotes(), ctx.getUser().getAccountId());
+        commentsUtil.draftByChangeAuthor(ctx.getNotes(), ctx.getUser().getAccountId());
     publishCommentUtil.publish(
         ctx, patchSetId, comments, ChangeMessagesUtil.uploadedPatchSetTag(workInProgress));
     return comments;
diff --git a/java/com/google/gerrit/server/index/IndexModule.java b/java/com/google/gerrit/server/index/IndexModule.java
index 3c9538c..a84fbe9 100644
--- a/java/com/google/gerrit/server/index/IndexModule.java
+++ b/java/com/google/gerrit/server/index/IndexModule.java
@@ -125,7 +125,7 @@
     factory(ChangeIndexer.Factory.class);
 
     bind(GroupIndexRewriter.class);
-    bind(GroupIndexCollection.class);
+    // GroupIndexCollection is already bound very high up in ReviewDbSchemaModule.
     listener().to(GroupIndexCollection.class);
     factory(GroupIndexerImpl.Factory.class);
 
diff --git a/java/com/google/gerrit/server/index/OnlineReindexer.java b/java/com/google/gerrit/server/index/OnlineReindexer.java
index ec0e1d4..8a1776d 100644
--- a/java/com/google/gerrit/server/index/OnlineReindexer.java
+++ b/java/com/google/gerrit/server/index/OnlineReindexer.java
@@ -18,11 +18,11 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.index.Index;
 import com.google.gerrit.index.IndexCollection;
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.SiteIndexer;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -35,7 +35,7 @@
   private final SiteIndexer<K, V, I> batchIndexer;
   private final int oldVersion;
   private final int newVersion;
-  private final DynamicSet<OnlineUpgradeListener> listeners;
+  private final PluginSetContext<OnlineUpgradeListener> listeners;
   private I index;
   private final AtomicBoolean running = new AtomicBoolean();
 
@@ -43,7 +43,7 @@
       IndexDefinition<K, V, I> def,
       int oldVersion,
       int newVersion,
-      DynamicSet<OnlineUpgradeListener> listeners) {
+      PluginSetContext<OnlineUpgradeListener> listeners) {
     this.name = def.getName();
     this.indexes = def.getIndexCollection();
     this.batchIndexer = def.getSiteIndexer();
@@ -68,9 +68,7 @@
               } finally {
                 running.set(false);
                 if (!ok) {
-                  for (OnlineUpgradeListener listener : listeners) {
-                    listener.onFailure(name, oldVersion, newVersion);
-                  }
+                  listeners.runEach(listener -> listener.onFailure(name, oldVersion, newVersion));
                 }
               }
             }
@@ -94,9 +92,7 @@
   }
 
   private void reindex() throws IOException {
-    for (OnlineUpgradeListener listener : listeners) {
-      listener.onStart(name, oldVersion, newVersion);
-    }
+    listeners.runEach(listener -> listener.onStart(name, oldVersion, newVersion));
     index =
         requireNonNull(
             indexes.getWriteIndex(newVersion),
@@ -118,9 +114,7 @@
     }
     logger.atInfo().log("Reindex %s to version %s complete", name, version(index));
     activateIndex();
-    for (OnlineUpgradeListener listener : listeners) {
-      listener.onSuccess(name, oldVersion, newVersion);
-    }
+    listeners.runEach(listener -> listener.onSuccess(name, oldVersion, newVersion));
   }
 
   public void activateIndex() {
diff --git a/java/com/google/gerrit/server/index/VersionManager.java b/java/com/google/gerrit/server/index/VersionManager.java
index f37472c..3417379 100644
--- a/java/com/google/gerrit/server/index/VersionManager.java
+++ b/java/com/google/gerrit/server/index/VersionManager.java
@@ -21,13 +21,13 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.index.Index;
 import com.google.gerrit.index.IndexCollection;
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.IndexDefinition.IndexFactory;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.ProvisionException;
 import java.io.IOException;
 import java.util.Collection;
@@ -61,7 +61,7 @@
   protected final String runReindexMsg;
   protected final SitePaths sitePaths;
 
-  private final DynamicSet<OnlineUpgradeListener> listeners;
+  private final PluginSetContext<OnlineUpgradeListener> listeners;
 
   // The following fields must be accessed synchronized on this.
   protected final Map<String, IndexDefinition<?, ?, ?>> defs;
@@ -69,7 +69,7 @@
 
   protected VersionManager(
       SitePaths sitePaths,
-      DynamicSet<OnlineUpgradeListener> listeners,
+      PluginSetContext<OnlineUpgradeListener> listeners,
       Collection<IndexDefinition<?, ?, ?>> defs,
       boolean onlineUpgrade) {
     this.sitePaths = sitePaths;
diff --git a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
index 37e288c..8f1433e 100644
--- a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
+++ b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
@@ -224,7 +224,7 @@
         // It does mean that reindexing after invalidating the DiffSummary cache will be expensive,
         // but the goal is to invalidate that cache as infrequently as we possibly can. And besides,
         // we don't have concrete proof that improving packfile locality would help.
-        notesFactory.scan(repo, db, project).forEach(r -> index(db, r));
+        notesFactory.scan(repo, project).forEach(r -> index(db, r));
       } catch (RepositoryNotFoundException rnfe) {
         logger.atSevere().log(rnfe.getMessage());
       }
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index 064af64..af13cac 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -33,8 +33,6 @@
 import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.logging.TraceContext;
 import com.google.gerrit.server.logging.TraceContext.TraceTimer;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -87,8 +85,6 @@
   @Nullable private final ChangeIndexCollection indexes;
   @Nullable private final ChangeIndex index;
   private final SchemaFactory<ReviewDb> schemaFactory;
-  private final NotesMigration notesMigration;
-  private final ChangeNotes.Factory changeNotesFactory;
   private final ChangeData.Factory changeDataFactory;
   private final ThreadLocalRequestContext context;
   private final ListeningExecutorService batchExecutor;
@@ -101,8 +97,6 @@
   ChangeIndexer(
       @GerritServerConfig Config cfg,
       SchemaFactory<ReviewDb> schemaFactory,
-      NotesMigration notesMigration,
-      ChangeNotes.Factory changeNotesFactory,
       ChangeData.Factory changeDataFactory,
       ThreadLocalRequestContext context,
       PluginSetContext<ChangeIndexedListener> indexedListeners,
@@ -112,8 +106,6 @@
       @Assisted ChangeIndex index) {
     this.executor = executor;
     this.schemaFactory = schemaFactory;
-    this.notesMigration = notesMigration;
-    this.changeNotesFactory = changeNotesFactory;
     this.changeDataFactory = changeDataFactory;
     this.context = context;
     this.indexedListeners = indexedListeners;
@@ -128,8 +120,6 @@
   ChangeIndexer(
       SchemaFactory<ReviewDb> schemaFactory,
       @GerritServerConfig Config cfg,
-      NotesMigration notesMigration,
-      ChangeNotes.Factory changeNotesFactory,
       ChangeData.Factory changeDataFactory,
       ThreadLocalRequestContext context,
       PluginSetContext<ChangeIndexedListener> indexedListeners,
@@ -139,8 +129,6 @@
       @Assisted ChangeIndexCollection indexes) {
     this.executor = executor;
     this.schemaFactory = schemaFactory;
-    this.notesMigration = notesMigration;
-    this.changeNotesFactory = changeNotesFactory;
     this.changeDataFactory = changeDataFactory;
     this.context = context;
     this.indexedListeners = indexedListeners;
@@ -239,8 +227,8 @@
    * @param db review database.
    * @param change change to index.
    */
-  public void index(ReviewDb db, Change change) throws IOException, OrmException {
-    index(newChangeData(db, change));
+  public void index(ReviewDb db, Change change) throws IOException {
+    index(changeDataFactory.create(db, change));
   }
 
   /**
@@ -250,9 +238,8 @@
    * @param project the project to which the change belongs.
    * @param changeId ID of the change to index.
    */
-  public void index(ReviewDb db, Project.NameKey project, Change.Id changeId)
-      throws IOException, OrmException {
-    index(newChangeData(db, project, changeId));
+  public void index(ReviewDb db, Project.NameKey project, Change.Id changeId) throws IOException {
+    index(changeDataFactory.create(db, project, changeId));
   }
 
   /**
@@ -383,7 +370,7 @@
 
     @Override
     public Void callImpl(Provider<ReviewDb> db) throws Exception {
-      ChangeData cd = newChangeData(db.get(), project, id);
+      ChangeData cd = changeDataFactory.create(db.get(), project, id);
       index(cd);
       return null;
     }
@@ -429,7 +416,7 @@
     public Boolean callImpl(Provider<ReviewDb> db) throws Exception {
       try {
         if (stalenessChecker.isStale(id)) {
-          indexImpl(newChangeData(db.get(), project, id));
+          indexImpl(changeDataFactory.create(db.get(), project, id));
           return true;
         }
       } catch (NoSuchChangeException nsce) {
@@ -460,26 +447,4 @@
     }
     return false;
   }
-
-  // Avoid auto-rebuilding when reindexing if reading is disabled. This just
-  // increases contention on the meta ref from a background indexing thread
-  // with little benefit. The next actual write to the entity may still incur a
-  // less-contentious rebuild.
-  private ChangeData newChangeData(ReviewDb db, Change change) throws OrmException {
-    if (!notesMigration.readChanges()) {
-      ChangeNotes notes = changeNotesFactory.createWithAutoRebuildingDisabled(change, null);
-      return changeDataFactory.create(db, notes);
-    }
-    return changeDataFactory.create(db, change);
-  }
-
-  private ChangeData newChangeData(ReviewDb db, Project.NameKey project, Change.Id changeId)
-      throws OrmException {
-    if (!notesMigration.readChanges()) {
-      ChangeNotes notes =
-          changeNotesFactory.createWithAutoRebuildingDisabled(db, project, changeId);
-      return changeDataFactory.create(db, notes);
-    }
-    return changeDataFactory.create(db, project, changeId);
-  }
 }
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index 262e82b..b39f4e3 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -283,14 +283,14 @@
     @Override
     public boolean updateChange(ChangeContext ctx)
         throws OrmException, UnprocessableEntityException, PatchListNotAvailableException {
-      patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+      patchSet = psUtil.get(ctx.getNotes(), psId);
       notes = ctx.getNotes();
       if (patchSet == null) {
         throw new OrmException("patch set not found: " + psId);
       }
 
       changeMessage = generateChangeMessage(ctx);
-      changeMessagesUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), changeMessage);
+      changeMessagesUtil.addChangeMessage(ctx.getUpdate(psId), changeMessage);
 
       comments = new ArrayList<>();
       for (MailComment c : parsedComments) {
@@ -301,10 +301,7 @@
             persistentCommentFromMailComment(ctx, c, targetPatchSetForComment(ctx, c, patchSet)));
       }
       commentsUtil.putComments(
-          ctx.getDb(),
-          ctx.getUpdate(ctx.getChange().currentPatchSetId()),
-          Status.PUBLISHED,
-          comments);
+          ctx.getUpdate(ctx.getChange().currentPatchSetId()), Status.PUBLISHED, comments);
 
       return true;
     }
@@ -369,7 +366,6 @@
         ChangeContext ctx, MailComment mailComment, PatchSet current) throws OrmException {
       if (mailComment.getInReplyTo() != null) {
         return psUtil.get(
-            ctx.getDb(),
             ctx.getNotes(),
             new PatchSet.Id(ctx.getChange().getId(), mailComment.getInReplyTo().key.patchSetId));
       }
diff --git a/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index 0b8a3c1..84576f6 100644
--- a/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -159,8 +159,7 @@
       setHeader(MailHeader.PATCH_SET.fieldName(), patchSet.getPatchSetId() + "");
       if (patchSetInfo == null) {
         try {
-          patchSetInfo =
-              args.patchSetInfoFactory.get(args.db.get(), changeData.notes(), patchSet.getId());
+          patchSetInfo = args.patchSetInfoFactory.get(changeData.notes(), patchSet.getId());
         } catch (PatchSetInfoNotAvailableException | OrmException err) {
           patchSetInfo = null;
         }
diff --git a/java/com/google/gerrit/server/mail/send/CommentSender.java b/java/com/google/gerrit/server/mail/send/CommentSender.java
index e810397..c8aa259 100644
--- a/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -316,7 +316,7 @@
 
     Comment.Key key = new Comment.Key(child.parentUuid, child.key.filename, child.key.patchSetId);
     try {
-      return commentsUtil.getPublished(args.db.get(), changeData.notes(), key);
+      return commentsUtil.getPublished(changeData.notes(), key);
     } catch (OrmException e) {
       logger.atWarning().log("Could not find the parent of this comment: %s", child);
       return Optional.empty();
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 72f1ba6..32d086c 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.notedb;
 
+import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
 import static java.util.Objects.requireNonNull;
 
@@ -27,8 +28,6 @@
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -55,9 +54,6 @@
 
     // Providers required to avoid dependency cycles.
 
-    // ChangeRebuilder -> ChangeNotes.Factory -> Args
-    final Provider<ChangeRebuilder> rebuilder;
-
     // ChangeNoteCache -> Args
     final Provider<ChangeNotesCache> cache;
 
@@ -70,7 +66,6 @@
         LegacyChangeNoteRead legacyChangeNoteRead,
         NoteDbMetrics metrics,
         Provider<ReviewDb> db,
-        Provider<ChangeRebuilder> rebuilder,
         Provider<ChangeNotesCache> cache) {
       this.repoManager = repoManager;
       this.migration = migration;
@@ -79,7 +74,6 @@
       this.changeNoteJson = changeNoteJson;
       this.metrics = metrics;
       this.db = db;
-      this.rebuilder = rebuilder;
       this.cache = cache;
     }
   }
@@ -114,22 +108,14 @@
   }
 
   protected final Args args;
-  protected final PrimaryStorage primaryStorage;
-  protected final boolean autoRebuild;
   private final Change.Id changeId;
 
   private ObjectId revision;
   private boolean loaded;
 
-  AbstractChangeNotes(
-      Args args, Change.Id changeId, @Nullable PrimaryStorage primaryStorage, boolean autoRebuild) {
+  AbstractChangeNotes(Args args, Change.Id changeId) {
     this.args = requireNonNull(args);
     this.changeId = requireNonNull(changeId);
-    this.primaryStorage = primaryStorage;
-    this.autoRebuild =
-        primaryStorage == PrimaryStorage.REVIEW_DB
-            && !args.migration.disableChangeReviewDb()
-            && autoRebuild;
   }
 
   public Change.Id getChangeId() {
@@ -146,18 +132,7 @@
       return self();
     }
 
-    boolean read = args.migration.readChanges();
-    if (!read && primaryStorage == PrimaryStorage.NOTE_DB) {
-      throw new OrmException("NoteDb is required to read change " + changeId);
-    }
-    boolean readOrWrite = read || args.migration.rawWriteChangesSetting();
-    if (!readOrWrite) {
-      // Don't even open the repo if we neither write to nor read from NoteDb. It's possible that
-      // there is some garbage in the noteDbState field and/or the repo, but at this point NoteDb is
-      // completely off so it's none of our business.
-      loadDefaults();
-      return self();
-    }
+    checkState(args.migration.readChanges(), "NoteDb is required to read changes");
     if (args.migration.failOnLoadForTest()) {
       throw new OrmException("Reading from NoteDb is disabled");
     }
@@ -166,12 +141,8 @@
         // Call openHandle even if reading is disabled, to trigger
         // auto-rebuilding before this object may get passed to a ChangeUpdate.
         LoadHandle handle = openHandle(repo)) {
-      if (read) {
-        revision = handle.id();
-        onLoad(handle);
-      } else {
-        loadDefaults();
-      }
+      revision = handle.id();
+      onLoad(handle);
       loaded = true;
     } catch (ConfigInvalidException | IOException e) {
       throw new OrmException(e);
@@ -202,7 +173,7 @@
     return LoadHandle.create(ChangeNotesCommit.newRevWalk(repo), id);
   }
 
-  public T reload() throws NoSuchChangeException, OrmException {
+  public T reload() throws OrmException {
     loaded = false;
     return load();
   }
diff --git a/java/com/google/gerrit/server/notedb/ChangeBundle.java b/java/com/google/gerrit/server/notedb/ChangeBundle.java
deleted file mode 100644
index c4d6a91..0000000
--- a/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ /dev/null
@@ -1,976 +0,0 @@
-// Copyright (C) 2016 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.notedb;
-
-import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
-import static com.google.gerrit.reviewdb.server.ReviewDbUtil.checkColumns;
-import static com.google.gerrit.reviewdb.server.ReviewDbUtil.intKeyOrdering;
-import static com.google.gerrit.server.notedb.ChangeBundle.Source.NOTE_DB;
-import static com.google.gerrit.server.notedb.ChangeBundle.Source.REVIEW_DB;
-import static com.google.gerrit.server.util.time.TimeUtil.truncateToSecond;
-import static java.util.Comparator.comparing;
-import static java.util.Comparator.naturalOrder;
-import static java.util.Comparator.nullsFirst;
-import static java.util.Objects.requireNonNull;
-import static java.util.stream.Collectors.toList;
-
-import com.google.auto.value.AutoValue;
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.base.Strings;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSortedMap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Ordering;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSet.Id;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.ReviewerSet;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
-import com.google.gwtorm.client.Column;
-import com.google.gwtorm.server.OrmException;
-import java.lang.reflect.Field;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-
-/**
- * A bundle of all entities rooted at a single {@link Change} entity.
- *
- * <p>See the {@link Change} Javadoc for a depiction of this tree. Bundles may be compared using
- * {@link #differencesFrom(ChangeBundle)}, which normalizes out the minor implementation differences
- * between ReviewDb and NoteDb.
- */
-public class ChangeBundle {
-  public enum Source {
-    REVIEW_DB,
-    NOTE_DB;
-  }
-
-  public static ChangeBundle fromNotes(CommentsUtil commentsUtil, ChangeNotes notes)
-      throws OrmException {
-    return new ChangeBundle(
-        notes.getChange(),
-        notes.getChangeMessages(),
-        notes.getPatchSets().values(),
-        notes.getApprovals().values(),
-        Iterables.concat(
-            CommentsUtil.toPatchLineComments(
-                notes.getChangeId(),
-                PatchLineComment.Status.DRAFT,
-                commentsUtil.draftByChange(null, notes)),
-            CommentsUtil.toPatchLineComments(
-                notes.getChangeId(),
-                PatchLineComment.Status.PUBLISHED,
-                commentsUtil.publishedByChange(null, notes))),
-        notes.getReviewers(),
-        Source.NOTE_DB);
-  }
-
-  private static ImmutableSortedMap<ChangeMessage.Key, ChangeMessage> changeMessageMap(
-      Collection<ChangeMessage> in) {
-    return in.stream()
-        .collect(
-            toImmutableSortedMap(
-                comparing((ChangeMessage.Key k) -> k.getParentKey().get())
-                    .thenComparing(k -> k.get()),
-                cm -> cm.getKey(),
-                cm -> cm));
-  }
-
-  // Unlike the *Map comparators, which are intended to make key lists diffable,
-  // this comparator sorts first on timestamp, then on every other field.
-  private static final Comparator<ChangeMessage> CHANGE_MESSAGE_COMPARATOR =
-      comparing(ChangeMessage::getWrittenOn)
-          .thenComparing(m -> m.getKey().getParentKey().get())
-          .thenComparing(
-              m -> m.getPatchSetId() != null ? m.getPatchSetId().get() : null,
-              nullsFirst(naturalOrder()))
-          .thenComparing(ChangeMessage::getAuthor, intKeyOrdering())
-          .thenComparing(ChangeMessage::getMessage, nullsFirst(naturalOrder()));
-
-  private static ImmutableList<ChangeMessage> changeMessageList(Iterable<ChangeMessage> in) {
-    return Streams.stream(in).sorted(CHANGE_MESSAGE_COMPARATOR).collect(toImmutableList());
-  }
-
-  private static ImmutableSortedMap<Id, PatchSet> patchSetMap(Iterable<PatchSet> in) {
-    return Streams.stream(in)
-        .collect(toImmutableSortedMap(patchSetIdComparator(), PatchSet::getId, ps -> ps));
-  }
-
-  private static ImmutableSortedMap<PatchSetApproval.Key, PatchSetApproval> patchSetApprovalMap(
-      Iterable<PatchSetApproval> in) {
-    return Streams.stream(in)
-        .collect(
-            toImmutableSortedMap(
-                comparing(PatchSetApproval.Key::getParentKey, patchSetIdComparator())
-                    .thenComparing(PatchSetApproval.Key::getAccountId, intKeyOrdering())
-                    .thenComparing(PatchSetApproval.Key::getLabelId),
-                PatchSetApproval::getKey,
-                a -> a));
-  }
-
-  private static ImmutableSortedMap<PatchLineComment.Key, PatchLineComment> patchLineCommentMap(
-      Iterable<PatchLineComment> in) {
-    return Streams.stream(in)
-        .collect(
-            toImmutableSortedMap(
-                comparing(
-                        (PatchLineComment.Key k) -> k.getParentKey().getParentKey(),
-                        patchSetIdComparator())
-                    .thenComparing(PatchLineComment.Key::getParentKey)
-                    .thenComparing(PatchLineComment.Key::get),
-                PatchLineComment::getKey,
-                c -> c));
-  }
-
-  private static Comparator<PatchSet.Id> patchSetIdComparator() {
-    return comparing((PatchSet.Id id) -> id.getParentKey().get()).thenComparing(id -> id.get());
-  }
-
-  static {
-    // Initialization-time checks that the column set hasn't changed since the
-    // last time this file was updated.
-    checkColumns(Change.Id.class, 1);
-
-    checkColumns(
-        Change.class, 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 101);
-    checkColumns(ChangeMessage.Key.class, 1, 2);
-    checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6, 7);
-    checkColumns(PatchSet.Id.class, 1, 2);
-    checkColumns(PatchSet.class, 1, 2, 3, 4, 6, 8, 9);
-    checkColumns(PatchSetApproval.Key.class, 1, 2, 3);
-    checkColumns(PatchSetApproval.class, 1, 2, 3, 6, 7, 8);
-    checkColumns(PatchLineComment.Key.class, 1, 2);
-    checkColumns(PatchLineComment.class, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
-  }
-
-  private final Change change;
-  private final ImmutableList<ChangeMessage> changeMessages;
-  private final ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets;
-  private final ImmutableMap<PatchSetApproval.Key, PatchSetApproval> patchSetApprovals;
-  private final ImmutableMap<PatchLineComment.Key, PatchLineComment> patchLineComments;
-  private final ReviewerSet reviewers;
-  private final Source source;
-
-  public ChangeBundle(
-      Change change,
-      Iterable<ChangeMessage> changeMessages,
-      Iterable<PatchSet> patchSets,
-      Iterable<PatchSetApproval> patchSetApprovals,
-      Iterable<PatchLineComment> patchLineComments,
-      ReviewerSet reviewers,
-      Source source) {
-    this.change = requireNonNull(change);
-    this.changeMessages = changeMessageList(changeMessages);
-    this.patchSets = ImmutableSortedMap.copyOfSorted(patchSetMap(patchSets));
-    this.patchSetApprovals = ImmutableMap.copyOf(patchSetApprovalMap(patchSetApprovals));
-    this.patchLineComments = ImmutableMap.copyOf(patchLineCommentMap(patchLineComments));
-    this.reviewers = requireNonNull(reviewers);
-    this.source = requireNonNull(source);
-
-    for (ChangeMessage m : this.changeMessages) {
-      checkArgument(m.getKey().getParentKey().equals(change.getId()));
-    }
-    for (PatchSet.Id id : this.patchSets.keySet()) {
-      checkArgument(id.getParentKey().equals(change.getId()));
-    }
-    for (PatchSetApproval.Key k : this.patchSetApprovals.keySet()) {
-      checkArgument(k.getParentKey().getParentKey().equals(change.getId()));
-    }
-    for (PatchLineComment.Key k : this.patchLineComments.keySet()) {
-      checkArgument(k.getParentKey().getParentKey().getParentKey().equals(change.getId()));
-    }
-  }
-
-  public Change getChange() {
-    return change;
-  }
-
-  public ImmutableCollection<ChangeMessage> getChangeMessages() {
-    return changeMessages;
-  }
-
-  public ImmutableCollection<PatchSet> getPatchSets() {
-    return patchSets.values();
-  }
-
-  public ImmutableCollection<PatchSetApproval> getPatchSetApprovals() {
-    return patchSetApprovals.values();
-  }
-
-  public ImmutableCollection<PatchLineComment> getPatchLineComments() {
-    return patchLineComments.values();
-  }
-
-  public ReviewerSet getReviewers() {
-    return reviewers;
-  }
-
-  public Source getSource() {
-    return source;
-  }
-
-  public ImmutableList<String> differencesFrom(ChangeBundle o) {
-    List<String> diffs = new ArrayList<>();
-    diffChanges(diffs, this, o);
-    diffChangeMessages(diffs, this, o);
-    diffPatchSets(diffs, this, o);
-    diffPatchSetApprovals(diffs, this, o);
-    diffReviewers(diffs, this, o);
-    diffPatchLineComments(diffs, this, o);
-    return ImmutableList.copyOf(diffs);
-  }
-
-  private Timestamp getFirstPatchSetTime() {
-    if (patchSets.isEmpty()) {
-      return change.getCreatedOn();
-    }
-    return patchSets.firstEntry().getValue().getCreatedOn();
-  }
-
-  private Timestamp getLatestTimestamp() {
-    Ordering<Timestamp> o = Ordering.natural().nullsFirst();
-    Timestamp ts = null;
-    for (ChangeMessage cm : filterChangeMessages()) {
-      ts = o.max(ts, cm.getWrittenOn());
-    }
-    for (PatchSet ps : getPatchSets()) {
-      ts = o.max(ts, ps.getCreatedOn());
-    }
-    for (PatchSetApproval psa : filterPatchSetApprovals().values()) {
-      ts = o.max(ts, psa.getGranted());
-    }
-    for (PatchLineComment plc : filterPatchLineComments().values()) {
-      // Ignore draft comments, as they do not show up in the change meta graph.
-      if (plc.getStatus() != PatchLineComment.Status.DRAFT) {
-        ts = o.max(ts, plc.getWrittenOn());
-      }
-    }
-    return firstNonNull(ts, change.getLastUpdatedOn());
-  }
-
-  private Map<PatchSetApproval.Key, PatchSetApproval> filterPatchSetApprovals() {
-    return limitToValidPatchSets(patchSetApprovals, PatchSetApproval.Key::getParentKey);
-  }
-
-  private Map<PatchLineComment.Key, PatchLineComment> filterPatchLineComments() {
-    return limitToValidPatchSets(patchLineComments, k -> k.getParentKey().getParentKey());
-  }
-
-  private <K, V> Map<K, V> limitToValidPatchSets(Map<K, V> in, Function<K, PatchSet.Id> func) {
-    return Maps.filterKeys(in, Predicates.compose(validPatchSetPredicate(), func));
-  }
-
-  private Predicate<PatchSet.Id> validPatchSetPredicate() {
-    return patchSets::containsKey;
-  }
-
-  private Collection<ChangeMessage> filterChangeMessages() {
-    final Predicate<PatchSet.Id> validPatchSet = validPatchSetPredicate();
-    return Collections2.filter(
-        changeMessages,
-        m -> {
-          PatchSet.Id psId = m.getPatchSetId();
-          if (psId == null) {
-            return true;
-          }
-          return validPatchSet.apply(psId);
-        });
-  }
-
-  private static void diffChanges(List<String> diffs, ChangeBundle bundleA, ChangeBundle bundleB) {
-    Change a = bundleA.change;
-    Change b = bundleB.change;
-    String desc = a.getId().equals(b.getId()) ? describe(a.getId()) : "Changes";
-
-    boolean excludeCreatedOn = false;
-    boolean excludeCurrentPatchSetId = false;
-    boolean excludeTopic = false;
-    Timestamp aCreated = a.getCreatedOn();
-    Timestamp bCreated = b.getCreatedOn();
-    Timestamp aUpdated = a.getLastUpdatedOn();
-    Timestamp bUpdated = b.getLastUpdatedOn();
-
-    boolean excludeSubject = false;
-    boolean excludeOrigSubj = false;
-    // Subject is not technically a nullable field, but we observed some null
-    // subjects in the wild on googlesource.com, so treat null as empty.
-    String aSubj = Strings.nullToEmpty(a.getSubject());
-    String bSubj = Strings.nullToEmpty(b.getSubject());
-
-    // Allow created timestamp in NoteDb to be any of:
-    //  - The created timestamp of the change.
-    //  - The timestamp of the first remaining patch set.
-    //  - The last updated timestamp, if it is less than the created timestamp.
-    //
-    // Ignore subject if the NoteDb subject starts with the ReviewDb subject.
-    // The NoteDb subject is read directly from the commit, whereas the ReviewDb
-    // subject historically may have been truncated to fit in a SQL varchar
-    // column.
-    //
-    // Ignore original subject on the ReviewDb side when comparing to NoteDb.
-    // This field may have any number of values:
-    //  - It may be null, if the change has had no new patch sets pushed since
-    //    migrating to schema 103.
-    //  - It may match the first patch set subject, if the change was created
-    //    after migrating to schema 103.
-    //  - It may match the subject of the first patch set that was pushed after
-    //    the migration to schema 103, even though that is neither the subject
-    //    of the first patch set nor the subject of the last patch set. (See
-    //    Change#setCurrentPatchSet as of 43b10f86 for this behavior.) This
-    //    subject of an intermediate patch set is not available to the
-    //    ChangeBundle; we would have to get the subject from the repo, which is
-    //    inconvenient at this point.
-    //
-    // Ignore original subject on the ReviewDb side if it equals the subject of
-    // the current patch set.
-    //
-    // For all of the above subject comparisons, first trim any leading spaces
-    // from the NoteDb strings. (We actually do represent the leading spaces
-    // faithfully during conversion, but JGit's FooterLine parser trims them
-    // when reading.)
-    //
-    // Ignore empty topic on the ReviewDb side if it is null on the NoteDb side.
-    //
-    // Ignore currentPatchSetId on NoteDb side if ReviewDb does not point to a
-    // valid patch set.
-    //
-    // Use max timestamp of all ReviewDb entities when comparing with NoteDb.
-    if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
-      boolean createdOnMatchesFirstPs =
-          !timestampsDiffer(bundleA, bundleA.getFirstPatchSetTime(), bundleB, bCreated);
-      boolean createdOnMatchesLastUpdatedOn =
-          !timestampsDiffer(bundleA, aUpdated, bundleB, bCreated);
-      boolean createdAfterUpdated = aCreated.compareTo(aUpdated) > 0;
-      excludeCreatedOn =
-          createdOnMatchesFirstPs || (createdAfterUpdated && createdOnMatchesLastUpdatedOn);
-
-      aSubj = cleanReviewDbSubject(aSubj);
-      bSubj = cleanNoteDbSubject(bSubj);
-      excludeCurrentPatchSetId = !bundleA.validPatchSetPredicate().apply(a.currentPatchSetId());
-      excludeSubject = bSubj.startsWith(aSubj) || excludeCurrentPatchSetId;
-      excludeOrigSubj = true;
-      String aTopic = trimOrNull(a.getTopic());
-      excludeTopic =
-          Objects.equals(aTopic, b.getTopic()) || ("".equals(aTopic) && b.getTopic() == null);
-      aUpdated = bundleA.getLatestTimestamp();
-    } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
-      boolean createdOnMatchesFirstPs =
-          !timestampsDiffer(bundleA, aCreated, bundleB, bundleB.getFirstPatchSetTime());
-      boolean createdOnMatchesLastUpdatedOn =
-          !timestampsDiffer(bundleA, aCreated, bundleB, bUpdated);
-      boolean createdAfterUpdated = bCreated.compareTo(bUpdated) > 0;
-      excludeCreatedOn =
-          createdOnMatchesFirstPs || (createdAfterUpdated && createdOnMatchesLastUpdatedOn);
-
-      aSubj = cleanNoteDbSubject(aSubj);
-      bSubj = cleanReviewDbSubject(bSubj);
-      excludeCurrentPatchSetId = !bundleB.validPatchSetPredicate().apply(b.currentPatchSetId());
-      excludeSubject = aSubj.startsWith(bSubj) || excludeCurrentPatchSetId;
-      excludeOrigSubj = true;
-      String bTopic = trimOrNull(b.getTopic());
-      excludeTopic =
-          Objects.equals(bTopic, a.getTopic()) || (a.getTopic() == null && "".equals(bTopic));
-      bUpdated = bundleB.getLatestTimestamp();
-    }
-
-    String subjectField = "subject";
-    String updatedField = "lastUpdatedOn";
-    List<String> exclude =
-        Lists.newArrayList(subjectField, updatedField, "noteDbState", "rowVersion");
-    if (excludeCreatedOn) {
-      exclude.add("createdOn");
-    }
-    if (excludeCurrentPatchSetId) {
-      exclude.add("currentPatchSetId");
-    }
-    if (excludeOrigSubj) {
-      exclude.add("originalSubject");
-    }
-    if (excludeTopic) {
-      exclude.add("topic");
-    }
-    diffColumnsExcluding(diffs, Change.class, desc, bundleA, a, bundleB, b, exclude);
-
-    // Allow last updated timestamps to either be exactly equal (within slop),
-    // or the NoteDb timestamp to be equal to the latest entity timestamp in the
-    // whole ReviewDb bundle (within slop).
-    if (timestampsDiffer(bundleA, a.getLastUpdatedOn(), bundleB, b.getLastUpdatedOn())) {
-      diffTimestamps(
-          diffs, desc, bundleA, aUpdated, bundleB, bUpdated, "effective last updated time");
-    }
-    if (!excludeSubject) {
-      diffValues(diffs, desc, aSubj, bSubj, subjectField);
-    }
-  }
-
-  private static String trimOrNull(String s) {
-    return s != null ? CharMatcher.whitespace().trimFrom(s) : null;
-  }
-
-  private static String cleanReviewDbSubject(String s) {
-    s = CharMatcher.is(' ').trimLeadingFrom(s);
-
-    // An old JGit bug failed to extract subjects from commits with "\r\n"
-    // terminators: https://bugs.eclipse.org/bugs/show_bug.cgi?id=400707
-    // Changes created with this bug may have "\r\n" converted to "\r " and the
-    // entire commit in the subject. The version of JGit used to read NoteDb
-    // changes parses these subjects correctly, so we need to clean up old
-    // ReviewDb subjects before comparing.
-    int rn = s.indexOf("\r \r ");
-    if (rn >= 0) {
-      s = s.substring(0, rn);
-    }
-    return NoteDbUtil.sanitizeFooter(s);
-  }
-
-  private static String cleanNoteDbSubject(String s) {
-    return NoteDbUtil.sanitizeFooter(s);
-  }
-
-  /**
-   * Set of fields that must always exactly match between ReviewDb and NoteDb.
-   *
-   * <p>Used to limit the worst-case quadratic search when pairing off matching messages below.
-   */
-  @AutoValue
-  abstract static class ChangeMessageCandidate {
-    static ChangeMessageCandidate create(ChangeMessage cm) {
-      return new AutoValue_ChangeBundle_ChangeMessageCandidate(
-          cm.getAuthor(), cm.getMessage(), cm.getTag());
-    }
-
-    @Nullable
-    abstract Account.Id author();
-
-    @Nullable
-    abstract String message();
-
-    @Nullable
-    abstract String tag();
-
-    // Exclude:
-    //  - patch set, which may be null on ReviewDb side but not NoteDb
-    //  - UUID, which is always different between ReviewDb and NoteDb
-    //  - writtenOn, which is fuzzy
-  }
-
-  private static void diffChangeMessages(
-      List<String> diffs, ChangeBundle bundleA, ChangeBundle bundleB) {
-    if (bundleA.source == REVIEW_DB && bundleB.source == REVIEW_DB) {
-      // Both came from ReviewDb: check all fields exactly.
-      Map<ChangeMessage.Key, ChangeMessage> as = changeMessageMap(bundleA.filterChangeMessages());
-      Map<ChangeMessage.Key, ChangeMessage> bs = changeMessageMap(bundleB.filterChangeMessages());
-
-      for (ChangeMessage.Key k : diffKeySets(diffs, as, bs)) {
-        ChangeMessage a = as.get(k);
-        ChangeMessage b = bs.get(k);
-        String desc = describe(k);
-        diffColumns(diffs, ChangeMessage.class, desc, bundleA, a, bundleB, b);
-      }
-      return;
-    }
-    Change.Id id = bundleA.getChange().getId();
-    checkArgument(id.equals(bundleB.getChange().getId()));
-
-    // Try to pair up matching ChangeMessages from each side, and succeed only
-    // if both collections are empty at the end. Quadratic in the worst case,
-    // but easy to reason about.
-    List<ChangeMessage> as = new LinkedList<>(bundleA.filterChangeMessages());
-
-    ListMultimap<ChangeMessageCandidate, ChangeMessage> bs = LinkedListMultimap.create();
-    for (ChangeMessage b : bundleB.filterChangeMessages()) {
-      bs.put(ChangeMessageCandidate.create(b), b);
-    }
-
-    Iterator<ChangeMessage> ait = as.iterator();
-    A:
-    while (ait.hasNext()) {
-      ChangeMessage a = ait.next();
-      Iterator<ChangeMessage> bit = bs.get(ChangeMessageCandidate.create(a)).iterator();
-      while (bit.hasNext()) {
-        ChangeMessage b = bit.next();
-        if (changeMessagesMatch(bundleA, a, bundleB, b)) {
-          ait.remove();
-          bit.remove();
-          continue A;
-        }
-      }
-    }
-
-    if (as.isEmpty() && bs.isEmpty()) {
-      return;
-    }
-    StringBuilder sb =
-        new StringBuilder("ChangeMessages differ for Change.Id ").append(id).append('\n');
-    if (!as.isEmpty()) {
-      sb.append("Only in A:");
-      for (ChangeMessage cm : as) {
-        sb.append("\n  ").append(cm);
-      }
-      if (!bs.isEmpty()) {
-        sb.append('\n');
-      }
-    }
-    if (!bs.isEmpty()) {
-      sb.append("Only in B:");
-      bs.values()
-          .stream()
-          .sorted(CHANGE_MESSAGE_COMPARATOR)
-          .forEach(cm -> sb.append("\n  ").append(cm));
-    }
-    diffs.add(sb.toString());
-  }
-
-  private static boolean changeMessagesMatch(
-      ChangeBundle bundleA, ChangeMessage a, ChangeBundle bundleB, ChangeMessage b) {
-    List<String> tempDiffs = new ArrayList<>();
-    String temp = "temp";
-
-    // ReviewDb allows timestamps before patch set was created, but NoteDb
-    // truncates this to the patch set creation timestamp.
-    Timestamp ta = a.getWrittenOn();
-    Timestamp tb = b.getWrittenOn();
-    PatchSet psa = bundleA.patchSets.get(a.getPatchSetId());
-    PatchSet psb = bundleB.patchSets.get(b.getPatchSetId());
-    boolean excludePatchSet = false;
-    boolean excludeWrittenOn = false;
-    if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
-      excludePatchSet = a.getPatchSetId() == null;
-      excludeWrittenOn =
-          psa != null
-              && psb != null
-              && ta.before(psa.getCreatedOn())
-              && tb.equals(psb.getCreatedOn());
-    } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
-      excludePatchSet = b.getPatchSetId() == null;
-      excludeWrittenOn =
-          psa != null
-              && psb != null
-              && tb.before(psb.getCreatedOn())
-              && ta.equals(psa.getCreatedOn());
-    }
-
-    List<String> exclude = Lists.newArrayList("key");
-    if (excludePatchSet) {
-      exclude.add("patchset");
-    }
-    if (excludeWrittenOn) {
-      exclude.add("writtenOn");
-    }
-
-    diffColumnsExcluding(tempDiffs, ChangeMessage.class, temp, bundleA, a, bundleB, b, exclude);
-    return tempDiffs.isEmpty();
-  }
-
-  private static void diffPatchSets(
-      List<String> diffs, ChangeBundle bundleA, ChangeBundle bundleB) {
-    Map<PatchSet.Id, PatchSet> as = bundleA.patchSets;
-    Map<PatchSet.Id, PatchSet> bs = bundleB.patchSets;
-    Optional<PatchSet.Id> minA = as.keySet().stream().min(intKeyOrdering());
-    Optional<PatchSet.Id> minB = bs.keySet().stream().min(intKeyOrdering());
-    Set<PatchSet.Id> ids = diffKeySets(diffs, as, bs);
-
-    // Old versions of Gerrit had a bug that created patch sets during
-    // rebase or submission with a createdOn timestamp earlier than the patch
-    // set it was replacing. (In the cases I examined, it was equal to createdOn
-    // for the change, but we're not counting on this exact behavior.)
-    //
-    // ChangeRebuilder ensures patch set events come out in order, but it's hard
-    // to predict what the resulting timestamps would look like. So, completely
-    // ignore the createdOn timestamps if both:
-    //   * ReviewDb timestamps are non-monotonic.
-    //   * NoteDb timestamps are monotonic.
-    //
-    // Allow the timestamp of the first patch set to match the creation time of
-    // the change.
-    boolean excludeAllCreatedOn = false;
-    if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
-      excludeAllCreatedOn = !createdOnIsMonotonic(as, ids) && createdOnIsMonotonic(bs, ids);
-    } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
-      excludeAllCreatedOn = createdOnIsMonotonic(as, ids) && !createdOnIsMonotonic(bs, ids);
-    }
-
-    for (PatchSet.Id id : ids) {
-      PatchSet a = as.get(id);
-      PatchSet b = bs.get(id);
-      String desc = describe(id);
-      String pushCertField = "pushCertificate";
-
-      boolean excludeCreatedOn = excludeAllCreatedOn;
-      boolean excludeDesc = false;
-      if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
-        excludeDesc = Objects.equals(trimOrNull(a.getDescription()), b.getDescription());
-        excludeCreatedOn |=
-            Optional.of(id).equals(minB) && b.getCreatedOn().equals(bundleB.change.getCreatedOn());
-      } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
-        excludeDesc = Objects.equals(a.getDescription(), trimOrNull(b.getDescription()));
-        excludeCreatedOn |=
-            Optional.of(id).equals(minA) && a.getCreatedOn().equals(bundleA.change.getCreatedOn());
-      }
-
-      List<String> exclude = Lists.newArrayList(pushCertField);
-      if (excludeCreatedOn) {
-        exclude.add("createdOn");
-      }
-      if (excludeDesc) {
-        exclude.add("description");
-      }
-
-      diffColumnsExcluding(diffs, PatchSet.class, desc, bundleA, a, bundleB, b, exclude);
-      diffValues(diffs, desc, trimPushCert(a), trimPushCert(b), pushCertField);
-    }
-  }
-
-  private static String trimPushCert(PatchSet ps) {
-    if (ps.getPushCertificate() == null) {
-      return null;
-    }
-    return CharMatcher.is('\n').trimTrailingFrom(ps.getPushCertificate());
-  }
-
-  private static boolean createdOnIsMonotonic(
-      Map<?, PatchSet> patchSets, Set<PatchSet.Id> limitToIds) {
-    List<PatchSet> orderedById =
-        patchSets
-            .values()
-            .stream()
-            .filter(ps -> limitToIds.contains(ps.getId()))
-            .sorted(ChangeUtil.PS_ID_ORDER)
-            .collect(toList());
-    return Ordering.natural().onResultOf(PatchSet::getCreatedOn).isOrdered(orderedById);
-  }
-
-  private static void diffPatchSetApprovals(
-      List<String> diffs, ChangeBundle bundleA, ChangeBundle bundleB) {
-    Map<PatchSetApproval.Key, PatchSetApproval> as = bundleA.filterPatchSetApprovals();
-    Map<PatchSetApproval.Key, PatchSetApproval> bs = bundleB.filterPatchSetApprovals();
-    for (PatchSetApproval.Key k : diffKeySets(diffs, as, bs)) {
-      PatchSetApproval a = as.get(k);
-      PatchSetApproval b = bs.get(k);
-      String desc = describe(k);
-
-      // ReviewDb allows timestamps before patch set was created, but NoteDb
-      // truncates this to the patch set creation timestamp.
-      //
-      // ChangeRebuilder ensures all post-submit approvals happen after the
-      // actual submit, so the timestamps may not line up. This shouldn't really
-      // happen, because postSubmit shouldn't be set in ReviewDb until after the
-      // change is submitted in ReviewDb, but you never know.
-      //
-      // Due to a quirk of PostReview, post-submit 0 votes might not have the
-      // postSubmit bit set in ReviewDb. As these are only used for tombstone
-      // purposes, ignore the postSubmit bit in NoteDb in this case.
-      Timestamp ta = a.getGranted();
-      Timestamp tb = b.getGranted();
-      PatchSet psa = requireNonNull(bundleA.patchSets.get(a.getPatchSetId()));
-      PatchSet psb = requireNonNull(bundleB.patchSets.get(b.getPatchSetId()));
-      boolean excludeGranted = false;
-      boolean excludePostSubmit = false;
-      List<String> exclude = new ArrayList<>(1);
-      if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
-        excludeGranted =
-            (ta.before(psa.getCreatedOn()) && tb.equals(psb.getCreatedOn()))
-                || ta.compareTo(tb) < 0;
-        excludePostSubmit = a.getValue() == 0 && b.isPostSubmit();
-      } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
-        excludeGranted =
-            (tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn()))
-                || (tb.compareTo(ta) < 0);
-        excludePostSubmit = b.getValue() == 0 && a.isPostSubmit();
-      }
-
-      // Legacy submit approvals may or may not have tags associated with them,
-      // depending on whether ChangeRebuilder happened to group them with the
-      // status change.
-      boolean excludeTag =
-          bundleA.source != bundleB.source && a.isLegacySubmit() && b.isLegacySubmit();
-
-      if (excludeGranted) {
-        exclude.add("granted");
-      }
-      if (excludePostSubmit) {
-        exclude.add("postSubmit");
-      }
-      if (excludeTag) {
-        exclude.add("tag");
-      }
-
-      diffColumnsExcluding(diffs, PatchSetApproval.class, desc, bundleA, a, bundleB, b, exclude);
-    }
-  }
-
-  private static void diffReviewers(
-      List<String> diffs, ChangeBundle bundleA, ChangeBundle bundleB) {
-    diffSets(diffs, bundleA.reviewers.all(), bundleB.reviewers.all(), "reviewer");
-  }
-
-  private static void diffPatchLineComments(
-      List<String> diffs, ChangeBundle bundleA, ChangeBundle bundleB) {
-    Map<PatchLineComment.Key, PatchLineComment> as = bundleA.filterPatchLineComments();
-    Map<PatchLineComment.Key, PatchLineComment> bs = bundleB.filterPatchLineComments();
-    for (PatchLineComment.Key k : diffKeySets(diffs, as, bs)) {
-      PatchLineComment a = as.get(k);
-      PatchLineComment b = bs.get(k);
-      String desc = describe(k);
-      diffColumns(diffs, PatchLineComment.class, desc, bundleA, a, bundleB, b);
-    }
-  }
-
-  private static <T> Set<T> diffKeySets(List<String> diffs, Map<T, ?> a, Map<T, ?> b) {
-    if (a.isEmpty() && b.isEmpty()) {
-      return a.keySet();
-    }
-    String clazz = keyClass((!a.isEmpty() ? a.keySet() : b.keySet()).iterator().next());
-    return diffSets(diffs, a.keySet(), b.keySet(), clazz);
-  }
-
-  private static <T> Set<T> diffSets(List<String> diffs, Set<T> as, Set<T> bs, String desc) {
-    if (as.isEmpty() && bs.isEmpty()) {
-      return as;
-    }
-
-    Set<T> aNotB = Sets.difference(as, bs);
-    Set<T> bNotA = Sets.difference(bs, as);
-    if (aNotB.isEmpty() && bNotA.isEmpty()) {
-      return as;
-    }
-    diffs.add(desc + " sets differ: " + aNotB + " only in A; " + bNotA + " only in B");
-    return Sets.intersection(as, bs);
-  }
-
-  private static <T> void diffColumns(
-      List<String> diffs,
-      Class<T> clazz,
-      String desc,
-      ChangeBundle bundleA,
-      T a,
-      ChangeBundle bundleB,
-      T b) {
-    diffColumnsExcluding(diffs, clazz, desc, bundleA, a, bundleB, b);
-  }
-
-  private static <T> void diffColumnsExcluding(
-      List<String> diffs,
-      Class<T> clazz,
-      String desc,
-      ChangeBundle bundleA,
-      T a,
-      ChangeBundle bundleB,
-      T b,
-      String... exclude) {
-    diffColumnsExcluding(diffs, clazz, desc, bundleA, a, bundleB, b, Arrays.asList(exclude));
-  }
-
-  private static <T> void diffColumnsExcluding(
-      List<String> diffs,
-      Class<T> clazz,
-      String desc,
-      ChangeBundle bundleA,
-      T a,
-      ChangeBundle bundleB,
-      T b,
-      Iterable<String> exclude) {
-    Set<String> toExclude = Sets.newLinkedHashSet(exclude);
-    for (Field f : clazz.getDeclaredFields()) {
-      Column col = f.getAnnotation(Column.class);
-      if (col == null) {
-        continue;
-      } else if (toExclude.remove(f.getName())) {
-        continue;
-      }
-      f.setAccessible(true);
-      try {
-        if (Timestamp.class.isAssignableFrom(f.getType())) {
-          diffTimestamps(diffs, desc, bundleA, a, bundleB, b, f.getName());
-        } else {
-          diffValues(diffs, desc, f.get(a), f.get(b), f.getName());
-        }
-      } catch (IllegalAccessException e) {
-        throw new IllegalArgumentException(e);
-      }
-    }
-    checkArgument(
-        toExclude.isEmpty(),
-        "requested columns to exclude not present in %s: %s",
-        clazz.getSimpleName(),
-        toExclude);
-  }
-
-  private static void diffTimestamps(
-      List<String> diffs,
-      String desc,
-      ChangeBundle bundleA,
-      Object a,
-      ChangeBundle bundleB,
-      Object b,
-      String field) {
-    checkArgument(a.getClass() == b.getClass());
-    Class<?> clazz = a.getClass();
-
-    Timestamp ta;
-    Timestamp tb;
-    try {
-      Field f = clazz.getDeclaredField(field);
-      checkArgument(f.getAnnotation(Column.class) != null);
-      f.setAccessible(true);
-      ta = (Timestamp) f.get(a);
-      tb = (Timestamp) f.get(b);
-    } catch (IllegalAccessException | NoSuchFieldException | SecurityException e) {
-      throw new IllegalArgumentException(e);
-    }
-    diffTimestamps(diffs, desc, bundleA, ta, bundleB, tb, field);
-  }
-
-  private static void diffTimestamps(
-      List<String> diffs,
-      String desc,
-      ChangeBundle bundleA,
-      Timestamp ta,
-      ChangeBundle bundleB,
-      Timestamp tb,
-      String fieldDesc) {
-    if (bundleA.source == bundleB.source || ta == null || tb == null) {
-      diffValues(diffs, desc, ta, tb, fieldDesc);
-    } else if (bundleA.source == NOTE_DB) {
-      diffTimestamps(diffs, desc, bundleA.getChange(), ta, bundleB.getChange(), tb, fieldDesc);
-    } else {
-      diffTimestamps(diffs, desc, bundleB.getChange(), tb, bundleA.getChange(), ta, fieldDesc);
-    }
-  }
-
-  private static boolean timestampsDiffer(
-      ChangeBundle bundleA, Timestamp ta, ChangeBundle bundleB, Timestamp tb) {
-    List<String> tempDiffs = new ArrayList<>(1);
-    diffTimestamps(tempDiffs, "temp", bundleA, ta, bundleB, tb, "temp");
-    return !tempDiffs.isEmpty();
-  }
-
-  private static void diffTimestamps(
-      List<String> diffs,
-      String desc,
-      Change changeFromNoteDb,
-      Timestamp tsFromNoteDb,
-      Change changeFromReviewDb,
-      Timestamp tsFromReviewDb,
-      String field) {
-    // Because ChangeRebuilder may batch events together that are several
-    // seconds apart, the timestamp in NoteDb may actually be several seconds
-    // *earlier* than the timestamp in ReviewDb that it was converted from.
-    checkArgument(
-        tsFromNoteDb.equals(truncateToSecond(tsFromNoteDb)),
-        "%s from NoteDb has non-rounded %s timestamp: %s",
-        desc,
-        field,
-        tsFromNoteDb);
-
-    if (tsFromReviewDb.before(changeFromReviewDb.getCreatedOn())
-        && tsFromNoteDb.equals(changeFromNoteDb.getCreatedOn())) {
-      // Timestamp predates change creation. These are truncated to change
-      // creation time during NoteDb conversion, so allow this if the timestamp
-      // in NoteDb matches the createdOn time in NoteDb.
-      return;
-    }
-
-    long delta = tsFromReviewDb.getTime() - tsFromNoteDb.getTime();
-    long max = ChangeRebuilderImpl.MAX_WINDOW_MS;
-    if (delta < 0 || delta > max) {
-      diffs.add(
-          field
-              + " differs for "
-              + desc
-              + " in NoteDb vs. ReviewDb:"
-              + " {"
-              + tsFromNoteDb
-              + "} != {"
-              + tsFromReviewDb
-              + "}");
-    }
-  }
-
-  private static void diffValues(
-      List<String> diffs, String desc, Object va, Object vb, String name) {
-    if (!Objects.equals(va, vb)) {
-      diffs.add(name + " differs for " + desc + ": {" + va + "} != {" + vb + "}");
-    }
-  }
-
-  private static String describe(Object key) {
-    return keyClass(key) + " " + key;
-  }
-
-  private static String keyClass(Object obj) {
-    Class<?> clazz = obj.getClass();
-    String name = clazz.getSimpleName();
-    checkArgument(name.endsWith("Key") || name.endsWith("Id"), "not an Id/Key class: %s", name);
-    if (name.equals("Key") || name.equals("Id")) {
-      return clazz.getEnclosingClass().getSimpleName() + "." + name;
-    } else if (name.startsWith("AutoValue_")) {
-      return name.substring(name.lastIndexOf('_') + 1);
-    }
-    return name;
-  }
-
-  @Override
-  public String toString() {
-    return getClass().getSimpleName()
-        + "{id="
-        + change.getId()
-        + ", ChangeMessage["
-        + changeMessages.size()
-        + "]"
-        + ", PatchSet["
-        + patchSets.size()
-        + "]"
-        + ", PatchSetApproval["
-        + patchSetApprovals.size()
-        + "]"
-        + ", PatchLineComment["
-        + patchLineComments.size()
-        + "]"
-        + "}";
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/ChangeBundleReader.java b/java/com/google/gerrit/server/notedb/ChangeBundleReader.java
deleted file mode 100644
index 3207c3b..0000000
--- a/java/com/google/gerrit/server/notedb/ChangeBundleReader.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2016 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.notedb;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmException;
-
-public interface ChangeBundleReader {
-  @Nullable
-  ChangeBundle fromReviewDb(ReviewDb db, Change.Id id) throws OrmException;
-}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index d2942dc..e5e0d51 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
-import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
 import static java.util.Comparator.comparing;
 import static java.util.Objects.requireNonNull;
 
@@ -28,18 +27,15 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.ImmutableSortedSet;
-import com.google.common.collect.Iterators;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.MultimapBuilder;
 import com.google.common.collect.Multimaps;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Sets.SetView;
-import com.google.common.collect.Streams;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.metrics.Timer1;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
@@ -57,9 +53,7 @@
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.ReviewerStatusUpdate;
 import com.google.gerrit.server.git.RefCache;
-import com.google.gerrit.server.git.RepoRefCache;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -76,8 +70,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -186,12 +178,6 @@
       return new ChangeNotes(args, loadChangeFromDb(db, project, changeId)).load();
     }
 
-    public ChangeNotes createWithAutoRebuildingDisabled(
-        ReviewDb db, Project.NameKey project, Change.Id changeId) throws OrmException {
-      return new ChangeNotes(args, loadChangeFromDb(db, project, changeId), true, false, null)
-          .load();
-    }
-
     /**
      * Create change notes for a change that was loaded from index. This method should only be used
      * when database access is harmful and potentially stale data from the index is acceptable.
@@ -205,12 +191,11 @@
 
     public ChangeNotes createForBatchUpdate(Change change, boolean shouldExist)
         throws OrmException {
-      return new ChangeNotes(args, change, shouldExist, false, null).load();
+      return new ChangeNotes(args, change, shouldExist, null).load();
     }
 
-    public ChangeNotes createWithAutoRebuildingDisabled(Change change, RefCache refs)
-        throws OrmException {
-      return new ChangeNotes(args, change, true, false, refs).load();
+    public ChangeNotes create(Change change, RefCache refs) throws OrmException {
+      return new ChangeNotes(args, change, true, refs).load();
     }
 
     // TODO(ekempin): Remove when database backend is deleted
@@ -285,7 +270,7 @@
       if (args.migration.readChanges()) {
         for (Project.NameKey project : projectCache.all()) {
           try (Repository repo = args.repoManager.openRepository(project)) {
-            scanNoteDb(repo, db, project)
+            scan(repo, project)
                 .filter(r -> !r.error().isPresent())
                 .map(ChangeNotesResult::notes)
                 .filter(predicate)
@@ -303,79 +288,23 @@
       return ImmutableListMultimap.copyOf(m);
     }
 
-    public Stream<ChangeNotesResult> scan(Repository repo, ReviewDb db, Project.NameKey project)
+    public Stream<ChangeNotesResult> scan(Repository repo, Project.NameKey project)
         throws IOException {
-      return args.migration.readChanges() ? scanNoteDb(repo, db, project) : scanReviewDb(repo, db);
-    }
-
-    private Stream<ChangeNotesResult> scanReviewDb(Repository repo, ReviewDb db)
-        throws IOException {
-      // Scan IDs that might exist in ReviewDb, assuming that each change has at least one patch set
-      // ref. Not all changes might exist: some patch set refs might have been written where the
-      // corresponding ReviewDb write failed. These will be silently filtered out by the batch get
-      // call below, which is intended.
-      Set<Change.Id> ids = scanChangeIds(repo).fromPatchSetRefs();
-
-      // A batch size of N may overload get(Iterable), so use something smaller, but still >1.
-      return Streams.stream(Iterators.partition(ids.iterator(), 30))
-          .flatMap(
-              batch -> {
-                try {
-                  return Streams.stream(ReviewDbUtil.unwrapDb(db).changes().get(batch))
-                      .map(this::toResult)
-                      .filter(Objects::nonNull);
-                } catch (OrmException e) {
-                  // Return this error for each Id in the input batch.
-                  return batch.stream().map(id -> ChangeNotesResult.error(id, e));
-                }
-              });
-    }
-
-    private Stream<ChangeNotesResult> scanNoteDb(
-        Repository repo, ReviewDb db, Project.NameKey project) throws IOException {
       ScanResult sr = scanChangeIds(repo);
-      PrimaryStorage defaultStorage = args.migration.changePrimaryStorage();
 
-      return sr.all()
-          .stream()
-          .map(id -> scanOneNoteDbChange(db, project, sr, defaultStorage, id))
-          .filter(Objects::nonNull);
+      return sr.all().stream().map(id -> scanOneChange(project, sr, id)).filter(Objects::nonNull);
     }
 
-    private ChangeNotesResult scanOneNoteDbChange(
-        ReviewDb db,
-        Project.NameKey project,
-        ScanResult sr,
-        PrimaryStorage defaultStorage,
-        Change.Id id) {
-      Change change;
-      try {
-        change = readOneReviewDbChange(db, id);
-      } catch (OrmException e) {
-        return ChangeNotesResult.error(id, e);
-      }
-
-      if (change == null) {
-        if (!sr.fromMetaRefs().contains(id)) {
-          // Stray patch set refs can happen due to normal error conditions, e.g. failed
-          // push processing, so aren't worth even a warning.
-          return null;
-        }
-        if (defaultStorage == PrimaryStorage.REVIEW_DB) {
-          // If changes should exist in ReviewDb, it's worth warning about a meta ref with
-          // no corresponding ReviewDb data.
-          logger.atWarning().log(
-              "skipping change %s found in project %s but not in ReviewDb", id, project);
-          return null;
-        }
-        // TODO(dborowitz): See discussion in NoteDbBatchUpdate#newChangeContext.
-        change = ChangeNotes.Factory.newNoteDbOnlyChange(project, id);
-      } else if (!change.getProject().equals(project)) {
-        logger.atSevere().log(
-            "skipping change %s found in project %s because ReviewDb change has project %s",
-            id, project, change.getProject());
+    private ChangeNotesResult scanOneChange(Project.NameKey project, ScanResult sr, Change.Id id) {
+      if (!sr.fromMetaRefs().contains(id)) {
+        // Stray patch set refs can happen due to normal error conditions, e.g. failed
+        // push processing, so aren't worth even a warning.
         return null;
       }
+
+      // TODO(dborowitz): See discussion in NoteDbBatchUpdate#newChangeContext.
+      Change change = ChangeNotes.Factory.newNoteDbOnlyChange(project, id);
+
       logger.atFine().log("adding change %s found in project %s", id, project);
       return toResult(change);
     }
@@ -391,7 +320,7 @@
       return ChangeNotesResult.notes(n);
     }
 
-    /** Result of {@link #scan(Repository, ReviewDb, Project.NameKey)}. */
+    /** Result of {@link #scan(Repository,Project.NameKey)}. */
     @AutoValue
     public abstract static class ChangeNotesResult {
       static ChangeNotesResult error(Change.Id id, OrmException e) {
@@ -459,7 +388,6 @@
   // notes easier.
   RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
 
-  private NoteDbUpdateManager.Result rebuildResult;
   private DraftCommentNotes draftCommentNotes;
   private RobotCommentNotes robotCommentNotes;
 
@@ -471,12 +399,11 @@
 
   @VisibleForTesting
   public ChangeNotes(Args args, Change change) {
-    this(args, change, true, true, null);
+    this(args, change, true, null);
   }
 
-  private ChangeNotes(
-      Args args, Change change, boolean shouldExist, boolean autoRebuild, @Nullable RefCache refs) {
-    super(args, change.getId(), PrimaryStorage.of(change), autoRebuild);
+  private ChangeNotes(Args args, Change change, boolean shouldExist, @Nullable RefCache refs) {
+    super(args, change.getId());
     this.change = new Change(change);
     this.shouldExist = shouldExist;
     this.refs = refs;
@@ -609,8 +536,7 @@
    */
   private void loadDraftComments(Account.Id author, @Nullable Ref ref) throws OrmException {
     if (draftCommentNotes == null || !author.equals(draftCommentNotes.getAuthor()) || ref != null) {
-      draftCommentNotes =
-          new DraftCommentNotes(args, change, author, autoRebuild, rebuildResult, ref);
+      draftCommentNotes = new DraftCommentNotes(args, getChangeId(), author, ref);
       draftCommentNotes.load();
     }
   }
@@ -665,8 +591,7 @@
   }
 
   @Override
-  protected void onLoad(LoadHandle handle)
-      throws NoSuchChangeException, IOException, ConfigInvalidException {
+  protected void onLoad(LoadHandle handle) throws NoSuchChangeException, IOException {
     ObjectId rev = handle.id();
     if (rev == null) {
       if (args.migration.readChanges()
@@ -699,89 +624,4 @@
   protected ObjectId readRef(Repository repo) throws IOException {
     return refs != null ? refs.get(getRefName()).orElse(null) : super.readRef(repo);
   }
-
-  @Override
-  protected LoadHandle openHandle(Repository repo) throws NoSuchChangeException, IOException {
-    if (autoRebuild) {
-      NoteDbChangeState state = NoteDbChangeState.parse(change);
-      if (args.migration.disableChangeReviewDb()) {
-        checkState(
-            state != null,
-            "shouldn't have null NoteDbChangeState when ReviewDb disabled: %s",
-            change);
-      }
-      ObjectId id = readRef(repo);
-      if (id == null) {
-        // Meta ref doesn't exist in NoteDb.
-
-        if (state == null) {
-          // Either ReviewDb change is being newly created, or it exists in ReviewDb but has not yet
-          // been rebuilt for the first time, e.g. because we just turned on write-only mode. In
-          // both cases, we don't want to auto-rebuild, just proceed with an empty ChangeNotes.
-          return super.openHandle(repo, id);
-        } else if (shouldExist && state.getPrimaryStorage() == PrimaryStorage.NOTE_DB) {
-          throw new NoSuchChangeException(getChangeId());
-        }
-
-        // ReviewDb claims NoteDb state exists, but meta ref isn't present: fall through and
-        // auto-rebuild if necessary.
-      }
-      RefCache refs = this.refs != null ? this.refs : new RepoRefCache(repo);
-      if (!NoteDbChangeState.isChangeUpToDate(state, refs, getChangeId())) {
-        return rebuildAndOpen(repo, id);
-      }
-    }
-    return super.openHandle(repo);
-  }
-
-  private LoadHandle rebuildAndOpen(Repository repo, ObjectId oldId) throws IOException {
-    Timer1.Context timer = args.metrics.autoRebuildLatency.start(CHANGES);
-    try {
-      Change.Id cid = getChangeId();
-      ReviewDb db = args.db.get();
-      ChangeRebuilder rebuilder = args.rebuilder.get();
-      NoteDbUpdateManager.Result r;
-      try (NoteDbUpdateManager manager = rebuilder.stage(db, cid)) {
-        if (manager == null) {
-          return super.openHandle(repo, oldId); // May be null in tests.
-        }
-        manager.setRefLogMessage("Auto-rebuilding change");
-        r = manager.stageAndApplyDelta(change);
-        try {
-          rebuilder.execute(db, cid, manager);
-          repo.scanForRepoChanges();
-        } catch (OrmException | IOException e) {
-          // Rebuilding failed. Most likely cause is contention on one or more
-          // change refs; there are other types of errors that can happen during
-          // rebuilding, but generally speaking they should happen during stage(),
-          // not execute(). Assume that some other worker is going to successfully
-          // store the rebuilt state, which is deterministic given an input
-          // ChangeBundle.
-          //
-          // Parse notes from the staged result so we can return something useful
-          // to the caller instead of throwing.
-          logger.atFine().log("Rebuilding change %s failed: %s", getChangeId(), e.getMessage());
-          args.metrics.autoRebuildFailureCount.increment(CHANGES);
-          rebuildResult = requireNonNull(r);
-          requireNonNull(r.newState());
-          requireNonNull(r.staged());
-          requireNonNull(r.staged().changeObjects());
-          return LoadHandle.create(
-              ChangeNotesCommit.newStagedRevWalk(repo, r.staged().changeObjects()),
-              r.newState().getChangeMetaId());
-        }
-      }
-      return LoadHandle.create(ChangeNotesCommit.newRevWalk(repo), r.newState().getChangeMetaId());
-    } catch (NoSuchChangeException e) {
-      return super.openHandle(repo, oldId);
-    } catch (OrmException e) {
-      throw new IOException(e);
-    } finally {
-      logger.atFine().log(
-          "Rebuilt change %s in project %s in %s ms",
-          getChangeId(),
-          getProjectName(),
-          TimeUnit.MILLISECONDS.convert(timer.stop(), TimeUnit.NANOSECONDS));
-    }
-  }
 }
diff --git a/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java b/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java
index e122754..8720c36 100644
--- a/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java
+++ b/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.UsedAt;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.GerritServerIdProvider;
 import com.google.inject.Inject;
@@ -50,6 +51,7 @@
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.util.MutableInteger;
 
+@UsedAt(UsedAt.Project.GOOGLE)
 @Singleton
 public class CommentJsonMigrator {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
index 8acc8d4..2523c2c 100644
--- a/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
+++ b/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
-import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -25,24 +24,15 @@
 import com.google.common.collect.MultimapBuilder;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.metrics.Timer1;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Comment;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.git.RepoRefCache;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager.StagedResult;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 import java.io.IOException;
-import java.util.concurrent.TimeUnit;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
@@ -50,53 +40,29 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.notes.NoteMap;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.ReceiveCommand;
 
 /** View of the draft comments for a single {@link Change} based on the log of its drafts branch. */
 public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   public interface Factory {
-    DraftCommentNotes create(Change change, Account.Id accountId);
-
-    DraftCommentNotes createWithAutoRebuildingDisabled(Change.Id changeId, Account.Id accountId);
+    DraftCommentNotes create(Change.Id changeId, Account.Id accountId);
   }
 
-  private final Change change;
   private final Account.Id author;
-  private final NoteDbUpdateManager.Result rebuildResult;
   private final Ref ref;
 
   private ImmutableListMultimap<RevId, Comment> comments;
   private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
 
   @AssistedInject
-  DraftCommentNotes(Args args, @Assisted Change change, @Assisted Account.Id author) {
-    this(args, change, author, true, null, null);
-  }
-
-  @AssistedInject
   DraftCommentNotes(Args args, @Assisted Change.Id changeId, @Assisted Account.Id author) {
-    // PrimaryStorage is unknown; this should only called by
-    // PatchLineCommentsUtil#draftByAuthor, which can live with this.
-    super(args, changeId, null, false);
-    this.change = null;
-    this.author = author;
-    this.rebuildResult = null;
-    this.ref = null;
+    this(args, changeId, author, null);
   }
 
-  DraftCommentNotes(
-      Args args,
-      Change change,
-      Account.Id author,
-      boolean autoRebuild,
-      @Nullable NoteDbUpdateManager.Result rebuildResult,
-      @Nullable Ref ref) {
-    super(args, change.getId(), PrimaryStorage.of(change), autoRebuild);
-    this.change = change;
-    this.author = author;
-    this.rebuildResult = rebuildResult;
+  DraftCommentNotes(Args args, Change.Id changeId, Account.Id author, @Nullable Ref ref) {
+    super(args, changeId);
+    this.author = requireNonNull(author);
     this.ref = ref;
     if (ref != null) {
       checkArgument(
@@ -181,79 +147,6 @@
     return args.allUsers;
   }
 
-  @Override
-  protected LoadHandle openHandle(Repository repo) throws NoSuchChangeException, IOException {
-    if (rebuildResult != null) {
-      StagedResult sr = requireNonNull(rebuildResult.staged());
-      return LoadHandle.create(
-          ChangeNotesCommit.newStagedRevWalk(repo, sr.allUsersObjects()),
-          findNewId(sr.allUsersCommands(), getRefName()));
-    } else if (change != null && autoRebuild) {
-      NoteDbChangeState state = NoteDbChangeState.parse(change);
-      // Only check if this particular user's drafts are up to date, to avoid
-      // reading unnecessary refs.
-      if (!NoteDbChangeState.areDraftsUpToDate(
-          state, new RepoRefCache(repo), getChangeId(), author)) {
-        return rebuildAndOpen(repo);
-      }
-    }
-    return super.openHandle(repo);
-  }
-
-  private static ObjectId findNewId(Iterable<ReceiveCommand> cmds, String refName) {
-    for (ReceiveCommand cmd : cmds) {
-      if (cmd.getRefName().equals(refName)) {
-        return cmd.getNewId();
-      }
-    }
-    return null;
-  }
-
-  private LoadHandle rebuildAndOpen(Repository repo) throws NoSuchChangeException, IOException {
-    Timer1.Context timer = args.metrics.autoRebuildLatency.start(CHANGES);
-    try {
-      Change.Id cid = getChangeId();
-      ReviewDb db = args.db.get();
-      ChangeRebuilder rebuilder = args.rebuilder.get();
-      NoteDbUpdateManager.Result r;
-      try (NoteDbUpdateManager manager = rebuilder.stage(db, cid)) {
-        if (manager == null) {
-          return super.openHandle(repo); // May be null in tests.
-        }
-        r = manager.stageAndApplyDelta(change);
-        try {
-          rebuilder.execute(db, cid, manager);
-          repo.scanForRepoChanges();
-        } catch (OrmException | IOException e) {
-          // See ChangeNotes#rebuildAndOpen.
-          logger.atFine().log(
-              "Rebuilding change %s via drafts failed: %s", getChangeId(), e.getMessage());
-          args.metrics.autoRebuildFailureCount.increment(CHANGES);
-          requireNonNull(r.staged());
-          return LoadHandle.create(
-              ChangeNotesCommit.newStagedRevWalk(repo, r.staged().allUsersObjects()), draftsId(r));
-        }
-      }
-      return LoadHandle.create(ChangeNotesCommit.newRevWalk(repo), draftsId(r));
-    } catch (NoSuchChangeException e) {
-      return super.openHandle(repo);
-    } catch (OrmException e) {
-      throw new IOException(e);
-    } finally {
-      logger.atFine().log(
-          "Rebuilt change %s in %s in %s ms via drafts",
-          getChangeId(),
-          change != null ? "project " + change.getProject() : "unknown project",
-          TimeUnit.MILLISECONDS.convert(timer.stop(), TimeUnit.NANOSECONDS));
-    }
-  }
-
-  private ObjectId draftsId(NoteDbUpdateManager.Result r) {
-    requireNonNull(r);
-    requireNonNull(r.newState());
-    return r.newState().getDraftIds().get(author);
-  }
-
   @VisibleForTesting
   NoteMap getNoteMap() {
     return revisionNoteMap != null ? revisionNoteMap.noteMap : null;
diff --git a/java/com/google/gerrit/server/notedb/GwtormChangeBundleReader.java b/java/com/google/gerrit/server/notedb/GwtormChangeBundleReader.java
deleted file mode 100644
index 347ba48..0000000
--- a/java/com/google/gerrit/server/notedb/GwtormChangeBundleReader.java
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (C) 2016 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.notedb;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ReviewerSet;
-import com.google.gerrit.server.notedb.ChangeBundle.Source;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.util.List;
-
-@Singleton
-public class GwtormChangeBundleReader implements ChangeBundleReader {
-  @Inject
-  GwtormChangeBundleReader() {}
-
-  @Override
-  @Nullable
-  public ChangeBundle fromReviewDb(ReviewDb db, Change.Id id) throws OrmException {
-    Change reviewDbChange = db.changes().get(id);
-    if (reviewDbChange == null) {
-      return null;
-    }
-
-    // TODO(dborowitz): Figure out how to do this more consistently, e.g. hand-written inner joins.
-    List<PatchSetApproval> approvals = db.patchSetApprovals().byChange(id).toList();
-    return new ChangeBundle(
-        reviewDbChange,
-        db.changeMessages().byChange(id),
-        db.patchSets().byChange(id),
-        approvals,
-        db.patchComments().byChange(id),
-        ReviewerSet.fromApprovals(approvals),
-        Source.REVIEW_DB);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/MutableNotesMigration.java b/java/com/google/gerrit/server/notedb/MutableNotesMigration.java
index 7f4912b..eb41cbc 100644
--- a/java/com/google/gerrit/server/notedb/MutableNotesMigration.java
+++ b/java/com/google/gerrit/server/notedb/MutableNotesMigration.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.notedb;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.inject.Inject;
@@ -79,24 +80,18 @@
   /**
    * Set the in-memory values returned by this instance to match the given state.
    *
-   * <p>This method is only intended for use by {@link
-   * com.google.gerrit.server.notedb.rebuild.NoteDbMigrator}.
+   * <p>This method is only intended for use by tests.
    *
    * <p>This <em>only</em> modifies the in-memory state; if this instance was initialized from a
    * file-based config, the underlying storage is not updated. Callers are responsible for managing
    * the underlying storage on their own.
    */
+  @VisibleForTesting
   public MutableNotesMigration setFrom(NotesMigrationState state) {
     snapshot.set(state.snapshot());
     return this;
   }
 
-  /** @see #setFrom(NotesMigrationState) */
-  public MutableNotesMigration setFrom(NotesMigration other) {
-    snapshot.set(other.snapshot.get());
-    return this;
-  }
-
   private MutableNotesMigration set(Function<Snapshot.Builder, Snapshot.Builder> f) {
     snapshot.updateAndGet(s -> f.apply(s.toBuilder()).build());
     return this;
diff --git a/java/com/google/gerrit/server/notedb/NoteDbModule.java b/java/com/google/gerrit/server/notedb/NoteDbModule.java
index c76c39b..cf12d84 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbModule.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbModule.java
@@ -17,33 +17,21 @@
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Id;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
-import com.google.gerrit.server.notedb.rebuild.NotesMigrationStateListener;
 import com.google.inject.TypeLiteral;
 import com.google.inject.name.Names;
-import org.eclipse.jgit.lib.Config;
 
 public class NoteDbModule extends FactoryModule {
-  private final Config cfg;
   private final boolean useTestBindings;
 
-  static NoteDbModule forTest(Config cfg) {
-    return new NoteDbModule(cfg, true);
+  static NoteDbModule forTest() {
+    return new NoteDbModule(true);
   }
 
-  public NoteDbModule(Config cfg) {
-    this(cfg, false);
+  public NoteDbModule() {
+    this(false);
   }
 
-  private NoteDbModule(Config cfg, boolean useTestBindings) {
-    this.cfg = cfg;
+  private NoteDbModule(boolean useTestBindings) {
     this.useTestBindings = useTestBindings;
   }
 
@@ -56,57 +44,10 @@
     factory(NoteDbUpdateManager.Factory.class);
     factory(RobotCommentNotes.Factory.class);
     factory(RobotCommentUpdate.Factory.class);
-    DynamicSet.setOf(binder(), NotesMigrationStateListener.class);
 
     if (!useTestBindings) {
       install(ChangeNotesCache.module());
-      if (cfg.getBoolean("noteDb", null, "testRebuilderWrapper", false)) {
-        // Yes, another variety of test bindings with a different way of
-        // configuring it.
-        bind(ChangeRebuilder.class).to(TestChangeRebuilderWrapper.class);
-      } else {
-        bind(ChangeRebuilder.class).to(ChangeRebuilderImpl.class);
-      }
     } else {
-      bind(ChangeRebuilder.class)
-          .toInstance(
-              new ChangeRebuilder(null) {
-                @Override
-                public Result rebuild(ReviewDb db, Change.Id changeId) {
-                  return null;
-                }
-
-                @Override
-                public Result rebuildEvenIfReadOnly(ReviewDb db, Id changeId) {
-                  return null;
-                }
-
-                @Override
-                public Result rebuild(NoteDbUpdateManager manager, ChangeBundle bundle) {
-                  return null;
-                }
-
-                @Override
-                public NoteDbUpdateManager stage(ReviewDb db, Change.Id changeId) {
-                  return null;
-                }
-
-                @Override
-                public Result execute(
-                    ReviewDb db, Change.Id changeId, NoteDbUpdateManager manager) {
-                  return null;
-                }
-
-                @Override
-                public void buildUpdates(NoteDbUpdateManager manager, ChangeBundle bundle) {
-                  // Do nothing.
-                }
-
-                @Override
-                public void rebuildReviewDb(ReviewDb db, Project.NameKey project, Id changeId) {
-                  // Do nothing.
-                }
-              });
       bind(new TypeLiteral<Cache<ChangeNotesCache.Key, ChangeNotesState>>() {})
           .annotatedWith(Names.named(ChangeNotesCache.CACHE_NAME))
           .toInstance(CacheBuilder.newBuilder().<ChangeNotesCache.Key, ChangeNotesState>build());
diff --git a/java/com/google/gerrit/server/notedb/NoteDbSchemaVersionManager.java b/java/com/google/gerrit/server/notedb/NoteDbSchemaVersionManager.java
new file mode 100644
index 0000000..a8355c3
--- /dev/null
+++ b/java/com/google/gerrit/server/notedb/NoteDbSchemaVersionManager.java
@@ -0,0 +1,101 @@
+// Copyright (C) 2018 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.notedb;
+
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_VERSION;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gwtorm.server.OrmException;
+import java.io.IOException;
+import java.util.Optional;
+import javax.inject.Inject;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class NoteDbSchemaVersionManager {
+  private final AllProjectsName allProjectsName;
+  private final GitRepositoryManager repoManager;
+
+  @Inject
+  @VisibleForTesting
+  public NoteDbSchemaVersionManager(
+      AllProjectsName allProjectsName, GitRepositoryManager repoManager) {
+    // Can't inject GitReferenceUpdated here because it has dependencies that are not always
+    // available in this injector (e.g. during init). This is ok for now since no other ref updates
+    // during init are available to plugins, and there are not any other use cases for listening for
+    // updates to the version ref.
+    this.allProjectsName = allProjectsName;
+    this.repoManager = repoManager;
+  }
+
+  public int read() throws OrmException {
+    try (Repository repo = repoManager.openRepository(allProjectsName)) {
+      return IntBlob.parse(repo, REFS_VERSION).map(IntBlob::value).orElse(0);
+    } catch (IOException e) {
+      throw new OrmException("Failed to read " + REFS_VERSION, e);
+    }
+  }
+
+  public void init() throws IOException, OrmException {
+    try (Repository repo = repoManager.openRepository(allProjectsName);
+        RevWalk rw = new RevWalk(repo)) {
+      Optional<IntBlob> old = IntBlob.parse(repo, REFS_VERSION, rw);
+      if (old.isPresent()) {
+        throw new OrmException(
+            String.format(
+                "Expected no old version for %s, found %s", REFS_VERSION, old.get().value()));
+      }
+      IntBlob.store(
+          repo,
+          rw,
+          allProjectsName,
+          REFS_VERSION,
+          old.map(IntBlob::id).orElse(ObjectId.zeroId()),
+          // TODO(dborowitz): Find some way to not hard-code this constant here. We can't depend on
+          // NoteDbSchemaVersions from this package, because the schema java_library depends on the
+          // server java_library, so that would add a circular dependency. But *this* class must
+          // live in the server library, because it's used by things like NoteDbMigrator. One
+          // option: once NoteDbMigrator goes away, this class could move back to the schema
+          // subpackage.
+          180,
+          GitReferenceUpdated.DISABLED);
+    }
+  }
+
+  public void increment(int expectedOldVersion) throws IOException, OrmException {
+    try (Repository repo = repoManager.openRepository(allProjectsName);
+        RevWalk rw = new RevWalk(repo)) {
+      Optional<IntBlob> old = IntBlob.parse(repo, REFS_VERSION, rw);
+      if (old.isPresent() && old.get().value() != expectedOldVersion) {
+        throw new OrmException(
+            String.format(
+                "Expected old version %d for %s, found %d",
+                expectedOldVersion, REFS_VERSION, old.get().value()));
+      }
+      IntBlob.store(
+          repo,
+          rw,
+          allProjectsName,
+          REFS_VERSION,
+          old.map(IntBlob::id).orElse(ObjectId.zeroId()),
+          expectedOldVersion + 1,
+          GitReferenceUpdated.DISABLED);
+    }
+  }
+}
diff --git a/java/com/google/gerrit/server/notedb/NotesMigration.java b/java/com/google/gerrit/server/notedb/NotesMigration.java
index 9cee2cd..28754a6 100644
--- a/java/com/google/gerrit/server/notedb/NotesMigration.java
+++ b/java/com/google/gerrit/server/notedb/NotesMigration.java
@@ -46,8 +46,7 @@
  * <p>This class controls the state of the migration according to options in {@code gerrit.config}.
  * In general, any changes to these options should only be made by adventurous administrators, who
  * know what they're doing, on non-production data, for the purposes of testing the NoteDb
- * implementation. Changing options quite likely requires re-running {@code MigrateToNoteDb}. For
- * these reasons, the options remain undocumented.
+ * implementation.
  *
  * <p><strong>Note:</strong> Callers should not assume the values returned by {@code
  * NotesMigration}'s methods will not change in a running server.
@@ -57,9 +56,8 @@
   public static final String READ = "read";
   public static final String WRITE = "write";
   public static final String DISABLE_REVIEW_DB = "disableReviewDb";
-
-  private static final String PRIMARY_STORAGE = "primaryStorage";
-  private static final String SEQUENCE = "sequence";
+  public static final String PRIMARY_STORAGE = "primaryStorage";
+  public static final String SEQUENCE = "sequence";
 
   public static class Module extends AbstractModule {
     @Override
diff --git a/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java b/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java
deleted file mode 100644
index 7b427b4..0000000
--- a/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java
+++ /dev/null
@@ -1,510 +0,0 @@
-// Copyright (C) 2017 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.notedb;
-
-import static com.google.common.base.Preconditions.checkState;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import com.github.rholder.retry.RetryException;
-import com.github.rholder.retry.Retryer;
-import com.github.rholder.retry.RetryerBuilder;
-import com.github.rholder.retry.StopStrategies;
-import com.github.rholder.retry.WaitStrategies;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.InternalUser;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.RepoRefCache;
-import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.NoteDbChangeState.RefState;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.InternalChangeQuery;
-import com.google.gerrit.server.update.BatchUpdate;
-import com.google.gerrit.server.update.BatchUpdateOp;
-import com.google.gerrit.server.update.ChangeContext;
-import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.UpdateException;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gwtorm.server.AtomicUpdate;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-
-/** Helper to migrate the {@link PrimaryStorage} of individual changes. */
-@Singleton
-public class PrimaryStorageMigrator {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  /**
-   * Exception thrown during migration if the change has no {@code noteDbState} field at the
-   * beginning of the migration.
-   */
-  public static class NoNoteDbStateException extends RuntimeException {
-    private static final long serialVersionUID = 1L;
-
-    private NoNoteDbStateException(Change.Id id) {
-      super("change " + id + " has no note_db_state; rebuild it first");
-    }
-  }
-
-  private final AllUsersName allUsers;
-  private final ChangeNotes.Factory changeNotesFactory;
-  private final ChangeRebuilder rebuilder;
-  private final ChangeUpdate.Factory updateFactory;
-  private final GitRepositoryManager repoManager;
-  private final InternalUser.Factory internalUserFactory;
-  private final Provider<InternalChangeQuery> queryProvider;
-  private final Provider<ReviewDb> db;
-  private final RetryHelper retryHelper;
-
-  private final long skewMs;
-  private final long timeoutMs;
-  private final Retryer<NoteDbChangeState> testEnsureRebuiltRetryer;
-
-  @Inject
-  PrimaryStorageMigrator(
-      @GerritServerConfig Config cfg,
-      Provider<ReviewDb> db,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsers,
-      ChangeRebuilder rebuilder,
-      ChangeNotes.Factory changeNotesFactory,
-      Provider<InternalChangeQuery> queryProvider,
-      ChangeUpdate.Factory updateFactory,
-      InternalUser.Factory internalUserFactory,
-      RetryHelper retryHelper) {
-    this(
-        cfg,
-        db,
-        repoManager,
-        allUsers,
-        rebuilder,
-        null,
-        changeNotesFactory,
-        queryProvider,
-        updateFactory,
-        internalUserFactory,
-        retryHelper);
-  }
-
-  @VisibleForTesting
-  public PrimaryStorageMigrator(
-      Config cfg,
-      Provider<ReviewDb> db,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsers,
-      ChangeRebuilder rebuilder,
-      @Nullable Retryer<NoteDbChangeState> testEnsureRebuiltRetryer,
-      ChangeNotes.Factory changeNotesFactory,
-      Provider<InternalChangeQuery> queryProvider,
-      ChangeUpdate.Factory updateFactory,
-      InternalUser.Factory internalUserFactory,
-      RetryHelper retryHelper) {
-    this.db = db;
-    this.repoManager = repoManager;
-    this.allUsers = allUsers;
-    this.rebuilder = rebuilder;
-    this.testEnsureRebuiltRetryer = testEnsureRebuiltRetryer;
-    this.changeNotesFactory = changeNotesFactory;
-    this.queryProvider = queryProvider;
-    this.updateFactory = updateFactory;
-    this.internalUserFactory = internalUserFactory;
-    this.retryHelper = retryHelper;
-    skewMs = NoteDbChangeState.getReadOnlySkew(cfg);
-
-    String s = "notedb";
-    timeoutMs =
-        cfg.getTimeUnit(
-            s,
-            null,
-            "primaryStorageMigrationTimeout",
-            MILLISECONDS.convert(60, SECONDS),
-            MILLISECONDS);
-  }
-
-  /**
-   * Migrate a change's primary storage from ReviewDb to NoteDb.
-   *
-   * <p>This method will return only if the primary storage of the change is NoteDb afterwards. (It
-   * may return early if the primary storage was already NoteDb.)
-   *
-   * <p>If this method throws an exception, then the primary storage of the change is probably not
-   * NoteDb. (It is possible that the primary storage of the change is NoteDb in this case, but
-   * there was an error reading the state.) Moreover, after an exception, the change may be
-   * read-only until a lease expires. If the caller chooses to retry, they should wait until the
-   * read-only lease expires; this method will fail relatively quickly if called on a read-only
-   * change.
-   *
-   * <p>Note that if the change is read-only after this method throws an exception, that does not
-   * necessarily guarantee that the read-only lease was acquired during that particular method
-   * invocation; this call may have in fact failed because another thread acquired the lease first.
-   *
-   * @param id change ID.
-   * @throws OrmException if a ReviewDb-level error occurs.
-   * @throws IOException if a repo-level error occurs.
-   */
-  public void migrateToNoteDbPrimary(Change.Id id) throws OrmException, IOException {
-    // Since there are multiple non-atomic steps in this method, we need to
-    // consider what happens when there is another writer concurrent with the
-    // thread executing this method.
-    //
-    // Let:
-    // * OR = other writer writes noteDbState & new data to ReviewDb (in one
-    //        transaction)
-    // * ON = other writer writes to NoteDb
-    // * MRO = migrator sets state to read-only
-    // * MR = ensureRebuilt writes rebuilt noteDbState to ReviewDb (but does not
-    //        otherwise update ReviewDb in this transaction)
-    // * MN = ensureRebuilt writes rebuilt state to NoteDb
-    //
-    // Consider all the interleavings of these operations.
-    //
-    // * OR,ON,MRO,...
-    //   Other writer completes before migrator begins; this is not a concurrent
-    //   write.
-    // * MRO,...,OR,...
-    //   OR will fail, since it atomically checks that the noteDbState is not
-    //   read-only before proceeding. This results in an exception, but not a
-    //   concurrent write.
-    //
-    // Thus all the "interesting" interleavings start with OR,MRO, and differ on
-    // where ON falls relative to MR/MN.
-    //
-    // * OR,MRO,ON,MR,MN
-    //   The other NoteDb write succeeds despite the noteDbState being
-    //   read-only. Because the read-only state from MRO includes the update
-    //   from OR, the change is up-to-date at this point. Thus MR,MN is a no-op.
-    //   The end result is an up-to-date, read-only change.
-    //
-    // * OR,MRO,MR,ON,MN
-    //   The change is out-of-date when ensureRebuilt begins, because OR
-    //   succeeded but the corresponding ON has not happened yet. ON will
-    //   succeed, because there have been no intervening NoteDb writes. MN will
-    //   fail, because ON updated the state in NoteDb to something other than
-    //   what MR claimed. This leaves the change in an out-of-date, read-only
-    //   state.
-    //
-    //   If this method threw an exception in this case, the change would
-    //   eventually switch back to read-write when the read-only lease expires,
-    //   so this situation is recoverable. However, it would be inconvenient for
-    //   a change to be read-only for so long.
-    //
-    //   Thus, as an optimization, we have a retry loop that attempts
-    //   ensureRebuilt while still holding the same read-only lease. This
-    //   effectively results in the interleaving OR,MR,ON,MR,MN; in contrast
-    //   with the previous case, here, MR/MN actually rebuilds the change. In
-    //   the case of a write failure, MR/MN might fail and get retried again. If
-    //   it exceeds the maximum number of retries, an exception is thrown.
-    //
-    // * OR,MRO,MR,MN,ON
-    //   The change is out-of-date when ensureRebuilt begins. The change is
-    //   rebuilt, leaving a new state in NoteDb. ON will fail, because the old
-    //   NoteDb state has changed since the ref state was read when the update
-    //   began (prior to OR). This results in an exception from ON, but the end
-    //   result is still an up-to-date, read-only change. The end user that
-    //   initiated the other write observes an error, but this is no different
-    //   from other errors that need retrying, e.g. due to a backend write
-    //   failure.
-
-    Stopwatch sw = Stopwatch.createStarted();
-    Change readOnlyChange = setReadOnlyInReviewDb(id); // MRO
-    if (readOnlyChange == null) {
-      return; // Already migrated.
-    }
-
-    NoteDbChangeState rebuiltState;
-    try {
-      // MR,MN
-      rebuiltState =
-          ensureRebuiltRetryer(sw)
-              .call(
-                  () ->
-                      ensureRebuilt(
-                          readOnlyChange.getProject(),
-                          id,
-                          NoteDbChangeState.parse(readOnlyChange)));
-    } catch (RetryException | ExecutionException e) {
-      throw new OrmException(e);
-    }
-
-    // At this point, the noteDbState in ReviewDb is read-only, and it is
-    // guaranteed to match the state actually in NoteDb. Now it is safe to set
-    // the primary storage to NoteDb.
-
-    setPrimaryStorageNoteDb(id, rebuiltState);
-    logger.atFine().log(
-        "Migrated change %s to NoteDb primary in %sms", id, sw.elapsed(MILLISECONDS));
-  }
-
-  private Change setReadOnlyInReviewDb(Change.Id id) throws OrmException {
-    AtomicBoolean alreadyMigrated = new AtomicBoolean(false);
-    Change result =
-        db().changes()
-            .atomicUpdate(
-                id,
-                new AtomicUpdate<Change>() {
-                  @Override
-                  public Change update(Change change) {
-                    NoteDbChangeState state = NoteDbChangeState.parse(change);
-                    if (state == null) {
-                      // Could rebuild the change here, but that's more complexity, and this
-                      // normally shouldn't happen.
-                      //
-                      // Known cases where this happens are described in and handled by
-                      // NoteDbMigrator#canSkipPrimaryStorageMigration.
-                      throw new NoNoteDbStateException(id);
-                    }
-                    // If the change is already read-only, then the lease is held by another
-                    // (likely failed) migrator thread. Fail early, as we can't take over
-                    // the lease.
-                    NoteDbChangeState.checkNotReadOnly(change, skewMs);
-                    if (state.getPrimaryStorage() != PrimaryStorage.NOTE_DB) {
-                      Timestamp now = TimeUtil.nowTs();
-                      Timestamp until = new Timestamp(now.getTime() + timeoutMs);
-                      change.setNoteDbState(state.withReadOnlyUntil(until).toString());
-                    } else {
-                      alreadyMigrated.set(true);
-                    }
-                    return change;
-                  }
-                });
-    return alreadyMigrated.get() ? null : result;
-  }
-
-  private Retryer<NoteDbChangeState> ensureRebuiltRetryer(Stopwatch sw) {
-    if (testEnsureRebuiltRetryer != null) {
-      return testEnsureRebuiltRetryer;
-    }
-    // Retry the ensureRebuilt step with backoff until half the timeout has
-    // expired, leaving the remaining half for the rest of the steps.
-    long remainingNanos = (MILLISECONDS.toNanos(timeoutMs) / 2) - sw.elapsed(NANOSECONDS);
-    remainingNanos = Math.max(remainingNanos, 0);
-    return RetryerBuilder.<NoteDbChangeState>newBuilder()
-        .retryIfException(e -> (e instanceof IOException) || (e instanceof OrmException))
-        .withWaitStrategy(
-            WaitStrategies.join(
-                WaitStrategies.exponentialWait(250, MILLISECONDS),
-                WaitStrategies.randomWait(50, MILLISECONDS)))
-        .withStopStrategy(StopStrategies.stopAfterDelay(remainingNanos, NANOSECONDS))
-        .build();
-  }
-
-  private NoteDbChangeState ensureRebuilt(
-      Project.NameKey project, Change.Id id, NoteDbChangeState readOnlyState)
-      throws IOException, OrmException, RepositoryNotFoundException {
-    try (Repository changeRepo = repoManager.openRepository(project);
-        Repository allUsersRepo = repoManager.openRepository(allUsers)) {
-      if (!readOnlyState.isUpToDate(new RepoRefCache(changeRepo), new RepoRefCache(allUsersRepo))) {
-        NoteDbUpdateManager.Result r = rebuilder.rebuildEvenIfReadOnly(db(), id);
-        checkState(
-            r.newState().getReadOnlyUntil().equals(readOnlyState.getReadOnlyUntil()),
-            "state after rebuilding has different read-only lease: %s != %s",
-            r.newState(),
-            readOnlyState);
-        readOnlyState = r.newState();
-      }
-    }
-    return readOnlyState;
-  }
-
-  private void setPrimaryStorageNoteDb(Change.Id id, NoteDbChangeState expectedState)
-      throws OrmException {
-    db().changes()
-        .atomicUpdate(
-            id,
-            new AtomicUpdate<Change>() {
-              @Override
-              public Change update(Change change) {
-                NoteDbChangeState state = NoteDbChangeState.parse(change);
-                if (!Objects.equals(state, expectedState)) {
-                  throw new OrmRuntimeException(badState(state, expectedState));
-                }
-                Timestamp until = state.getReadOnlyUntil().get();
-                if (TimeUtil.nowTs().after(until)) {
-                  throw new OrmRuntimeException(
-                      "read-only lease on change " + id + " expired at " + until);
-                }
-                change.setNoteDbState(NoteDbChangeState.NOTE_DB_PRIMARY_STATE);
-                return change;
-              }
-            });
-  }
-
-  private ReviewDb db() {
-    return ReviewDbUtil.unwrapDb(db.get());
-  }
-
-  private String badState(NoteDbChangeState actual, NoteDbChangeState expected) {
-    return "state changed unexpectedly: " + actual + " != " + expected;
-  }
-
-  public void migrateToReviewDbPrimary(Change.Id id, @Nullable Project.NameKey project)
-      throws OrmException, IOException {
-    // Migrating back to ReviewDb primary is much simpler than the original migration to NoteDb
-    // primary, because when NoteDb is primary, each write only goes to one storage location rather
-    // than both. We only need to consider whether a concurrent writer (OR) conflicts with the first
-    // setReadOnlyInNoteDb step (MR) in this method.
-    //
-    // If OR wins, then either:
-    // * MR will set read-only after OR is completed, which is not a concurrent write.
-    // * MR will fail to set read-only with a lock failure. The caller will have to retry, but the
-    //   change is not in a read-only state, so behavior is not degraded in the meantime.
-    //
-    // If MR wins, then either:
-    // * OR will fail with a read-only exception (via AbstractChangeNotes#apply).
-    // * OR will fail with a lock failure.
-    //
-    // In all of these scenarios, the change is read-only if and only if MR succeeds.
-    //
-    // There will be no concurrent writes to ReviewDb for this change until
-    // setPrimaryStorageReviewDb completes, because ReviewDb writes are not attempted when primary
-    // storage is NoteDb. After the primary storage changes back, it is possible for subsequent
-    // NoteDb writes to conflict with the releaseReadOnlyLeaseInNoteDb step, but at this point,
-    // since ReviewDb is primary, we are back to ignoring them.
-    Stopwatch sw = Stopwatch.createStarted();
-    if (project == null) {
-      project = getProject(id);
-    }
-    ObjectId newMetaId = setReadOnlyInNoteDb(project, id);
-    rebuilder.rebuildReviewDb(db(), project, id);
-    setPrimaryStorageReviewDb(id, newMetaId);
-    releaseReadOnlyLeaseInNoteDb(project, id);
-    logger.atFine().log(
-        "Migrated change %s to ReviewDb primary in %sms", id, sw.elapsed(MILLISECONDS));
-  }
-
-  private ObjectId setReadOnlyInNoteDb(Project.NameKey project, Change.Id id)
-      throws OrmException, IOException {
-    Timestamp now = TimeUtil.nowTs();
-    Timestamp until = new Timestamp(now.getTime() + timeoutMs);
-    ChangeUpdate update =
-        updateFactory.create(
-            changeNotesFactory.createChecked(db.get(), project, id), internalUserFactory.create());
-    update.setReadOnlyUntil(until);
-    return update.commit();
-  }
-
-  private void setPrimaryStorageReviewDb(Change.Id id, ObjectId newMetaId)
-      throws OrmException, IOException {
-    ImmutableMap.Builder<Account.Id, ObjectId> draftIds = ImmutableMap.builder();
-    try (Repository repo = repoManager.openRepository(allUsers)) {
-      for (Ref draftRef :
-          repo.getRefDatabase().getRefsByPrefix(RefNames.refsDraftCommentsPrefix(id))) {
-        Account.Id accountId = Account.Id.fromRef(draftRef.getName());
-        if (accountId != null) {
-          draftIds.put(accountId, draftRef.getObjectId().copy());
-        }
-      }
-    }
-    NoteDbChangeState newState =
-        new NoteDbChangeState(
-            id,
-            PrimaryStorage.REVIEW_DB,
-            Optional.of(RefState.create(newMetaId, draftIds.build())),
-            Optional.empty());
-    db().changes()
-        .atomicUpdate(
-            id,
-            new AtomicUpdate<Change>() {
-              @Override
-              public Change update(Change change) {
-                if (PrimaryStorage.of(change) != PrimaryStorage.NOTE_DB) {
-                  throw new OrmRuntimeException(
-                      "change " + id + " is not NoteDb primary: " + change.getNoteDbState());
-                }
-                change.setNoteDbState(newState.toString());
-                return change;
-              }
-            });
-  }
-
-  private void releaseReadOnlyLeaseInNoteDb(Project.NameKey project, Change.Id id)
-      throws OrmException {
-    // Use a BatchUpdate since ReviewDb is primary at this point, so it needs to reflect the update.
-    // (In practice retrying won't happen, since we aren't using fused updates at this point.)
-    try {
-      retryHelper.execute(
-          updateFactory -> {
-            try (BatchUpdate bu =
-                updateFactory.create(
-                    db.get(), project, internalUserFactory.create(), TimeUtil.nowTs())) {
-              bu.addOp(
-                  id,
-                  new BatchUpdateOp() {
-                    @Override
-                    public boolean updateChange(ChangeContext ctx) {
-                      ctx.getUpdate(ctx.getChange().currentPatchSetId())
-                          .setReadOnlyUntil(new Timestamp(0));
-                      return true;
-                    }
-                  });
-              bu.execute();
-              return null;
-            }
-          });
-    } catch (RestApiException | UpdateException e) {
-      throw new OrmException(e);
-    }
-  }
-
-  private Project.NameKey getProject(Change.Id id) throws OrmException {
-    List<ChangeData> cds =
-        queryProvider.get().setRequestedFields(ChangeField.PROJECT).byLegacyChangeId(id);
-    Set<Project.NameKey> projects = new TreeSet<>();
-    for (ChangeData cd : cds) {
-      projects.add(cd.project());
-    }
-    if (projects.size() != 1) {
-      throw new OrmException(
-          "zero or multiple projects found for change "
-              + id
-              + ", must specify project explicitly: "
-              + projects);
-    }
-    return projects.iterator().next();
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentNotes.java b/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
index a05e6a1..364ad75 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentNotes.java
@@ -24,7 +24,6 @@
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.client.RobotComment;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import java.io.IOException;
@@ -49,7 +48,7 @@
 
   @Inject
   RobotCommentNotes(Args args, @Assisted Change change) {
-    super(args, change.getId(), PrimaryStorage.of(change), false);
+    super(args, change.getId());
     this.change = change;
   }
 
diff --git a/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java b/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java
deleted file mode 100644
index 11fef24..0000000
--- a/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (C) 2016 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.notedb;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Id;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-@VisibleForTesting
-@Singleton
-public class TestChangeRebuilderWrapper extends ChangeRebuilder {
-  private final ChangeRebuilderImpl delegate;
-  private final AtomicBoolean failNextUpdate;
-  private final AtomicBoolean stealNextUpdate;
-
-  @Inject
-  TestChangeRebuilderWrapper(SchemaFactory<ReviewDb> schemaFactory, ChangeRebuilderImpl rebuilder) {
-    super(schemaFactory);
-    this.delegate = rebuilder;
-    this.failNextUpdate = new AtomicBoolean();
-    this.stealNextUpdate = new AtomicBoolean();
-  }
-
-  public void failNextUpdate() {
-    failNextUpdate.set(true);
-  }
-
-  public void stealNextUpdate() {
-    stealNextUpdate.set(true);
-  }
-
-  @Override
-  public Result rebuild(ReviewDb db, Change.Id changeId) throws IOException, OrmException {
-    return rebuild(db, changeId, true);
-  }
-
-  @Override
-  public Result rebuildEvenIfReadOnly(ReviewDb db, Change.Id changeId)
-      throws IOException, OrmException {
-    return rebuild(db, changeId, false);
-  }
-
-  private Result rebuild(ReviewDb db, Change.Id changeId, boolean checkReadOnly)
-      throws IOException, OrmException {
-    if (failNextUpdate.getAndSet(false)) {
-      throw new IOException("Update failed");
-    }
-    Result result =
-        checkReadOnly
-            ? delegate.rebuild(db, changeId)
-            : delegate.rebuildEvenIfReadOnly(db, changeId);
-    if (stealNextUpdate.getAndSet(false)) {
-      throw new IOException("Update stolen");
-    }
-    return result;
-  }
-
-  @Override
-  public Result rebuild(NoteDbUpdateManager manager, ChangeBundle bundle)
-      throws IOException, OrmException {
-    // stealNextUpdate doesn't really apply in this case because the IOException
-    // would normally come from the manager.execute() method, which isn't called
-    // here.
-    return delegate.rebuild(manager, bundle);
-  }
-
-  @Override
-  public NoteDbUpdateManager stage(ReviewDb db, Change.Id changeId)
-      throws IOException, OrmException {
-    // Don't inspect stealNextUpdate; that happens in execute() below.
-    return delegate.stage(db, changeId);
-  }
-
-  @Override
-  public Result execute(ReviewDb db, Change.Id changeId, NoteDbUpdateManager manager)
-      throws OrmException, IOException {
-    if (failNextUpdate.getAndSet(false)) {
-      throw new IOException("Update failed");
-    }
-    Result result = delegate.execute(db, changeId, manager);
-    if (stealNextUpdate.getAndSet(false)) {
-      throw new IOException("Update stolen");
-    }
-    return result;
-  }
-
-  @Override
-  public void buildUpdates(NoteDbUpdateManager manager, ChangeBundle bundle)
-      throws IOException, OrmException {
-    // Don't check for manual failure; that happens in execute().
-    delegate.buildUpdates(manager, bundle);
-  }
-
-  @Override
-  public void rebuildReviewDb(ReviewDb db, Project.NameKey project, Id changeId)
-      throws OrmException {
-    if (failNextUpdate.getAndSet(false)) {
-      throw new OrmException("Update failed");
-    }
-    delegate.rebuildReviewDb(db, project, changeId);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/AbortUpdateException.java b/java/com/google/gerrit/server/notedb/rebuild/AbortUpdateException.java
deleted file mode 100644
index 0e6d3e9..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/AbortUpdateException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import com.google.gwtorm.server.OrmRuntimeException;
-
-class AbortUpdateException extends OrmRuntimeException {
-  private static final long serialVersionUID = 1L;
-
-  AbortUpdateException() {
-    super("aborted");
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java b/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java
deleted file mode 100644
index 9ecf476..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import java.sql.Timestamp;
-
-class ApprovalEvent extends Event {
-  private PatchSetApproval psa;
-
-  ApprovalEvent(PatchSetApproval psa, Timestamp changeCreatedOn) {
-    super(
-        psa.getPatchSetId(),
-        psa.getAccountId(),
-        psa.getRealAccountId(),
-        psa.getGranted(),
-        changeCreatedOn,
-        psa.getTag());
-    this.psa = psa;
-  }
-
-  @Override
-  boolean uniquePerUpdate() {
-    return false;
-  }
-
-  @Override
-  protected boolean canHaveTag() {
-    // Legacy SUBM approvals don't have a tag field set, but the corresponding
-    // ChangeMessage for merging the change does. We need to let these be in the
-    // same meta commit so the SUBM approval isn't counted as post-submit.
-    return !psa.isLegacySubmit();
-  }
-
-  @Override
-  void apply(ChangeUpdate update) {
-    checkUpdate(update);
-    update.putApproval(psa.getLabel(), psa.getValue());
-  }
-
-  @Override
-  protected boolean isPostSubmitApproval() {
-    return psa.isPostSubmit();
-  }
-
-  @Override
-  protected void addToString(ToStringHelper helper) {
-    helper.add("approval", psa);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java b/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java
deleted file mode 100644
index 53c9dc4..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gwtorm.server.OrmException;
-import java.sql.Timestamp;
-import java.util.Map;
-import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-class ChangeMessageEvent extends Event {
-  private static final ImmutableMap<Change.Status, Pattern> STATUS_PATTERNS =
-      ImmutableMap.of(
-          Change.Status.ABANDONED, Pattern.compile("^Abandoned(\n.*)*$"),
-          Change.Status.MERGED,
-              Pattern.compile(
-                  "^Change has been successfully (merged|cherry-picked|rebased|pushed).*$"),
-          Change.Status.NEW, Pattern.compile("^Restored(\n.*)*$"));
-
-  private static final Pattern PRIVATE_SET_REGEXP = Pattern.compile("^Set private$");
-  private static final Pattern PRIVATE_UNSET_REGEXP = Pattern.compile("^Unset private$");
-
-  private static final Pattern TOPIC_SET_REGEXP = Pattern.compile("^Topic set to (.+)$");
-  private static final Pattern TOPIC_CHANGED_REGEXP =
-      Pattern.compile("^Topic changed from (.+) to (.+)$");
-  private static final Pattern TOPIC_REMOVED_REGEXP = Pattern.compile("^Topic (.+) removed$");
-
-  private static final Pattern WIP_SET_REGEXP = Pattern.compile("^Set Work In Progress$");
-  private static final Pattern WIP_UNSET_REGEXP = Pattern.compile("^Set Ready For Review$");
-
-  private final Change change;
-  private final Change noteDbChange;
-  private final Optional<Change.Status> status;
-  private final ChangeMessage message;
-
-  ChangeMessageEvent(
-      Change change, Change noteDbChange, ChangeMessage message, Timestamp changeCreatedOn) {
-    super(
-        message.getPatchSetId(),
-        message.getAuthor(),
-        message.getRealAuthor(),
-        message.getWrittenOn(),
-        changeCreatedOn,
-        message.getTag());
-    this.change = change;
-    this.noteDbChange = noteDbChange;
-    this.message = message;
-    this.status = parseStatus(message);
-  }
-
-  @Override
-  boolean uniquePerUpdate() {
-    return true;
-  }
-
-  @Override
-  protected boolean isSubmit() {
-    return status.isPresent() && status.get() == Change.Status.MERGED;
-  }
-
-  @Override
-  protected boolean canHaveTag() {
-    return true;
-  }
-
-  @SuppressWarnings("deprecation")
-  @Override
-  void apply(ChangeUpdate update) throws OrmException {
-    checkUpdate(update);
-    update.setChangeMessage(message.getMessage());
-    setPrivate(update);
-    setTopic(update);
-    setWorkInProgress(update);
-
-    if (status.isPresent()) {
-      Change.Status s = status.get();
-      update.fixStatus(s);
-      noteDbChange.setStatus(s);
-      if (s == Change.Status.MERGED) {
-        update.setSubmissionId(change.getSubmissionId());
-        noteDbChange.setSubmissionId(change.getSubmissionId());
-      }
-    }
-  }
-
-  private static Optional<Change.Status> parseStatus(ChangeMessage message) {
-    String msg = message.getMessage();
-    if (msg == null) {
-      return Optional.empty();
-    }
-    for (Map.Entry<Change.Status, Pattern> e : STATUS_PATTERNS.entrySet()) {
-      if (e.getValue().matcher(msg).matches()) {
-        return Optional.of(e.getKey());
-      }
-    }
-    return Optional.empty();
-  }
-
-  private void setPrivate(ChangeUpdate update) {
-    String msg = message.getMessage();
-    if (msg == null) {
-      return;
-    }
-    Matcher m = PRIVATE_SET_REGEXP.matcher(msg);
-    if (m.matches()) {
-      update.setPrivate(true);
-      noteDbChange.setPrivate(true);
-      return;
-    }
-
-    m = PRIVATE_UNSET_REGEXP.matcher(msg);
-    if (m.matches()) {
-      update.setPrivate(false);
-      noteDbChange.setPrivate(false);
-    }
-  }
-
-  private void setTopic(ChangeUpdate update) {
-    String msg = message.getMessage();
-    if (msg == null) {
-      return;
-    }
-    Matcher m = TOPIC_SET_REGEXP.matcher(msg);
-    if (m.matches()) {
-      String topic = m.group(1);
-      update.setTopic(topic);
-      noteDbChange.setTopic(topic);
-      return;
-    }
-
-    m = TOPIC_CHANGED_REGEXP.matcher(msg);
-    if (m.matches()) {
-      String topic = m.group(2);
-      update.setTopic(topic);
-      noteDbChange.setTopic(topic);
-      return;
-    }
-
-    if (TOPIC_REMOVED_REGEXP.matcher(msg).matches()) {
-      update.setTopic(null);
-      noteDbChange.setTopic(null);
-    }
-  }
-
-  private void setWorkInProgress(ChangeUpdate update) {
-    String msg = Strings.nullToEmpty(message.getMessage());
-    String tag = message.getTag();
-    if (ChangeMessagesUtil.TAG_SET_WIP.equals(tag)
-        || ChangeMessagesUtil.TAG_UPLOADED_WIP_PATCH_SET.equals(tag)
-        || WIP_SET_REGEXP.matcher(msg).matches()) {
-      update.setWorkInProgress(true);
-      noteDbChange.setWorkInProgress(true);
-    } else if (ChangeMessagesUtil.TAG_SET_READY.equals(tag)
-        || ChangeMessagesUtil.TAG_UPLOADED_PATCH_SET.equals(tag)
-        || WIP_UNSET_REGEXP.matcher(msg).matches()) {
-      update.setWorkInProgress(false);
-      noteDbChange.setWorkInProgress(false);
-    }
-  }
-
-  @Override
-  protected void addToString(ToStringHelper helper) {
-    helper.add("message", message);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilder.java b/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilder.java
deleted file mode 100644
index 8ce9987..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilder.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.notedb.ChangeBundle;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import java.io.IOException;
-
-public abstract class ChangeRebuilder {
-  public static class NoPatchSetsException extends OrmException {
-    private static final long serialVersionUID = 1L;
-
-    NoPatchSetsException(Change.Id changeId) {
-      super("Change " + changeId + " cannot be rebuilt because it has no patch sets");
-    }
-  }
-
-  private final SchemaFactory<ReviewDb> schemaFactory;
-
-  protected ChangeRebuilder(SchemaFactory<ReviewDb> schemaFactory) {
-    this.schemaFactory = schemaFactory;
-  }
-
-  public final ListenableFuture<Result> rebuildAsync(
-      Change.Id id, ListeningExecutorService executor) {
-    return executor.submit(
-        () -> {
-          try (ReviewDb db = schemaFactory.open()) {
-            return rebuild(db, id);
-          }
-        });
-  }
-
-  /**
-   * Rebuild ReviewDb contents by copying from NoteDb.
-   *
-   * <p>Requires NoteDb to be the primary storage for the change.
-   */
-  public abstract void rebuildReviewDb(ReviewDb db, Project.NameKey project, Change.Id changeId)
-      throws OrmException;
-
-  // In the following methods "rebuilding" always refers to copying the state
-  // from ReviewDb to NoteDb, i.e. assuming ReviewDb is the primary storage.
-
-  public abstract Result rebuild(ReviewDb db, Change.Id changeId) throws IOException, OrmException;
-
-  public abstract Result rebuildEvenIfReadOnly(ReviewDb db, Change.Id changeId)
-      throws IOException, OrmException;
-
-  public abstract Result rebuild(NoteDbUpdateManager manager, ChangeBundle bundle)
-      throws IOException, OrmException;
-
-  public abstract void buildUpdates(NoteDbUpdateManager manager, ChangeBundle bundle)
-      throws IOException, OrmException;
-
-  public abstract NoteDbUpdateManager stage(ReviewDb db, Change.Id changeId)
-      throws IOException, OrmException;
-
-  public abstract Result execute(ReviewDb db, Change.Id changeId, NoteDbUpdateManager manager)
-      throws OrmException, IOException;
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java b/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
deleted file mode 100644
index 8740710..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
+++ /dev/null
@@ -1,687 +0,0 @@
-// Copyright (C) 2014 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.notedb.rebuild;
-
-import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
-import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static java.util.stream.Collectors.toList;
-
-import com.google.common.base.Splitter;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Ordering;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Table;
-import com.google.common.primitives.Ints;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.GerritServerId;
-import com.google.gerrit.server.notedb.ChangeBundle;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.ChangeDraftUpdate;
-import com.google.gerrit.server.notedb.ChangeNoteUtil;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.NoteDbChangeState;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager.OpenRepo;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.update.ChainedReceiveCommands;
-import com.google.gwtorm.client.Key;
-import com.google.gwtorm.server.Access;
-import com.google.gwtorm.server.AtomicUpdate;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeMap;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-
-public class ChangeRebuilderImpl extends ChangeRebuilder {
-  /**
-   * The maximum amount of time between the ReviewDb timestamp of the first and last events batched
-   * together into a single NoteDb update.
-   *
-   * <p>Used to account for the fact that different records with their own timestamps (e.g. {@link
-   * PatchSetApproval} and {@link ChangeMessage}) historically didn't necessarily use the same
-   * timestamp, and tended to call {@code System.currentTimeMillis()} independently.
-   */
-  public static final long MAX_WINDOW_MS = SECONDS.toMillis(3);
-
-  /**
-   * The maximum amount of time between two consecutive events to consider them to be in the same
-   * batch.
-   */
-  static final long MAX_DELTA_MS = SECONDS.toMillis(1);
-
-  private final ChangeBundleReader bundleReader;
-  private final ChangeDraftUpdate.Factory draftUpdateFactory;
-  private final ChangeNoteUtil changeNoteUtil;
-  private final ChangeNotes.Factory notesFactory;
-  private final ChangeUpdate.Factory updateFactory;
-  private final CommentsUtil commentsUtil;
-  private final NoteDbUpdateManager.Factory updateManagerFactory;
-  private final NotesMigration migration;
-  private final PatchListCache patchListCache;
-  private final PersonIdent serverIdent;
-  private final ProjectCache projectCache;
-  private final String serverId;
-  private final long skewMs;
-
-  @Inject
-  ChangeRebuilderImpl(
-      @GerritServerConfig Config cfg,
-      SchemaFactory<ReviewDb> schemaFactory,
-      ChangeBundleReader bundleReader,
-      ChangeDraftUpdate.Factory draftUpdateFactory,
-      ChangeNoteUtil changeNoteUtil,
-      ChangeNotes.Factory notesFactory,
-      ChangeUpdate.Factory updateFactory,
-      CommentsUtil commentsUtil,
-      NoteDbUpdateManager.Factory updateManagerFactory,
-      NotesMigration migration,
-      PatchListCache patchListCache,
-      @GerritPersonIdent PersonIdent serverIdent,
-      @Nullable ProjectCache projectCache,
-      @GerritServerId String serverId) {
-    super(schemaFactory);
-    this.bundleReader = bundleReader;
-    this.draftUpdateFactory = draftUpdateFactory;
-    this.changeNoteUtil = changeNoteUtil;
-    this.notesFactory = notesFactory;
-    this.updateFactory = updateFactory;
-    this.commentsUtil = commentsUtil;
-    this.updateManagerFactory = updateManagerFactory;
-    this.migration = migration;
-    this.patchListCache = patchListCache;
-    this.serverIdent = serverIdent;
-    this.projectCache = projectCache;
-    this.serverId = serverId;
-    this.skewMs = NoteDbChangeState.getReadOnlySkew(cfg);
-  }
-
-  @Override
-  public Result rebuild(ReviewDb db, Change.Id changeId) throws IOException, OrmException {
-    return rebuild(db, changeId, true);
-  }
-
-  @Override
-  public Result rebuildEvenIfReadOnly(ReviewDb db, Change.Id changeId)
-      throws IOException, OrmException {
-    return rebuild(db, changeId, false);
-  }
-
-  private Result rebuild(ReviewDb db, Change.Id changeId, boolean checkReadOnly)
-      throws IOException, OrmException {
-    db = ReviewDbUtil.unwrapDb(db);
-    // Read change just to get project; this instance is then discarded so we can read a consistent
-    // ChangeBundle inside a transaction.
-    Change change = db.changes().get(changeId);
-    if (change == null) {
-      throw new NoSuchChangeException(changeId);
-    }
-    try (NoteDbUpdateManager manager = updateManagerFactory.create(change.getProject())) {
-      buildUpdates(manager, bundleReader.fromReviewDb(db, changeId));
-      return execute(db, changeId, manager, checkReadOnly, true);
-    }
-  }
-
-  @Override
-  public Result rebuild(NoteDbUpdateManager manager, ChangeBundle bundle)
-      throws NoSuchChangeException, IOException, OrmException {
-    Change change = new Change(bundle.getChange());
-    buildUpdates(manager, bundle);
-    return manager.stageAndApplyDelta(change);
-  }
-
-  @Override
-  public NoteDbUpdateManager stage(ReviewDb db, Change.Id changeId)
-      throws IOException, OrmException {
-    db = ReviewDbUtil.unwrapDb(db);
-    Change change = checkNoteDbState(ChangeNotes.readOneReviewDbChange(db, changeId));
-    if (change == null) {
-      throw new NoSuchChangeException(changeId);
-    }
-    NoteDbUpdateManager manager = updateManagerFactory.create(change.getProject());
-    buildUpdates(manager, bundleReader.fromReviewDb(db, changeId));
-    manager.stage();
-    return manager;
-  }
-
-  @Override
-  public Result execute(ReviewDb db, Change.Id changeId, NoteDbUpdateManager manager)
-      throws OrmException, IOException {
-    return execute(db, changeId, manager, true, true);
-  }
-
-  public Result execute(
-      ReviewDb db,
-      Change.Id changeId,
-      NoteDbUpdateManager manager,
-      boolean checkReadOnly,
-      boolean executeManager)
-      throws OrmException, IOException {
-    db = ReviewDbUtil.unwrapDb(db);
-    Change change = checkNoteDbState(ChangeNotes.readOneReviewDbChange(db, changeId));
-    if (change == null) {
-      throw new NoSuchChangeException(changeId);
-    }
-
-    String oldNoteDbStateStr = change.getNoteDbState();
-    Result r = manager.stageAndApplyDelta(change);
-    String newNoteDbStateStr = change.getNoteDbState();
-    if (newNoteDbStateStr == null) {
-      throw new OrmException(
-          String.format(
-              "Rebuilding change %s produced no writes to NoteDb: %s",
-              changeId, bundleReader.fromReviewDb(db, changeId)));
-    }
-    NoteDbChangeState newNoteDbState =
-        requireNonNull(NoteDbChangeState.parse(changeId, newNoteDbStateStr));
-    try {
-      db.changes()
-          .atomicUpdate(
-              changeId,
-              new AtomicUpdate<Change>() {
-                @Override
-                public Change update(Change change) {
-                  if (checkReadOnly) {
-                    NoteDbChangeState.checkNotReadOnly(change, skewMs);
-                  }
-                  String currNoteDbStateStr = change.getNoteDbState();
-                  if (Objects.equals(currNoteDbStateStr, newNoteDbStateStr)) {
-                    // Another thread completed the same rebuild we were about to.
-                    throw new AbortUpdateException();
-                  } else if (!Objects.equals(oldNoteDbStateStr, currNoteDbStateStr)) {
-                    // Another thread updated the state to something else.
-                    throw new ConflictingUpdateRuntimeException(change, oldNoteDbStateStr);
-                  }
-                  change.setNoteDbState(newNoteDbStateStr);
-                  return change;
-                }
-              });
-    } catch (ConflictingUpdateRuntimeException e) {
-      // Rethrow as an OrmException so the caller knows to use staged results. Strictly speaking
-      // they are not completely up to date, but result we send to the caller is the same as if this
-      // rebuild had executed before the other thread.
-      throw new ConflictingUpdateException(e);
-    } catch (AbortUpdateException e) {
-      if (newNoteDbState.isUpToDate(
-          manager.getChangeRepo().cmds.getRepoRefCache(),
-          manager.getAllUsersRepo().cmds.getRepoRefCache())) {
-        // If the state in ReviewDb matches NoteDb at this point, it means another thread
-        // successfully completed this rebuild. It's ok to not execute the update in this case,
-        // since the object referenced in the Result was flushed to the repo by whatever thread won
-        // the race.
-        return r;
-      }
-      // If the state doesn't match, that means another thread attempted this rebuild, but
-      // failed. Fall through and try to update the ref again.
-    }
-    if (migration.failChangeWrites()) {
-      // Don't even attempt to execute if read-only, it would fail anyway. But do throw an exception
-      // to the caller so they know to use the staged results instead of reading from the repo.
-      throw new OrmException(NoteDbUpdateManager.CHANGES_READ_ONLY);
-    }
-    if (executeManager) {
-      manager.execute();
-    }
-    return r;
-  }
-
-  static Change checkNoteDbState(Change c) throws OrmException {
-    // Can only rebuild a change if its primary storage is ReviewDb.
-    NoteDbChangeState s = NoteDbChangeState.parse(c);
-    if (s != null && s.getPrimaryStorage() != PrimaryStorage.REVIEW_DB) {
-      throw new OrmException(String.format("cannot rebuild change %s with state %s", c.getId(), s));
-    }
-    return c;
-  }
-
-  @Override
-  public void buildUpdates(NoteDbUpdateManager manager, ChangeBundle bundle)
-      throws IOException, OrmException {
-    manager.setCheckExpectedState(false).setRefLogMessage("Rebuilding change");
-    Change change = new Change(bundle.getChange());
-    if (bundle.getPatchSets().isEmpty()) {
-      throw new NoPatchSetsException(change.getId());
-    }
-    if (change.getLastUpdatedOn().compareTo(change.getCreatedOn()) < 0) {
-      // A bug in data migration might set created_on to the time of the migration. The
-      // correct timestamps were lost, but we can at least set it so created_on is not after
-      // last_updated_on.
-      // See https://bugs.chromium.org/p/gerrit/issues/detail?id=7397
-      change.setCreatedOn(change.getLastUpdatedOn());
-    }
-
-    // We will rebuild all events, except for draft comments, in buckets based on author and
-    // timestamp.
-    List<Event> events = new ArrayList<>();
-    ListMultimap<Account.Id, DraftCommentEvent> draftCommentEvents =
-        MultimapBuilder.hashKeys().arrayListValues().build();
-
-    events.addAll(getHashtagsEvents(change, manager));
-
-    // Delete ref only after hashtags have been read.
-    deleteChangeMetaRef(change, manager.getChangeRepo().cmds);
-    deleteDraftRefs(change, manager.getAllUsersRepo());
-
-    Integer minPsNum = getMinPatchSetNum(bundle);
-    TreeMap<PatchSet.Id, PatchSetEvent> patchSetEvents =
-        new TreeMap<>(ReviewDbUtil.intKeyOrdering());
-
-    for (PatchSet ps : bundle.getPatchSets()) {
-      PatchSetEvent pse = new PatchSetEvent(change, ps, manager.getChangeRepo().rw);
-      patchSetEvents.put(ps.getId(), pse);
-      events.add(pse);
-      for (Comment c : getComments(bundle, serverId, Status.PUBLISHED, ps)) {
-        CommentEvent e = new CommentEvent(c, change, ps, patchListCache);
-        events.add(e.addDep(pse));
-      }
-      for (Comment c : getComments(bundle, serverId, Status.DRAFT, ps)) {
-        DraftCommentEvent e = new DraftCommentEvent(c, change, ps, patchListCache);
-        draftCommentEvents.put(c.author.getId(), e);
-      }
-    }
-    ensurePatchSetOrder(patchSetEvents);
-
-    for (PatchSetApproval psa : bundle.getPatchSetApprovals()) {
-      PatchSetEvent pse = patchSetEvents.get(psa.getPatchSetId());
-      if (pse != null) {
-        events.add(new ApprovalEvent(psa, change.getCreatedOn()).addDep(pse));
-      }
-    }
-
-    for (Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> r :
-        bundle.getReviewers().asTable().cellSet()) {
-      events.add(new ReviewerEvent(r, change.getCreatedOn()));
-    }
-
-    Change noteDbChange = new Change(null, null, null, null, null);
-    for (ChangeMessage msg : bundle.getChangeMessages()) {
-      Event msgEvent = new ChangeMessageEvent(change, noteDbChange, msg, change.getCreatedOn());
-      if (msg.getPatchSetId() != null) {
-        PatchSetEvent pse = patchSetEvents.get(msg.getPatchSetId());
-        if (pse == null) {
-          continue; // Ignore events for missing patch sets.
-        }
-        msgEvent.addDep(pse);
-      }
-      events.add(msgEvent);
-    }
-
-    sortAndFillEvents(change, noteDbChange, bundle.getPatchSets(), events, minPsNum);
-
-    EventList<Event> el = new EventList<>();
-    for (Event e : events) {
-      if (!el.canAdd(e)) {
-        flushEventsToUpdate(manager, el, change);
-        checkState(el.canAdd(e));
-      }
-      el.add(e);
-    }
-    flushEventsToUpdate(manager, el, change);
-
-    EventList<DraftCommentEvent> plcel = new EventList<>();
-    for (Account.Id author : draftCommentEvents.keys()) {
-      for (DraftCommentEvent e : Ordering.natural().sortedCopy(draftCommentEvents.get(author))) {
-        if (!plcel.canAdd(e)) {
-          flushEventsToDraftUpdate(manager, plcel, change);
-          checkState(plcel.canAdd(e));
-        }
-        plcel.add(e);
-      }
-      flushEventsToDraftUpdate(manager, plcel, change);
-    }
-  }
-
-  private static Integer getMinPatchSetNum(ChangeBundle bundle) {
-    Integer minPsNum = null;
-    for (PatchSet ps : bundle.getPatchSets()) {
-      int n = ps.getId().get();
-      if (minPsNum == null || n < minPsNum) {
-        minPsNum = n;
-      }
-    }
-    return minPsNum;
-  }
-
-  private static void ensurePatchSetOrder(TreeMap<PatchSet.Id, PatchSetEvent> events) {
-    if (events.isEmpty()) {
-      return;
-    }
-    Iterator<PatchSetEvent> it = events.values().iterator();
-    PatchSetEvent curr = it.next();
-    while (it.hasNext()) {
-      PatchSetEvent next = it.next();
-      next.addDep(curr);
-      curr = next;
-    }
-  }
-
-  private static List<Comment> getComments(
-      ChangeBundle bundle, String serverId, PatchLineComment.Status status, PatchSet ps) {
-    return bundle
-        .getPatchLineComments()
-        .stream()
-        .filter(c -> c.getPatchSetId().equals(ps.getId()) && c.getStatus() == status)
-        .map(plc -> plc.asComment(serverId))
-        .sorted(CommentsUtil.COMMENT_ORDER)
-        .collect(toList());
-  }
-
-  private void sortAndFillEvents(
-      Change change,
-      Change noteDbChange,
-      ImmutableCollection<PatchSet> patchSets,
-      List<Event> events,
-      Integer minPsNum) {
-    Event finalUpdates = new FinalUpdatesEvent(change, noteDbChange, patchSets);
-    events.add(finalUpdates);
-    setPostSubmitDeps(events);
-    new EventSorter(events).sort();
-
-    // Ensure the first event in the list creates the change, setting the author and any required
-    // footers. Also force the creation time of the first patch set to match the creation time of
-    // the change.
-    Event first = events.get(0);
-    if (first instanceof PatchSetEvent && change.getOwner().equals(first.user)) {
-      first.when = change.getCreatedOn();
-      ((PatchSetEvent) first).createChange = true;
-    } else {
-      events.add(0, new CreateChangeEvent(change, minPsNum));
-    }
-
-    // Final pass to correct some inconsistencies.
-    //
-    // First, fill in any missing patch set IDs using the latest patch set of the change at the time
-    // of the event, because NoteDb can't represent actions with no associated patch set ID. This
-    // workaround is as if a user added a ChangeMessage on the change by replying from the latest
-    // patch set.
-    //
-    // Start with the first patch set that actually exists. If there are no patch sets at all,
-    // minPsNum will be null, so just bail and use 1 as the patch set ID.
-    //
-    // Second, ensure timestamps are nondecreasing, by copying the previous timestamp if this
-    // happens. This assumes that the only way this can happen is due to dependency constraints, and
-    // it is ok to give an event the same timestamp as one of its dependencies.
-    int ps = firstNonNull(minPsNum, 1);
-    for (int i = 0; i < events.size(); i++) {
-      Event e = events.get(i);
-      if (e.psId == null) {
-        e.psId = new PatchSet.Id(change.getId(), ps);
-      } else {
-        ps = Math.max(ps, e.psId.get());
-      }
-
-      if (i > 0) {
-        Event p = events.get(i - 1);
-        if (e.when.before(p.when)) {
-          e.when = p.when;
-        }
-      }
-    }
-  }
-
-  private void setPostSubmitDeps(List<Event> events) {
-    Optional<Event> submitEvent =
-        Lists.reverse(events).stream().filter(Event::isSubmit).findFirst();
-    if (submitEvent.isPresent()) {
-      events.stream().filter(Event::isPostSubmitApproval).forEach(e -> e.addDep(submitEvent.get()));
-    }
-  }
-
-  private void flushEventsToUpdate(
-      NoteDbUpdateManager manager, EventList<Event> events, Change change)
-      throws OrmException, IOException {
-    if (events.isEmpty()) {
-      return;
-    }
-    Comparator<String> labelNameComparator;
-    if (projectCache != null) {
-      labelNameComparator = projectCache.get(change.getProject()).getLabelTypes().nameComparator();
-    } else {
-      // No project cache available, bail and use natural ordering; there's no semantic difference
-      // anyway difference.
-      labelNameComparator = Ordering.natural();
-    }
-    ChangeUpdate update =
-        updateFactory.create(
-            change,
-            events.getAccountId(),
-            events.getRealAccountId(),
-            newAuthorIdent(events),
-            events.getWhen(),
-            labelNameComparator);
-    update.setAllowWriteToNewRef(true);
-    update.setPatchSetId(events.getPatchSetId());
-    update.setTag(events.getTag());
-    for (Event e : events) {
-      e.apply(update);
-    }
-    manager.add(update);
-    events.clear();
-  }
-
-  private void flushEventsToDraftUpdate(
-      NoteDbUpdateManager manager, EventList<DraftCommentEvent> events, Change change) {
-    if (events.isEmpty()) {
-      return;
-    }
-    ChangeDraftUpdate update =
-        draftUpdateFactory.create(
-            change,
-            events.getAccountId(),
-            events.getRealAccountId(),
-            newAuthorIdent(events),
-            events.getWhen());
-    update.setPatchSetId(events.getPatchSetId());
-    for (DraftCommentEvent e : events) {
-      e.applyDraft(update);
-    }
-    manager.add(update);
-    events.clear();
-  }
-
-  private PersonIdent newAuthorIdent(EventList<?> events) {
-    Account.Id id = events.getAccountId();
-    if (id == null) {
-      return new PersonIdent(serverIdent, events.getWhen());
-    }
-    return changeNoteUtil.newIdent(id, events.getWhen(), serverIdent);
-  }
-
-  private List<HashtagsEvent> getHashtagsEvents(Change change, NoteDbUpdateManager manager)
-      throws IOException {
-    String refName = changeMetaRef(change.getId());
-    Optional<ObjectId> old = manager.getChangeRepo().getObjectId(refName);
-    if (!old.isPresent()) {
-      return Collections.emptyList();
-    }
-
-    RevWalk rw = manager.getChangeRepo().rw;
-    List<HashtagsEvent> events = new ArrayList<>();
-    rw.reset();
-    rw.markStart(rw.parseCommit(old.get()));
-    for (RevCommit commit : rw) {
-      Account.Id authorId;
-      try {
-        authorId =
-            changeNoteUtil
-                .getLegacyChangeNoteRead()
-                .parseIdent(commit.getAuthorIdent(), change.getId());
-      } catch (ConfigInvalidException e) {
-        continue; // Corrupt data, no valid hashtags in this commit.
-      }
-      PatchSet.Id psId = parsePatchSetId(change, commit);
-      Set<String> hashtags = parseHashtags(commit);
-      if (authorId == null || psId == null || hashtags == null) {
-        continue;
-      }
-
-      Timestamp commitTime = new Timestamp(commit.getCommitterIdent().getWhen().getTime());
-      events.add(new HashtagsEvent(psId, authorId, commitTime, hashtags, change.getCreatedOn()));
-    }
-    return events;
-  }
-
-  private Set<String> parseHashtags(RevCommit commit) {
-    List<String> hashtagsLines = commit.getFooterLines(FOOTER_HASHTAGS);
-    if (hashtagsLines.isEmpty() || hashtagsLines.size() > 1) {
-      return null;
-    }
-
-    if (hashtagsLines.get(0).isEmpty()) {
-      return ImmutableSet.of();
-    }
-    return Sets.newHashSet(Splitter.on(',').split(hashtagsLines.get(0)));
-  }
-
-  private PatchSet.Id parsePatchSetId(Change change, RevCommit commit) {
-    List<String> psIdLines = commit.getFooterLines(FOOTER_PATCH_SET);
-    if (psIdLines.size() != 1) {
-      return null;
-    }
-    Integer psId = Ints.tryParse(psIdLines.get(0));
-    if (psId == null) {
-      return null;
-    }
-    return new PatchSet.Id(change.getId(), psId);
-  }
-
-  private void deleteChangeMetaRef(Change change, ChainedReceiveCommands cmds) throws IOException {
-    String refName = changeMetaRef(change.getId());
-    Optional<ObjectId> old = cmds.get(refName);
-    if (old.isPresent()) {
-      cmds.add(new ReceiveCommand(old.get(), ObjectId.zeroId(), refName));
-    }
-  }
-
-  private void deleteDraftRefs(Change change, OpenRepo allUsersRepo) throws IOException {
-    for (Ref r :
-        allUsersRepo
-            .repo
-            .getRefDatabase()
-            .getRefsByPrefix(RefNames.refsDraftCommentsPrefix(change.getId()))) {
-      allUsersRepo.cmds.add(new ReceiveCommand(r.getObjectId(), ObjectId.zeroId(), r.getName()));
-    }
-  }
-
-  static void createChange(ChangeUpdate update, Change change) {
-    update.setSubjectForCommit("Create change");
-    update.setChangeId(change.getKey().get());
-    update.setBranch(change.getDest().get());
-    update.setSubject(change.getOriginalSubject());
-    if (change.getRevertOf() != null) {
-      update.setRevertOf(change.getRevertOf().get());
-    }
-  }
-
-  @Override
-  public void rebuildReviewDb(ReviewDb db, Project.NameKey project, Change.Id changeId)
-      throws OrmException {
-    // TODO(dborowitz): Fail fast if changes tables are disabled in ReviewDb.
-    ChangeNotes notes = notesFactory.create(db, project, changeId);
-    ChangeBundle bundle = ChangeBundle.fromNotes(commentsUtil, notes);
-
-    db = ReviewDbUtil.unwrapDb(db);
-    db.changes().beginTransaction(changeId);
-    try {
-      Change c = db.changes().get(changeId);
-      if (c != null) {
-        PrimaryStorage ps = PrimaryStorage.of(c);
-        switch (ps) {
-          case REVIEW_DB:
-            return; // Nothing to do.
-          case NOTE_DB:
-            break; // Continue and rebuild.
-          default:
-            throw new OrmException("primary storage of " + changeId + " is " + ps);
-        }
-      } else {
-        c = notes.getChange();
-      }
-      db.changes().upsert(Collections.singleton(c));
-      putExactlyEntities(
-          db.changeMessages(), db.changeMessages().byChange(c.getId()), bundle.getChangeMessages());
-      putExactlyEntities(db.patchSets(), db.patchSets().byChange(c.getId()), bundle.getPatchSets());
-      putExactlyEntities(
-          db.patchSetApprovals(),
-          db.patchSetApprovals().byChange(c.getId()),
-          bundle.getPatchSetApprovals());
-      putExactlyEntities(
-          db.patchComments(),
-          db.patchComments().byChange(c.getId()),
-          bundle.getPatchLineComments());
-      db.commit();
-    } finally {
-      db.rollback();
-    }
-  }
-
-  private static <T, K extends Key<?>> void putExactlyEntities(
-      Access<T, K> access, Iterable<T> existing, Collection<T> ents) throws OrmException {
-    Set<K> toKeep = access.toMap(ents).keySet();
-    access.delete(
-        FluentIterable.from(existing).filter(e -> !toKeep.contains(access.primaryKey(e))));
-    access.upsert(ents);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/CommentEvent.java b/java/com/google/gerrit/server/notedb/rebuild/CommentEvent.java
deleted file mode 100644
index 8f7b387..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/CommentEvent.java
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
-
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListNotAvailableException;
-
-class CommentEvent extends Event {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  public final Comment c;
-  private final Change change;
-  private final PatchSet ps;
-  private final PatchListCache cache;
-
-  CommentEvent(Comment c, Change change, PatchSet ps, PatchListCache cache) {
-    super(
-        CommentsUtil.getCommentPsId(change.getId(), c),
-        c.author.getId(),
-        c.getRealAuthor().getId(),
-        c.writtenOn,
-        change.getCreatedOn(),
-        c.tag);
-    this.c = c;
-    this.change = change;
-    this.ps = ps;
-    this.cache = cache;
-  }
-
-  @Override
-  boolean uniquePerUpdate() {
-    return false;
-  }
-
-  @Override
-  protected boolean canHaveTag() {
-    return true;
-  }
-
-  @Override
-  void apply(ChangeUpdate update) {
-    checkUpdate(update);
-    if (c.revId == null) {
-      try {
-        setCommentRevId(c, cache, change, ps);
-      } catch (PatchListNotAvailableException e) {
-        logger.atWarning().log(
-            "Unable to determine parent commit of patch set %s (%s); omitting inline comment %s",
-            ps.getId(), ps.getRevision(), c);
-        return;
-      }
-    }
-    update.putComment(PatchLineComment.Status.PUBLISHED, c);
-  }
-
-  @Override
-  protected void addToString(ToStringHelper helper) {
-    helper.add("message", c.message);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateException.java b/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateException.java
deleted file mode 100644
index d8e7480..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2017 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.notedb.rebuild;
-
-import com.google.gwtorm.server.OrmException;
-
-/**
- * {@link com.google.gwtorm.server.OrmException} thrown by {@link ChangeRebuilder} when rebuilding a
- * change failed because another operation modified its {@link
- * com.google.gerrit.server.notedb.NoteDbChangeState}.
- */
-public class ConflictingUpdateException extends OrmException {
-  private static final long serialVersionUID = 1L;
-
-  // Always created from a ConflictingUpdateRuntimeException because it originates from an
-  // AtomicUpdate, which cannot throw checked exceptions.
-  ConflictingUpdateException(ConflictingUpdateRuntimeException cause) {
-    super(cause.getMessage(), cause);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateRuntimeException.java b/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateRuntimeException.java
deleted file mode 100644
index abfafa2..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateRuntimeException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwtorm.server.OrmRuntimeException;
-
-class ConflictingUpdateRuntimeException extends OrmRuntimeException {
-  private static final long serialVersionUID = 1L;
-
-  ConflictingUpdateRuntimeException(Change change, String expectedNoteDbState) {
-    super(
-        String.format(
-            "Expected change %s to have noteDbState %s but was %s",
-            change.getId(), expectedNoteDbState, change.getNoteDbState()));
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java b/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java
deleted file mode 100644
index d01071b..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gwtorm.server.OrmException;
-import java.io.IOException;
-
-class CreateChangeEvent extends Event {
-  private final Change change;
-
-  private static PatchSet.Id psId(Change change, Integer minPsNum) {
-    int n;
-    if (minPsNum == null) {
-      // There were no patch sets for the change at all, so something is very
-      // wrong. Bail and use 1 as the patch set.
-      n = 1;
-    } else {
-      n = minPsNum;
-    }
-    return new PatchSet.Id(change.getId(), n);
-  }
-
-  CreateChangeEvent(Change change, Integer minPsNum) {
-    super(
-        psId(change, minPsNum),
-        change.getOwner(),
-        change.getOwner(),
-        change.getCreatedOn(),
-        change.getCreatedOn(),
-        null);
-    this.change = change;
-  }
-
-  @Override
-  boolean uniquePerUpdate() {
-    return true;
-  }
-
-  @Override
-  void apply(ChangeUpdate update) throws IOException, OrmException {
-    checkUpdate(update);
-    ChangeRebuilderImpl.createChange(update, change);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/DraftCommentEvent.java b/java/com/google/gerrit/server/notedb/rebuild/DraftCommentEvent.java
deleted file mode 100644
index 2a2795d..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/DraftCommentEvent.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
-
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.notedb.ChangeDraftUpdate;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListNotAvailableException;
-
-class DraftCommentEvent extends Event {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  public final Comment c;
-  private final Change change;
-  private final PatchSet ps;
-  private final PatchListCache cache;
-
-  DraftCommentEvent(Comment c, Change change, PatchSet ps, PatchListCache cache) {
-    super(
-        CommentsUtil.getCommentPsId(change.getId(), c),
-        c.author.getId(),
-        c.getRealAuthor().getId(),
-        c.writtenOn,
-        change.getCreatedOn(),
-        c.tag);
-    this.c = c;
-    this.change = change;
-    this.ps = ps;
-    this.cache = cache;
-  }
-
-  @Override
-  boolean uniquePerUpdate() {
-    return false;
-  }
-
-  @Override
-  void apply(ChangeUpdate update) {
-    throw new UnsupportedOperationException();
-  }
-
-  void applyDraft(ChangeDraftUpdate draftUpdate) {
-    if (c.revId == null) {
-      try {
-        setCommentRevId(c, cache, change, ps);
-      } catch (PatchListNotAvailableException e) {
-        logger.atWarning().log(
-            "Unable to determine parent commit of patch set %s (%s);"
-                + " omitting draft inline comment %s",
-            ps.getId(), ps.getRevision(), c);
-        return;
-      }
-    }
-    draftUpdate.putComment(c);
-  }
-
-  @Override
-  protected void addToString(ToStringHelper helper) {
-    helper.add("message", c.message);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/Event.java b/java/com/google/gerrit/server/notedb/rebuild/Event.java
deleted file mode 100644
index 3957c5c..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/Event.java
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl.MAX_WINDOW_MS;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.collect.ComparisonChain;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.notedb.AbstractChangeUpdate;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gwtorm.server.OrmException;
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-abstract class Event implements Comparable<Event> {
-  // NOTE: EventList only supports direct subclasses, not an arbitrary
-  // hierarchy.
-
-  final Account.Id user;
-  final Account.Id realUser;
-  final String tag;
-  final boolean predatesChange;
-
-  /** Dependencies of this event; other events that must happen before this one. */
-  final List<Event> deps;
-
-  Timestamp when;
-  PatchSet.Id psId;
-
-  protected Event(
-      PatchSet.Id psId,
-      Account.Id effectiveUser,
-      Account.Id realUser,
-      Timestamp when,
-      Timestamp changeCreatedOn,
-      String tag) {
-    this.psId = psId;
-    this.user = effectiveUser;
-    this.realUser = realUser != null ? realUser : effectiveUser;
-    this.tag = tag;
-    // Truncate timestamps at the change's createdOn timestamp.
-    predatesChange = when.before(changeCreatedOn);
-    this.when = predatesChange ? changeCreatedOn : when;
-    deps = new ArrayList<>();
-  }
-
-  protected void checkUpdate(AbstractChangeUpdate update) {
-    checkState(
-        Objects.equals(update.getPatchSetId(), psId),
-        "cannot apply event for %s to update for %s",
-        update.getPatchSetId(),
-        psId);
-    checkState(
-        when.getTime() - update.getWhen().getTime() <= MAX_WINDOW_MS,
-        "event at %s outside update window starting at %s",
-        when,
-        update.getWhen());
-    checkState(
-        Objects.equals(update.getNullableAccountId(), user),
-        "cannot apply event by %s to update by %s",
-        user,
-        update.getNullableAccountId());
-  }
-
-  Event addDep(Event e) {
-    deps.add(e);
-    return this;
-  }
-
-  /**
-   * @return whether this event type must be unique per {@link ChangeUpdate}, i.e. there may be at
-   *     most one of this type.
-   */
-  abstract boolean uniquePerUpdate();
-
-  abstract void apply(ChangeUpdate update) throws OrmException, IOException;
-
-  protected boolean isPostSubmitApproval() {
-    return false;
-  }
-
-  protected boolean isSubmit() {
-    return false;
-  }
-
-  protected boolean canHaveTag() {
-    return false;
-  }
-
-  @Override
-  public String toString() {
-    ToStringHelper helper =
-        MoreObjects.toStringHelper(this)
-            .add("psId", psId)
-            .add("effectiveUser", user)
-            .add("realUser", realUser)
-            .add("when", when)
-            .add("tag", tag);
-    addToString(helper);
-    return helper.toString();
-  }
-
-  /** @param helper toString helper to add fields to */
-  protected void addToString(ToStringHelper helper) {}
-
-  @Override
-  public int compareTo(Event other) {
-    return ComparisonChain.start()
-        .compareFalseFirst(this.isFinalUpdates(), other.isFinalUpdates())
-        .compare(this.when, other.when)
-        .compareTrueFirst(isPatchSet(), isPatchSet())
-        .compareTrueFirst(this.predatesChange, other.predatesChange)
-        .compare(this.user, other.user, ReviewDbUtil.intKeyOrdering())
-        .compare(this.realUser, other.realUser, ReviewDbUtil.intKeyOrdering())
-        .compare(this.psId, other.psId, ReviewDbUtil.intKeyOrdering().nullsLast())
-        .result();
-  }
-
-  private boolean isPatchSet() {
-    return this instanceof PatchSetEvent;
-  }
-
-  private boolean isFinalUpdates() {
-    return this instanceof FinalUpdatesEvent;
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/EventList.java b/java/com/google/gerrit/server/notedb/rebuild/EventList.java
deleted file mode 100644
index e83814d..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/EventList.java
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.collect.Lists;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Objects;
-
-class EventList<E extends Event> implements Iterable<E> {
-  private final ArrayList<E> list = new ArrayList<>();
-  private boolean isSubmit;
-
-  @Override
-  public Iterator<E> iterator() {
-    return list.iterator();
-  }
-
-  void add(E e) {
-    list.add(e);
-    if (e.isSubmit()) {
-      isSubmit = true;
-    }
-  }
-
-  void clear() {
-    list.clear();
-    isSubmit = false;
-  }
-
-  boolean isEmpty() {
-    return list.isEmpty();
-  }
-
-  boolean canAdd(E e) {
-    if (isEmpty()) {
-      return true;
-    }
-    if (e instanceof FinalUpdatesEvent) {
-      return false; // FinalUpdatesEvent always gets its own update.
-    }
-
-    Event last = getLast();
-    if (!Objects.equals(e.user, last.user)
-        || !Objects.equals(e.realUser, last.realUser)
-        || !e.psId.equals(last.psId)) {
-      return false; // Different patch set or author.
-    }
-    if (e.canHaveTag() && canHaveTag() && !Objects.equals(e.tag, getTag())) {
-      // We should trust the tag field, and it doesn't match.
-      return false;
-    }
-    if (e.isPostSubmitApproval() && isSubmit) {
-      // Post-submit approvals must come after the update that submits.
-      return false;
-    }
-
-    long t = e.when.getTime();
-    long tFirst = getFirstTime();
-    long tLast = getLastTime();
-    checkArgument(t >= tLast, "event %s is before previous event in list %s", e, last);
-    if (t - tLast > ChangeRebuilderImpl.MAX_DELTA_MS
-        || t - tFirst > ChangeRebuilderImpl.MAX_WINDOW_MS) {
-      return false; // Too much time elapsed.
-    }
-
-    if (!e.uniquePerUpdate()) {
-      return true;
-    }
-    for (Event o : this) {
-      if (e.getClass() == o.getClass()) {
-        return false; // Only one event of this type allowed per update.
-      }
-    }
-
-    // TODO(dborowitz): Additional heuristics, like keeping events separate if
-    // they affect overlapping fields within a single entity.
-
-    return true;
-  }
-
-  Timestamp getWhen() {
-    return get(0).when;
-  }
-
-  PatchSet.Id getPatchSetId() {
-    PatchSet.Id id = requireNonNull(get(0).psId);
-    for (int i = 1; i < size(); i++) {
-      checkState(
-          get(i).psId.equals(id), "mismatched patch sets in EventList: %s != %s", id, get(i).psId);
-    }
-    return id;
-  }
-
-  Account.Id getAccountId() {
-    Account.Id id = get(0).user;
-    for (int i = 1; i < size(); i++) {
-      checkState(
-          Objects.equals(id, get(i).user),
-          "mismatched users in EventList: %s != %s",
-          id,
-          get(i).user);
-    }
-    return id;
-  }
-
-  Account.Id getRealAccountId() {
-    Account.Id id = get(0).realUser;
-    for (int i = 1; i < size(); i++) {
-      checkState(
-          Objects.equals(id, get(i).realUser),
-          "mismatched real users in EventList: %s != %s",
-          id,
-          get(i).realUser);
-    }
-    return id;
-  }
-
-  String getTag() {
-    for (E e : Lists.reverse(list)) {
-      if (e.tag != null) {
-        return e.tag;
-      }
-    }
-    return null;
-  }
-
-  private boolean canHaveTag() {
-    return list.stream().anyMatch(Event::canHaveTag);
-  }
-
-  private E get(int i) {
-    return list.get(i);
-  }
-
-  private int size() {
-    return list.size();
-  }
-
-  private E getLast() {
-    return list.get(list.size() - 1);
-  }
-
-  private long getLastTime() {
-    return getLast().when.getTime();
-  }
-
-  private long getFirstTime() {
-    return list.get(0).when.getTime();
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java b/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java
deleted file mode 100644
index 077a027..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.SetMultimap;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.PriorityQueue;
-
-/**
- * Helper to sort a list of events.
- *
- * <p>Events are sorted in two passes:
- *
- * <ol>
- *   <li>Sort by natural order (timestamp, patch set, author, etc.)
- *   <li>Postpone any events with dependencies to occur only after all of their dependencies, where
- *       this violates natural order.
- * </ol>
- *
- * {@link #sort()} modifies the event list in place (similar to {@link Collections#sort(List)}), but
- * does not modify any event. In particular, events might end up out of order with respect to
- * timestamp; callers are responsible for adjusting timestamps later if they prefer monotonicity.
- */
-class EventSorter {
-  private final List<Event> out;
-  private final LinkedHashSet<Event> sorted;
-  private ListMultimap<Event, Event> waiting;
-  private SetMultimap<Event, Event> deps;
-
-  EventSorter(List<Event> events) {
-    LinkedHashSet<Event> all = new LinkedHashSet<>(events);
-    out = events;
-
-    for (Event e : events) {
-      for (Event d : e.deps) {
-        checkArgument(all.contains(d), "dep %s of %s not in input list", d, e);
-      }
-    }
-
-    all.clear();
-    sorted = all; // Presized.
-  }
-
-  void sort() {
-    // First pass: sort by natural order.
-    PriorityQueue<Event> todo = new PriorityQueue<>(out);
-
-    // Populate waiting map after initial sort to preserve natural order.
-    waiting = MultimapBuilder.hashKeys().arrayListValues().build();
-    deps = MultimapBuilder.hashKeys().hashSetValues().build();
-    for (Event e : todo) {
-      for (Event d : e.deps) {
-        deps.put(e, d);
-        waiting.put(d, e);
-      }
-    }
-
-    // Second pass: enforce dependencies.
-    int size = out.size();
-    while (!todo.isEmpty()) {
-      process(todo.remove(), todo);
-    }
-    checkState(
-        sorted.size() == size, "event sort expected %s elements, got %s", size, sorted.size());
-
-    // Modify out in-place a la Collections#sort.
-    out.clear();
-    out.addAll(sorted);
-  }
-
-  void process(Event e, PriorityQueue<Event> todo) {
-    if (sorted.contains(e)) {
-      return; // Already emitted.
-    }
-    if (!deps.get(e).isEmpty()) {
-      // Not all events that e depends on have been emitted yet. Ignore e for
-      // now; it will get added back to the queue in the block below once its
-      // last dependency is processed.
-      return;
-    }
-
-    // All events that e depends on have been emitted, so e can be emitted.
-    sorted.add(e);
-
-    // Remove e from the dependency set of all events waiting on e, and add
-    // those events back to the queue in the original priority order for
-    // reconsideration.
-    for (Event w : waiting.get(e)) {
-      deps.get(w).remove(e);
-      todo.add(w);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java b/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java
deleted file mode 100644
index 55d5a31..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import static com.google.gerrit.reviewdb.server.ReviewDbUtil.intKeyOrdering;
-
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.collect.ImmutableCollection;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gwtorm.server.OrmException;
-import java.util.Objects;
-
-class FinalUpdatesEvent extends Event {
-  private final Change change;
-  private final Change noteDbChange;
-  private final ImmutableCollection<PatchSet> patchSets;
-
-  FinalUpdatesEvent(Change change, Change noteDbChange, ImmutableCollection<PatchSet> patchSets) {
-    super(
-        change.currentPatchSetId(),
-        change.getOwner(),
-        change.getOwner(),
-        change.getLastUpdatedOn(),
-        change.getCreatedOn(),
-        null);
-    this.change = change;
-    this.noteDbChange = noteDbChange;
-    this.patchSets = patchSets;
-  }
-
-  @Override
-  boolean uniquePerUpdate() {
-    return true;
-  }
-
-  @SuppressWarnings("deprecation")
-  @Override
-  void apply(ChangeUpdate update) throws OrmException {
-    if (!Objects.equals(change.getTopic(), noteDbChange.getTopic())) {
-      update.setTopic(change.getTopic());
-    }
-    if (!statusMatches()) {
-      // TODO(dborowitz): Stamp approximate approvals at this time.
-      update.fixStatus(change.getStatus());
-    }
-    if (change.isPrivate() != noteDbChange.isPrivate()) {
-      update.setPrivate(change.isPrivate());
-    }
-    if (change.isWorkInProgress() != noteDbChange.isWorkInProgress()) {
-      update.setWorkInProgress(change.isWorkInProgress());
-    }
-    if (change.getSubmissionId() != null && noteDbChange.getSubmissionId() == null) {
-      update.setSubmissionId(change.getSubmissionId());
-    }
-    if (!Objects.equals(change.getAssignee(), noteDbChange.getAssignee())) {
-      // TODO(dborowitz): Parse intermediate values out from messages.
-      update.setAssignee(change.getAssignee());
-    }
-    if (!patchSets.isEmpty() && !highestNumberedPatchSetIsCurrent()) {
-      update.setCurrentPatchSet();
-    }
-    if (!update.isEmpty()) {
-      update.setSubjectForCommit("Final NoteDb migration updates");
-    }
-  }
-
-  private boolean statusMatches() {
-    return Objects.equals(change.getStatus(), noteDbChange.getStatus());
-  }
-
-  private boolean highestNumberedPatchSetIsCurrent() {
-    PatchSet.Id max = patchSets.stream().map(PatchSet::getId).max(intKeyOrdering()).get();
-    return max.equals(change.currentPatchSetId());
-  }
-
-  @Override
-  protected boolean isSubmit() {
-    return change.getStatus() == Change.Status.MERGED;
-  }
-
-  @Override
-  protected void addToString(ToStringHelper helper) {
-    if (!statusMatches()) {
-      helper.add("status", change.getStatus());
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/GcAllUsers.java b/java/com/google/gerrit/server/notedb/rebuild/GcAllUsers.java
deleted file mode 100644
index f2a5cc6..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/GcAllUsers.java
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (C) 2018 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.notedb.rebuild;
-
-import static java.util.Objects.requireNonNull;
-import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_GC_SECTION;
-import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_AUTO;
-
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.GarbageCollectionResult;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GarbageCollection;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.function.Consumer;
-import org.eclipse.jgit.lib.Repository;
-
-@Singleton
-public class GcAllUsers {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private final AllUsersName allUsers;
-  private final GarbageCollection.Factory gcFactory;
-  private final GitRepositoryManager repoManager;
-
-  @Inject
-  GcAllUsers(
-      AllUsersName allUsers,
-      GarbageCollection.Factory gcFactory,
-      GitRepositoryManager repoManager) {
-    this.allUsers = allUsers;
-    this.gcFactory = gcFactory;
-    this.repoManager = repoManager;
-  }
-
-  public void runWithLogger() {
-    // Print log messages using logger, and skip progress.
-    run(s -> logger.atInfo().log(s), null);
-  }
-
-  public void run(PrintWriter writer) {
-    // Print both log messages and progress to given writer.
-    run(requireNonNull(writer)::println, writer);
-  }
-
-  private void run(Consumer<String> logOneLine, @Nullable PrintWriter progressWriter) {
-    if (!(repoManager instanceof LocalDiskRepositoryManager)) {
-      logOneLine.accept("Skipping GC of " + allUsers + "; not a local disk repo");
-      return;
-    }
-    if (!enableAutoGc(logOneLine)) {
-      logOneLine.accept(
-          "Skipping GC of "
-              + allUsers
-              + " due to disabling "
-              + CONFIG_GC_SECTION
-              + "."
-              + CONFIG_KEY_AUTO);
-      logOneLine.accept(
-          "If loading accounts is slow after the NoteDb migration, run `git gc` on "
-              + allUsers
-              + " manually");
-      return;
-    }
-
-    if (progressWriter == null) {
-      // Mimic log line from GarbageCollection.
-      logOneLine.accept("collecting garbage for \"" + allUsers + "\":\n");
-    }
-    GarbageCollectionResult result =
-        gcFactory.create().run(ImmutableList.of(allUsers), progressWriter);
-    if (!result.hasErrors()) {
-      return;
-    }
-    for (GarbageCollectionResult.Error e : result.getErrors()) {
-      switch (e.getType()) {
-        case GC_ALREADY_SCHEDULED:
-          logOneLine.accept("GC already scheduled for " + e.getProjectName());
-          break;
-        case GC_FAILED:
-          logOneLine.accept("GC failed for " + e.getProjectName());
-          break;
-        case REPOSITORY_NOT_FOUND:
-          logOneLine.accept(e.getProjectName() + " repo not found");
-          break;
-        default:
-          logOneLine.accept("GC failed for " + e.getProjectName() + ": " + e.getType());
-          break;
-      }
-    }
-  }
-
-  private boolean enableAutoGc(Consumer<String> logOneLine) {
-    try (Repository repo = repoManager.openRepository(allUsers)) {
-      return repo.getConfig().getInt(CONFIG_GC_SECTION, CONFIG_KEY_AUTO, -1) != 0;
-    } catch (IOException e) {
-      logOneLine.accept(
-          "Error reading config for " + allUsers + ":\n" + Throwables.getStackTraceAsString(e));
-      return false;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java b/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java
deleted file mode 100644
index 4f6f6ad..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gwtorm.server.OrmException;
-import java.sql.Timestamp;
-import java.util.Set;
-
-class HashtagsEvent extends Event {
-  private final Set<String> hashtags;
-
-  HashtagsEvent(
-      PatchSet.Id psId,
-      Account.Id who,
-      Timestamp when,
-      Set<String> hashtags,
-      Timestamp changeCreatdOn) {
-    super(
-        psId,
-        who,
-        who,
-        when,
-        changeCreatdOn,
-        // Somewhat confusingly, hashtags do not use the setTag method on
-        // AbstractChangeUpdate, so pass null as the tag.
-        null);
-    this.hashtags = hashtags;
-  }
-
-  @Override
-  boolean uniquePerUpdate() {
-    // Since these are produced from existing commits in the old NoteDb graph,
-    // we know that there must be one per commit in the rebuilt graph.
-    return true;
-  }
-
-  @Override
-  void apply(ChangeUpdate update) throws OrmException {
-    update.setHashtags(hashtags);
-  }
-
-  @Override
-  protected void addToString(ToStringHelper helper) {
-    helper.add("hashtags", hashtags);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/MigrationException.java b/java/com/google/gerrit/server/notedb/rebuild/MigrationException.java
deleted file mode 100644
index 0cf78be..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/MigrationException.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2017 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.notedb.rebuild;
-
-import java.io.IOException;
-
-/** Exception thrown by {@link NoteDbMigrator} when migration fails. */
-public class MigrationException extends IOException {
-  private static final long serialVersionUID = 1L;
-
-  MigrationException(String message) {
-    super(message);
-  }
-
-  MigrationException(String message, Throwable why) {
-    super(message, why);
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java b/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
deleted file mode 100644
index 01ac3e2..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
+++ /dev/null
@@ -1,1027 +0,0 @@
-// Copyright (C) 2017 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.notedb.rebuild;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.reviewdb.server.ReviewDbUtil.unwrapDb;
-import static com.google.gerrit.server.notedb.NotesMigration.SECTION_NOTE_DB;
-import static com.google.gerrit.server.notedb.NotesMigrationState.NOTE_DB;
-import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_NO_SEQUENCE;
-import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY;
-import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY;
-import static com.google.gerrit.server.notedb.NotesMigrationState.WRITE;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Comparator.comparing;
-import static java.util.Objects.requireNonNull;
-import static java.util.stream.Collectors.joining;
-import static java.util.stream.Collectors.toList;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Ordering;
-import com.google.common.collect.SetMultimap;
-import com.google.common.collect.Streams;
-import com.google.common.flogger.FluentLogger;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.gerrit.common.FormatUtil;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.git.LockFailureException;
-import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.InternalUser;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerConfigProvider;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.MutableNotesMigration;
-import com.google.gerrit.server.notedb.NoteDbTable;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager;
-import com.google.gerrit.server.notedb.NotesMigrationState;
-import com.google.gerrit.server.notedb.PrimaryStorageMigrator;
-import com.google.gerrit.server.notedb.PrimaryStorageMigrator.NoNoteDbStateException;
-import com.google.gerrit.server.notedb.RepoSequence;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.update.ChainedReceiveCommands;
-import com.google.gerrit.server.util.ManualRequestContext;
-import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.internal.storage.file.FileRepository;
-import org.eclipse.jgit.internal.storage.file.PackInserter;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.TextProgressMonitor;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.io.NullOutputStream;
-
-/** One stop shop for migrating a site's change storage from ReviewDb to NoteDb. */
-public class NoteDbMigrator implements AutoCloseable {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private static final String AUTO_MIGRATE = "autoMigrate";
-  private static final String TRIAL = "trial";
-
-  public static boolean getAutoMigrate(Config cfg) {
-    return cfg.getBoolean(SECTION_NOTE_DB, NoteDbTable.CHANGES.key(), AUTO_MIGRATE, false);
-  }
-
-  private static void setAutoMigrate(Config cfg, boolean autoMigrate) {
-    cfg.setBoolean(SECTION_NOTE_DB, NoteDbTable.CHANGES.key(), AUTO_MIGRATE, autoMigrate);
-  }
-
-  public static boolean getTrialMode(Config cfg) {
-    return cfg.getBoolean(SECTION_NOTE_DB, NoteDbTable.CHANGES.key(), TRIAL, false);
-  }
-
-  public static void setTrialMode(Config cfg, boolean trial) {
-    cfg.setBoolean(SECTION_NOTE_DB, NoteDbTable.CHANGES.key(), TRIAL, trial);
-  }
-
-  public static class Builder {
-    private final Config cfg;
-    private final SitePaths sitePaths;
-    private final Provider<PersonIdent> serverIdent;
-    private final AllUsersName allUsers;
-    private final SchemaFactory<ReviewDb> schemaFactory;
-    private final GitRepositoryManager repoManager;
-    private final NoteDbUpdateManager.Factory updateManagerFactory;
-    private final ChangeBundleReader bundleReader;
-    private final AllProjectsName allProjects;
-    private final InternalUser.Factory userFactory;
-    private final ThreadLocalRequestContext requestContext;
-    private final ChangeRebuilderImpl rebuilder;
-    private final WorkQueue workQueue;
-    private final MutableNotesMigration globalNotesMigration;
-    private final PrimaryStorageMigrator primaryStorageMigrator;
-    private final DynamicSet<NotesMigrationStateListener> listeners;
-
-    private int threads;
-    private ImmutableList<Project.NameKey> projects = ImmutableList.of();
-    private ImmutableList<Change.Id> changes = ImmutableList.of();
-    private OutputStream progressOut = NullOutputStream.INSTANCE;
-    private NotesMigrationState stopAtState;
-    private boolean trial;
-    private boolean forceRebuild;
-    private int sequenceGap = -1;
-    private boolean autoMigrate;
-
-    @Inject
-    Builder(
-        GerritServerConfigProvider configProvider,
-        SitePaths sitePaths,
-        @GerritPersonIdent Provider<PersonIdent> serverIdent,
-        AllUsersName allUsers,
-        SchemaFactory<ReviewDb> schemaFactory,
-        GitRepositoryManager repoManager,
-        NoteDbUpdateManager.Factory updateManagerFactory,
-        ChangeBundleReader bundleReader,
-        AllProjectsName allProjects,
-        ThreadLocalRequestContext requestContext,
-        InternalUser.Factory userFactory,
-        ChangeRebuilderImpl rebuilder,
-        WorkQueue workQueue,
-        MutableNotesMigration globalNotesMigration,
-        PrimaryStorageMigrator primaryStorageMigrator,
-        DynamicSet<NotesMigrationStateListener> listeners) {
-      // Reload gerrit.config/notedb.config on each migrator invocation, in case a previous
-      // migration in the same process modified the on-disk contents. This ensures the defaults for
-      // trial/autoMigrate get set correctly below.
-      this.cfg = configProvider.loadConfig();
-      this.sitePaths = sitePaths;
-      this.serverIdent = serverIdent;
-      this.allUsers = allUsers;
-      this.schemaFactory = schemaFactory;
-      this.repoManager = repoManager;
-      this.updateManagerFactory = updateManagerFactory;
-      this.bundleReader = bundleReader;
-      this.allProjects = allProjects;
-      this.requestContext = requestContext;
-      this.userFactory = userFactory;
-      this.rebuilder = rebuilder;
-      this.workQueue = workQueue;
-      this.globalNotesMigration = globalNotesMigration;
-      this.primaryStorageMigrator = primaryStorageMigrator;
-      this.listeners = listeners;
-      this.trial = getTrialMode(cfg);
-      this.autoMigrate = getAutoMigrate(cfg);
-    }
-
-    /**
-     * Set the number of threads used by parallelizable phases of the migration, such as rebuilding
-     * all changes.
-     *
-     * <p>Not all phases are parallelizable, and calling {@link #rebuild()} directly will do
-     * substantial work in the calling thread regardless of the number of threads configured.
-     *
-     * <p>By default, all work is done in the calling thread.
-     *
-     * @param threads thread count; if less than 2, all work happens in the calling thread.
-     * @return this.
-     */
-    public Builder setThreads(int threads) {
-      this.threads = threads;
-      return this;
-    }
-
-    /**
-     * Limit the set of projects that are processed.
-     *
-     * <p>Incompatible with {@link #setChanges(Collection)}.
-     *
-     * <p>By default, all projects will be processed.
-     *
-     * @param projects set of projects; if null or empty, all projects will be processed.
-     * @return this.
-     */
-    public Builder setProjects(@Nullable Collection<Project.NameKey> projects) {
-      this.projects = projects != null ? ImmutableList.copyOf(projects) : ImmutableList.of();
-      return this;
-    }
-
-    /**
-     * Limit the set of changes that are processed.
-     *
-     * <p>Incompatible with {@link #setProjects(Collection)}.
-     *
-     * <p>By default, all changes will be processed.
-     *
-     * @param changes set of changes; if null or empty, all changes will be processed.
-     * @return this.
-     */
-    public Builder setChanges(@Nullable Collection<Change.Id> changes) {
-      this.changes = changes != null ? ImmutableList.copyOf(changes) : ImmutableList.of();
-      return this;
-    }
-
-    /**
-     * Set output stream for progress monitors.
-     *
-     * <p>By default, there is no progress monitor output (although there may be other logs).
-     *
-     * @param progressOut output stream.
-     * @return this.
-     */
-    public Builder setProgressOut(OutputStream progressOut) {
-      this.progressOut = requireNonNull(progressOut);
-      return this;
-    }
-
-    /**
-     * Stop at a specific migration state, for testing only.
-     *
-     * @param stopAtState state to stop at.
-     * @return this.
-     */
-    @VisibleForTesting
-    public Builder setStopAtStateForTesting(NotesMigrationState stopAtState) {
-      this.stopAtState = stopAtState;
-      return this;
-    }
-
-    /**
-     * Rebuild in "trial mode": configure Gerrit to write to and read from NoteDb, but leave
-     * ReviewDb as the source of truth for all changes.
-     *
-     * <p>By default, trial mode is off, and NoteDb is the source of truth for all changes following
-     * the migration.
-     *
-     * @param trial whether to rebuild in trial mode.
-     * @return this.
-     */
-    public Builder setTrialMode(boolean trial) {
-      this.trial = trial;
-      return this;
-    }
-
-    /**
-     * Rebuild all changes in NoteDb from ReviewDb, even if Gerrit is currently configured to read
-     * from NoteDb.
-     *
-     * <p>Only supported if ReviewDb is still the source of truth for all changes.
-     *
-     * <p>By default, force rebuilding is off.
-     *
-     * @param forceRebuild whether to force rebuilding.
-     * @return this.
-     */
-    public Builder setForceRebuild(boolean forceRebuild) {
-      this.forceRebuild = forceRebuild;
-      return this;
-    }
-
-    /**
-     * Gap between ReviewDb change sequence numbers and NoteDb.
-     *
-     * <p>If NoteDb sequences are enabled in a running server, there is a race between the migration
-     * step that calls {@code nextChangeId()} to seed the ref, and other threads that call {@code
-     * nextChangeId()} to create new changes. In order to prevent these operations stepping on one
-     * another, we use this value to skip some predefined sequence numbers. This is strongly
-     * recommended in a running server.
-     *
-     * <p>If the migration takes place offline, there is no race with other threads, and this option
-     * may be set to 0. However, admins may still choose to use a gap, for example to make it easier
-     * to distinguish changes that were created before and after the NoteDb migration.
-     *
-     * <p>By default, uses the value from {@code noteDb.changes.initialSequenceGap} in {@code
-     * gerrit.config}, which defaults to 1000.
-     *
-     * @param sequenceGap sequence gap size; if negative, use the default.
-     * @return this.
-     */
-    public Builder setSequenceGap(int sequenceGap) {
-      this.sequenceGap = sequenceGap;
-      return this;
-    }
-
-    /**
-     * Enable auto-migration on subsequent daemon launches.
-     *
-     * <p>If true, prior to running any migration steps, sets the necessary configuration in {@code
-     * gerrit.config} to make {@code gerrit.war daemon} retry the migration on next startup, if it
-     * fails.
-     *
-     * @param autoMigrate whether to set auto-migration config.
-     * @return this.
-     */
-    public Builder setAutoMigrate(boolean autoMigrate) {
-      this.autoMigrate = autoMigrate;
-      return this;
-    }
-
-    public NoteDbMigrator build() throws MigrationException {
-      return new NoteDbMigrator(
-          sitePaths,
-          schemaFactory,
-          serverIdent,
-          allUsers,
-          repoManager,
-          updateManagerFactory,
-          bundleReader,
-          allProjects,
-          requestContext,
-          userFactory,
-          rebuilder,
-          globalNotesMigration,
-          primaryStorageMigrator,
-          listeners,
-          threads > 1
-              ? MoreExecutors.listeningDecorator(
-                  workQueue.createQueue(threads, "RebuildChange", true))
-              : MoreExecutors.newDirectExecutorService(),
-          projects,
-          changes,
-          progressOut,
-          stopAtState,
-          trial,
-          forceRebuild,
-          sequenceGap >= 0 ? sequenceGap : Sequences.getChangeSequenceGap(cfg),
-          autoMigrate);
-    }
-  }
-
-  private final FileBasedConfig gerritConfig;
-  private final FileBasedConfig noteDbConfig;
-  private final SchemaFactory<ReviewDb> schemaFactory;
-  private final Provider<PersonIdent> serverIdent;
-  private final AllUsersName allUsers;
-  private final GitRepositoryManager repoManager;
-  private final NoteDbUpdateManager.Factory updateManagerFactory;
-  private final ChangeBundleReader bundleReader;
-  private final AllProjectsName allProjects;
-  private final ThreadLocalRequestContext requestContext;
-  private final InternalUser.Factory userFactory;
-  private final ChangeRebuilderImpl rebuilder;
-  private final MutableNotesMigration globalNotesMigration;
-  private final PrimaryStorageMigrator primaryStorageMigrator;
-  private final DynamicSet<NotesMigrationStateListener> listeners;
-
-  private final ListeningExecutorService executor;
-  private final ImmutableList<Project.NameKey> projects;
-  private final ImmutableList<Change.Id> changes;
-  private final OutputStream progressOut;
-  private final NotesMigrationState stopAtState;
-  private final boolean trial;
-  private final boolean forceRebuild;
-  private final int sequenceGap;
-  private final boolean autoMigrate;
-
-  private NoteDbMigrator(
-      SitePaths sitePaths,
-      SchemaFactory<ReviewDb> schemaFactory,
-      Provider<PersonIdent> serverIdent,
-      AllUsersName allUsers,
-      GitRepositoryManager repoManager,
-      NoteDbUpdateManager.Factory updateManagerFactory,
-      ChangeBundleReader bundleReader,
-      AllProjectsName allProjects,
-      ThreadLocalRequestContext requestContext,
-      InternalUser.Factory userFactory,
-      ChangeRebuilderImpl rebuilder,
-      MutableNotesMigration globalNotesMigration,
-      PrimaryStorageMigrator primaryStorageMigrator,
-      DynamicSet<NotesMigrationStateListener> listeners,
-      ListeningExecutorService executor,
-      ImmutableList<Project.NameKey> projects,
-      ImmutableList<Change.Id> changes,
-      OutputStream progressOut,
-      NotesMigrationState stopAtState,
-      boolean trial,
-      boolean forceRebuild,
-      int sequenceGap,
-      boolean autoMigrate)
-      throws MigrationException {
-    if (!changes.isEmpty() && !projects.isEmpty()) {
-      throw new MigrationException("Cannot set both changes and projects");
-    }
-    if (sequenceGap < 0) {
-      throw new MigrationException("Sequence gap must be non-negative: " + sequenceGap);
-    }
-
-    this.schemaFactory = schemaFactory;
-    this.serverIdent = serverIdent;
-    this.allUsers = allUsers;
-    this.rebuilder = rebuilder;
-    this.repoManager = repoManager;
-    this.updateManagerFactory = updateManagerFactory;
-    this.bundleReader = bundleReader;
-    this.allProjects = allProjects;
-    this.requestContext = requestContext;
-    this.userFactory = userFactory;
-    this.globalNotesMigration = globalNotesMigration;
-    this.primaryStorageMigrator = primaryStorageMigrator;
-    this.listeners = listeners;
-    this.executor = executor;
-    this.projects = projects;
-    this.changes = changes;
-    this.progressOut = progressOut;
-    this.stopAtState = stopAtState;
-    this.trial = trial;
-    this.forceRebuild = forceRebuild;
-    this.sequenceGap = sequenceGap;
-    this.autoMigrate = autoMigrate;
-
-    // Stack notedb.config over gerrit.config, in the same way as GerritServerConfigProvider.
-    this.gerritConfig = new FileBasedConfig(sitePaths.gerrit_config.toFile(), FS.detect());
-    this.noteDbConfig =
-        new FileBasedConfig(gerritConfig, sitePaths.notedb_config.toFile(), FS.detect());
-  }
-
-  @Override
-  public void close() {
-    executor.shutdownNow();
-  }
-
-  public void migrate() throws OrmException, IOException {
-    if (!changes.isEmpty() || !projects.isEmpty()) {
-      throw new MigrationException(
-          "Cannot set changes or projects during full migration; call rebuild() instead");
-    }
-    Optional<NotesMigrationState> maybeState = loadState();
-    if (!maybeState.isPresent()) {
-      throw new MigrationException("Could not determine initial migration state");
-    }
-
-    NotesMigrationState state = maybeState.get();
-    if (trial && state.compareTo(READ_WRITE_NO_SEQUENCE) > 0) {
-      throw new MigrationException(
-          "Migration has already progressed past the endpoint of the \"trial mode\" state;"
-              + " NoteDb is already the primary storage for some changes");
-    }
-    if (forceRebuild && state.compareTo(READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY) > 0) {
-      throw new MigrationException(
-          "Cannot force rebuild changes; NoteDb is already the primary storage for some changes");
-    }
-    setControlFlags();
-
-    boolean rebuilt = false;
-    while (state.compareTo(NOTE_DB) < 0) {
-      if (state.equals(stopAtState)) {
-        return;
-      }
-      boolean stillNeedsRebuild = forceRebuild && !rebuilt;
-      if (trial && state.compareTo(READ_WRITE_NO_SEQUENCE) >= 0) {
-        if (stillNeedsRebuild && state == READ_WRITE_NO_SEQUENCE) {
-          // We're at the end state of trial mode, but still need a rebuild due to forceRebuild. Let
-          // the loop go one more time.
-        } else {
-          return;
-        }
-      }
-      switch (state) {
-        case REVIEW_DB:
-          state = turnOnWrites(state);
-          break;
-        case WRITE:
-          state = rebuildAndEnableReads(state);
-          rebuilt = true;
-          break;
-        case READ_WRITE_NO_SEQUENCE:
-          if (stillNeedsRebuild) {
-            state = rebuildAndEnableReads(state);
-            rebuilt = true;
-          } else {
-            state = enableSequences(state);
-          }
-          break;
-        case READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY:
-          if (stillNeedsRebuild) {
-            state = rebuildAndEnableReads(state);
-            rebuilt = true;
-          } else {
-            state = setNoteDbPrimary(state);
-          }
-          break;
-        case READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY:
-          // The only way we can get here is if there was a failure on a previous run of
-          // setNoteDbPrimary, since that method moves to NOTE_DB if it completes
-          // successfully. Assume that not all changes were converted and re-run the step.
-          // migrateToNoteDbPrimary is a relatively fast no-op for already-migrated changes, so this
-          // isn't actually repeating work.
-          state = setNoteDbPrimary(state);
-          break;
-        case NOTE_DB:
-          // Done!
-          break;
-        default:
-          throw new MigrationException(
-              "Migration out of the following state is not supported:\n" + state.toText());
-      }
-    }
-  }
-
-  private NotesMigrationState turnOnWrites(NotesMigrationState prev) throws IOException {
-    return saveState(prev, WRITE);
-  }
-
-  private NotesMigrationState rebuildAndEnableReads(NotesMigrationState prev)
-      throws OrmException, IOException {
-    rebuild();
-    return saveState(prev, READ_WRITE_NO_SEQUENCE);
-  }
-
-  private NotesMigrationState enableSequences(NotesMigrationState prev)
-      throws OrmException, IOException {
-    try (ReviewDb db = schemaFactory.open()) {
-      @SuppressWarnings("deprecation")
-      final int nextChangeId = db.nextChangeId();
-
-      RepoSequence seq =
-          new RepoSequence(
-              repoManager,
-              GitReferenceUpdated.DISABLED,
-              allProjects,
-              Sequences.NAME_CHANGES,
-              // If sequenceGap is 0, this writes into the sequence ref the same ID that is returned
-              // by the call to seq.next() below. If we actually used this as a change ID, that
-              // would be a problem, but we just discard it, so this is safe.
-              () -> nextChangeId + sequenceGap - 1,
-              1,
-              nextChangeId);
-      seq.next();
-    }
-    return saveState(prev, READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY);
-  }
-
-  private NotesMigrationState setNoteDbPrimary(NotesMigrationState prev)
-      throws MigrationException, OrmException, IOException {
-    checkState(
-        projects.isEmpty() && changes.isEmpty(),
-        "Should not have attempted setNoteDbPrimary with a subset of changes");
-    checkState(
-        prev == READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY
-            || prev == READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY,
-        "Unexpected start state for setNoteDbPrimary: %s",
-        prev);
-
-    // Before changing the primary storage of old changes, ensure new changes are created with
-    // NoteDb primary.
-    prev = saveState(prev, READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY);
-
-    Stopwatch sw = Stopwatch.createStarted();
-    logger.atInfo().log("Setting primary storage to NoteDb");
-    List<Change.Id> allChanges;
-    try (ReviewDb db = unwrapDb(schemaFactory.open())) {
-      allChanges = Streams.stream(db.changes().all()).map(Change::getId).collect(toList());
-    }
-
-    try (ContextHelper contextHelper = new ContextHelper()) {
-      List<ListenableFuture<Boolean>> futures =
-          allChanges
-              .stream()
-              .map(
-                  id ->
-                      executor.submit(
-                          () -> {
-                            try (ManualRequestContext ctx = contextHelper.open()) {
-                              try {
-                                primaryStorageMigrator.migrateToNoteDbPrimary(id);
-                              } catch (NoNoteDbStateException e) {
-                                if (canSkipPrimaryStorageMigration(
-                                    ctx.getReviewDbProvider().get(), id)) {
-                                  logger.atWarning().withCause(e).log(
-                                      "Change %s previously failed to rebuild;"
-                                          + " skipping primary storage migration",
-                                      id);
-                                } else {
-                                  throw e;
-                                }
-                              }
-                              return true;
-                            } catch (Exception e) {
-                              logger.atSevere().withCause(e).log(
-                                  "Error migrating primary storage for %s", id);
-                              return false;
-                            }
-                          }))
-              .collect(toList());
-
-      boolean ok = futuresToBoolean(futures, "Error migrating primary storage");
-      double t = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
-      logger.atInfo().log(
-          "Migrated primary storage of %d changes in %.01fs (%.01f/s)\n",
-          allChanges.size(), t, allChanges.size() / t);
-      if (!ok) {
-        throw new MigrationException("Migrating primary storage for some changes failed, see log");
-      }
-    }
-
-    return disableReviewDb(prev);
-  }
-
-  /**
-   * Checks whether a change is so corrupt that it can be completely skipped by the primary storage
-   * migration step.
-   *
-   * <p>To get to the point where this method is called from {@link #setNoteDbPrimary}, it means we
-   * attempted to rebuild it, and encountered an error that was then caught in {@link
-   * #rebuildProject} and skipped. As a result, there is no {@code noteDbState} field in the change
-   * by the time we get to {@link #setNoteDbPrimary}, so {@code migrateToNoteDbPrimary} throws an
-   * exception.
-   *
-   * <p>We have to do this hacky double-checking because we don't have a way for the rebuilding
-   * phase to communicate to the primary storage migration phase that the change is skippable. It
-   * would be possible to store this info in some field in this class, but there is no guarantee
-   * that the rebuild and primary storage migration phases are run in the same JVM invocation.
-   *
-   * <p>In an ideal world, we could do this through the {@link
-   * com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage} enum, having a separate value
-   * for errors. However, that would be an invasive change touching many non-migration-related parts
-   * of the NoteDb migration code, which is too risky to attempt in the stable branch where this bug
-   * had to be fixed.
-   *
-   * <p>As of this writing, the only case where this happens is when a change has no patch sets.
-   */
-  private static boolean canSkipPrimaryStorageMigration(ReviewDb db, Change.Id id) {
-    try {
-      return Iterables.isEmpty(unwrapDb(db).patchSets().byChange(id));
-    } catch (Exception e) {
-      logger.atSevere().withCause(e).log(
-          "Error checking if change %s can be skipped, assuming no", id);
-      return false;
-    }
-  }
-
-  private NotesMigrationState disableReviewDb(NotesMigrationState prev) throws IOException {
-    return saveState(prev, NOTE_DB, c -> setAutoMigrate(c, false));
-  }
-
-  private Optional<NotesMigrationState> loadState() throws IOException {
-    try {
-      gerritConfig.load();
-      noteDbConfig.load();
-      return NotesMigrationState.forConfig(noteDbConfig);
-    } catch (ConfigInvalidException | IllegalArgumentException e) {
-      logger.atWarning().withCause(e).log(
-          "error reading NoteDb migration options from %s", noteDbConfig.getFile());
-      return Optional.empty();
-    }
-  }
-
-  private NotesMigrationState saveState(
-      NotesMigrationState expectedOldState, NotesMigrationState newState) throws IOException {
-    return saveState(expectedOldState, newState, c -> {});
-  }
-
-  private NotesMigrationState saveState(
-      NotesMigrationState expectedOldState,
-      NotesMigrationState newState,
-      Consumer<Config> additionalUpdates)
-      throws IOException {
-    synchronized (globalNotesMigration) {
-      // This read-modify-write is racy. We're counting on the fact that no other Gerrit operation
-      // modifies gerrit.config, and hoping that admins don't either.
-      Optional<NotesMigrationState> actualOldState = loadState();
-      if (!actualOldState.equals(Optional.of(expectedOldState))) {
-        throw new MigrationException(
-            "Cannot move to new state:\n"
-                + newState.toText()
-                + "\n\n"
-                + "Expected this state in gerrit.config:\n"
-                + expectedOldState.toText()
-                + "\n\n"
-                + (actualOldState.isPresent()
-                    ? "But found this state:\n" + actualOldState.get().toText()
-                    : "But could not parse the current state"));
-      }
-
-      preStateChange(expectedOldState, newState);
-
-      newState.setConfigValues(noteDbConfig);
-      additionalUpdates.accept(noteDbConfig);
-      noteDbConfig.save();
-
-      // Only set in-memory state once it's been persisted to storage.
-      globalNotesMigration.setFrom(newState);
-      logger.atInfo().log("Migration state: %s => %s", expectedOldState, newState);
-
-      return newState;
-    }
-  }
-
-  private void preStateChange(NotesMigrationState oldState, NotesMigrationState newState)
-      throws IOException {
-    for (NotesMigrationStateListener listener : listeners) {
-      listener.preStateChange(oldState, newState);
-    }
-  }
-
-  private void setControlFlags() throws MigrationException {
-    synchronized (globalNotesMigration) {
-      try {
-        noteDbConfig.load();
-        setAutoMigrate(noteDbConfig, autoMigrate);
-        setTrialMode(noteDbConfig, trial);
-        noteDbConfig.save();
-      } catch (ConfigInvalidException | IOException e) {
-        throw new MigrationException("Error saving auto-migration config", e);
-      }
-    }
-  }
-
-  public void rebuild() throws MigrationException, OrmException {
-    if (!globalNotesMigration.commitChangeWrites()) {
-      throw new MigrationException("Cannot rebuild without noteDb.changes.write=true");
-    }
-    Stopwatch sw = Stopwatch.createStarted();
-    logger.atInfo().log("Rebuilding changes in NoteDb");
-
-    ImmutableListMultimap<Project.NameKey, Change.Id> changesByProject = getChangesByProject();
-    List<ListenableFuture<Boolean>> futures = new ArrayList<>();
-    try (ContextHelper contextHelper = new ContextHelper()) {
-      List<Project.NameKey> projectNames =
-          Ordering.usingToString().sortedCopy(changesByProject.keySet());
-      for (Project.NameKey project : projectNames) {
-        ListenableFuture<Boolean> future =
-            executor.submit(
-                () -> {
-                  try {
-                    return rebuildProject(contextHelper.getReviewDb(), changesByProject, project);
-                  } catch (Exception e) {
-                    logger.atSevere().withCause(e).log("Error rebuilding project %s", project);
-                    return false;
-                  }
-                });
-        futures.add(future);
-      }
-
-      boolean ok = futuresToBoolean(futures, "Error rebuilding projects");
-      double t = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
-      logger.atInfo().log(
-          "Rebuilt %d changes in %.01fs (%.01f/s)\n",
-          changesByProject.size(), t, changesByProject.size() / t);
-      if (!ok) {
-        throw new MigrationException("Rebuilding some changes failed, see log");
-      }
-    }
-  }
-
-  private ImmutableListMultimap<Project.NameKey, Change.Id> getChangesByProject()
-      throws OrmException {
-    // Memoize all changes so we can close the db connection and allow other threads to use the full
-    // connection pool.
-    SetMultimap<Project.NameKey, Change.Id> out =
-        MultimapBuilder.treeKeys(comparing(Project.NameKey::get))
-            .treeSetValues(comparing(Change.Id::get))
-            .build();
-    try (ReviewDb db = unwrapDb(schemaFactory.open())) {
-      if (!projects.isEmpty()) {
-        return byProject(db.changes().all(), c -> projects.contains(c.getProject()), out);
-      }
-      if (!changes.isEmpty()) {
-        return byProject(db.changes().get(changes), c -> true, out);
-      }
-      return byProject(db.changes().all(), c -> true, out);
-    }
-  }
-
-  private static ImmutableListMultimap<Project.NameKey, Change.Id> byProject(
-      Iterable<Change> changes,
-      Predicate<Change> pred,
-      SetMultimap<Project.NameKey, Change.Id> out) {
-    Streams.stream(changes).filter(pred).forEach(c -> out.put(c.getProject(), c.getId()));
-    return ImmutableListMultimap.copyOf(out);
-  }
-
-  private static ObjectInserter newPackInserter(Repository repo) {
-    if (!(repo instanceof FileRepository)) {
-      return repo.newObjectInserter();
-    }
-    PackInserter ins = ((FileRepository) repo).getObjectDatabase().newPackInserter();
-    ins.checkExisting(false);
-    return ins;
-  }
-
-  private boolean rebuildProject(
-      ReviewDb db,
-      ImmutableListMultimap<Project.NameKey, Change.Id> allChanges,
-      Project.NameKey project) {
-    checkArgument(allChanges.containsKey(project));
-    boolean ok = true;
-    ProgressMonitor pm =
-        new TextProgressMonitor(
-            new PrintWriter(new BufferedWriter(new OutputStreamWriter(progressOut, UTF_8))));
-    try (Repository changeRepo = repoManager.openRepository(project);
-        // Only use a PackInserter for the change repo, not All-Users.
-        //
-        // It's not possible to share a single inserter for All-Users across all project tasks, and
-        // we don't want to add one pack per project to All-Users. Adding many loose objects is
-        // preferable to many packs.
-        //
-        // Anyway, the number of objects inserted into All-Users is proportional to the number
-        // of pending draft comments, which should not be high (relative to the total number of
-        // changes), so the number of loose objects shouldn't be too unreasonable.
-        ObjectInserter changeIns = newPackInserter(changeRepo);
-        ObjectReader changeReader = changeIns.newReader();
-        RevWalk changeRw = new RevWalk(changeReader);
-        Repository allUsersRepo = repoManager.openRepository(allUsers);
-        ObjectInserter allUsersIns = allUsersRepo.newObjectInserter();
-        ObjectReader allUsersReader = allUsersIns.newReader();
-        RevWalk allUsersRw = new RevWalk(allUsersReader)) {
-      ChainedReceiveCommands changeCmds = new ChainedReceiveCommands(changeRepo);
-      ChainedReceiveCommands allUsersCmds = new ChainedReceiveCommands(allUsersRepo);
-
-      Collection<Change.Id> changes = allChanges.get(project);
-      pm.beginTask(FormatUtil.elide("Rebuilding " + project.get(), 50), changes.size());
-      int toSave = 0;
-      try {
-        for (Change.Id changeId : changes) {
-          // NoteDbUpdateManager assumes that all commands in its OpenRepo were added by itself, so
-          // we can't share the top-level ChainedReceiveCommands. Use a new set of commands sharing
-          // the same underlying repo, and copy commands back to the top-level
-          // ChainedReceiveCommands later. This also assumes that each ref in the final list of
-          // commands was only modified by a single NoteDbUpdateManager; since we use one manager
-          // per change, and each ref corresponds to exactly one change, this assumption should be
-          // safe.
-          ChainedReceiveCommands tmpChangeCmds =
-              new ChainedReceiveCommands(changeCmds.getRepoRefCache());
-          ChainedReceiveCommands tmpAllUsersCmds =
-              new ChainedReceiveCommands(allUsersCmds.getRepoRefCache());
-
-          try (NoteDbUpdateManager manager =
-              updateManagerFactory
-                  .create(project)
-                  .setAtomicRefUpdates(false)
-                  .setSaveObjects(false)
-                  .setChangeRepo(changeRepo, changeRw, changeIns, tmpChangeCmds)
-                  .setAllUsersRepo(allUsersRepo, allUsersRw, allUsersIns, tmpAllUsersCmds)) {
-            rebuild(db, changeId, manager);
-
-            // Executing with dryRun=true writes all objects to the underlying inserters and adds
-            // commands to the ChainedReceiveCommands. Afterwards, we can discard the manager, so we
-            // don't keep using any memory beyond what may be buffered in the PackInserter.
-            manager.execute(true);
-
-            tmpChangeCmds.getCommands().values().forEach(c -> addCommand(changeCmds, c));
-            tmpAllUsersCmds.getCommands().values().forEach(c -> addCommand(allUsersCmds, c));
-
-            toSave++;
-          } catch (NoPatchSetsException e) {
-            logger.atWarning().log(e.getMessage());
-          } catch (ConflictingUpdateException ex) {
-            logger.atWarning().log(
-                "Rebuilding detected a conflicting ReviewDb update for change %s;"
-                    + " will be auto-rebuilt at runtime",
-                changeId);
-          } catch (Throwable t) {
-            logger.atSevere().withCause(t).log("Failed to rebuild change %s", changeId);
-            ok = false;
-          }
-          pm.update(1);
-        }
-      } finally {
-        pm.endTask();
-      }
-
-      pm.beginTask(FormatUtil.elide("Saving " + project.get(), 50), ProgressMonitor.UNKNOWN);
-      try {
-        save(changeRepo, changeRw, changeIns, changeCmds);
-        save(allUsersRepo, allUsersRw, allUsersIns, allUsersCmds);
-        // This isn't really useful progress. If we passed a real ProgressMonitor to
-        // BatchRefUpdate#execute we might get something more incremental, but that doesn't allow us
-        // to specify the repo name in the task text.
-        pm.update(toSave);
-      } catch (LockFailureException e) {
-        logger.atWarning().log(
-            "Rebuilding detected a conflicting NoteDb update for the following refs, which will"
-                + " be auto-rebuilt at runtime: %s",
-            e.getFailedRefs().stream().distinct().sorted().collect(joining(", ")));
-      } catch (IOException e) {
-        logger.atSevere().withCause(e).log("Failed to save NoteDb state for %s", project);
-      } finally {
-        pm.endTask();
-      }
-    } catch (RepositoryNotFoundException e) {
-      logger.atWarning().log("Repository %s not found", project);
-    } catch (IOException e) {
-      logger.atSevere().withCause(e).log("Failed to rebuild project %s", project);
-    }
-    return ok;
-  }
-
-  private void rebuild(ReviewDb db, Change.Id changeId, NoteDbUpdateManager manager)
-      throws OrmException, IOException {
-    // Match ChangeRebuilderImpl#stage, but without calling manager.stage(), since that can only be
-    // called after building updates for all changes.
-    Change change =
-        ChangeRebuilderImpl.checkNoteDbState(ChangeNotes.readOneReviewDbChange(db, changeId));
-    if (change == null) {
-      // Could log here instead, but this matches the behavior of ChangeRebuilderImpl#stage.
-      throw new NoSuchChangeException(changeId);
-    }
-    rebuilder.buildUpdates(manager, bundleReader.fromReviewDb(db, changeId));
-
-    rebuilder.execute(db, changeId, manager, true, false);
-  }
-
-  private static void addCommand(ChainedReceiveCommands cmds, ReceiveCommand cmd) {
-    // ChainedReceiveCommands doesn't allow no-ops, but these occur when rebuilding a
-    // previously-rebuilt change.
-    if (!cmd.getOldId().equals(cmd.getNewId())) {
-      cmds.add(cmd);
-    }
-  }
-
-  private void save(Repository repo, RevWalk rw, ObjectInserter ins, ChainedReceiveCommands cmds)
-      throws IOException {
-    if (cmds.isEmpty()) {
-      return;
-    }
-    ins.flush();
-    BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
-    bru.setRefLogMessage("Migrate changes to NoteDb", false);
-    bru.setRefLogIdent(serverIdent.get());
-    bru.setAtomic(false);
-    bru.setAllowNonFastForwards(true);
-    cmds.addTo(bru);
-    RefUpdateUtil.executeChecked(bru, rw);
-  }
-
-  private static boolean futuresToBoolean(List<ListenableFuture<Boolean>> futures, String errMsg) {
-    try {
-      return Futures.allAsList(futures).get().stream().allMatch(b -> b);
-    } catch (InterruptedException | ExecutionException e) {
-      logger.atSevere().withCause(e).log(errMsg);
-      return false;
-    }
-  }
-
-  private class ContextHelper implements AutoCloseable {
-    private final Thread callingThread;
-    private ReviewDb db;
-    private Runnable closeDb;
-
-    ContextHelper() {
-      callingThread = Thread.currentThread();
-    }
-
-    ManualRequestContext open() throws OrmException {
-      return new ManualRequestContext(
-          userFactory.create(),
-          // Reuse the same lazily-opened ReviewDb on the original calling thread, otherwise open
-          // SchemaFactory in the normal way.
-          Thread.currentThread().equals(callingThread) ? this::getReviewDb : schemaFactory,
-          requestContext);
-    }
-
-    synchronized ReviewDb getReviewDb() throws OrmException {
-      if (db == null) {
-        ReviewDb actual = schemaFactory.open();
-        closeDb = actual::close;
-        db =
-            new ReviewDbWrapper(unwrapDb(actual)) {
-              @Override
-              public void close() {
-                // Closed by ContextHelper#close.
-              }
-            };
-      }
-      return db;
-    }
-
-    @Override
-    public synchronized void close() {
-      if (db != null) {
-        closeDb.run();
-        db = null;
-        closeDb = null;
-      }
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/NotesMigrationStateListener.java b/java/com/google/gerrit/server/notedb/rebuild/NotesMigrationStateListener.java
deleted file mode 100644
index aef30a2..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/NotesMigrationStateListener.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2017 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.notedb.rebuild;
-
-import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.server.notedb.NotesMigrationState;
-import java.io.IOException;
-
-/** Listener for state changes performed by {@link OnlineNoteDbMigrator}. */
-@ExtensionPoint
-public interface NotesMigrationStateListener {
-  /**
-   * Invoked just before saving the new migration state.
-   *
-   * @param oldState state prior to this state change.
-   * @param newState state after this state change.
-   * @throws IOException if an error occurred, which will cause the migration to abort. Exceptions
-   *     that should be considered non-fatal must be caught (and ideally logged) by the
-   *     implementation rather than thrown.
-   */
-  void preStateChange(NotesMigrationState oldState, NotesMigrationState newState)
-      throws IOException;
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/OnlineNoteDbMigrator.java b/java/com/google/gerrit/server/notedb/rebuild/OnlineNoteDbMigrator.java
deleted file mode 100644
index b5a8236..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/OnlineNoteDbMigrator.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (C) 2017 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.notedb.rebuild;
-
-import com.google.common.base.Stopwatch;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.OnlineUpgrader;
-import com.google.gerrit.server.index.VersionManager;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.google.inject.name.Named;
-import com.google.inject.name.Names;
-import java.util.concurrent.TimeUnit;
-import org.eclipse.jgit.lib.Config;
-
-@Singleton
-public class OnlineNoteDbMigrator implements LifecycleListener {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private static final String TRIAL = "OnlineNoteDbMigrator/trial";
-
-  public static class Module extends LifecycleModule {
-    private final boolean trial;
-
-    public Module(boolean trial) {
-      this.trial = trial;
-    }
-
-    @Override
-    public void configure() {
-      listener().to(OnlineNoteDbMigrator.class);
-      bindConstant().annotatedWith(Names.named(TRIAL)).to(trial);
-    }
-  }
-
-  private final GcAllUsers gcAllUsers;
-  private final OnlineUpgrader indexUpgrader;
-  private final Provider<NoteDbMigrator.Builder> migratorBuilderProvider;
-  private final boolean upgradeIndex;
-  private final boolean trial;
-
-  @Inject
-  OnlineNoteDbMigrator(
-      @GerritServerConfig Config cfg,
-      GcAllUsers gcAllUsers,
-      OnlineUpgrader indexUpgrader,
-      Provider<NoteDbMigrator.Builder> migratorBuilderProvider,
-      @Named(TRIAL) boolean trial) {
-    this.gcAllUsers = gcAllUsers;
-    this.indexUpgrader = indexUpgrader;
-    this.migratorBuilderProvider = migratorBuilderProvider;
-    this.upgradeIndex = VersionManager.getOnlineUpgrade(cfg);
-    this.trial = trial || NoteDbMigrator.getTrialMode(cfg);
-  }
-
-  @Override
-  public void start() {
-    Thread t = new Thread(this::migrate);
-    t.setDaemon(true);
-    t.setName(getClass().getSimpleName());
-    t.start();
-  }
-
-  private void migrate() {
-    logger.atInfo().log("Starting online NoteDb migration");
-    if (upgradeIndex) {
-      logger.atInfo().log(
-          "Online index schema upgrades will be deferred until NoteDb migration is complete");
-    }
-    Stopwatch sw = Stopwatch.createStarted();
-    // TODO(dborowitz): Tune threads, maybe expose a progress monitor somewhere.
-    try (NoteDbMigrator migrator =
-        migratorBuilderProvider.get().setAutoMigrate(true).setTrialMode(trial).build()) {
-      migrator.migrate();
-    } catch (Exception e) {
-      logger.atSevere().withCause(e).log("Error in online NoteDb migration");
-    }
-    gcAllUsers.runWithLogger();
-    logger.atInfo().log("Online NoteDb migration completed in %ss", sw.elapsed(TimeUnit.SECONDS));
-
-    if (upgradeIndex) {
-      logger.atInfo().log("Starting deferred index schema upgrades");
-      indexUpgrader.start();
-    }
-  }
-
-  @Override
-  public void stop() {
-    // Do nothing; upgrade process uses daemon threads and knows how to recover from failures on
-    // next attempt.
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java b/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
deleted file mode 100644
index acb80c0..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gwtorm.server.OrmException;
-import java.io.IOException;
-import java.util.List;
-import org.eclipse.jgit.errors.InvalidObjectIdException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-class PatchSetEvent extends Event {
-  private final Change change;
-  private final PatchSet ps;
-  private final RevWalk rw;
-  boolean createChange;
-
-  PatchSetEvent(Change change, PatchSet ps, RevWalk rw) {
-    super(
-        ps.getId(),
-        ps.getUploader(),
-        ps.getUploader(),
-        ps.getCreatedOn(),
-        change.getCreatedOn(),
-        null);
-    this.change = change;
-    this.ps = ps;
-    this.rw = rw;
-  }
-
-  @Override
-  boolean uniquePerUpdate() {
-    return true;
-  }
-
-  @Override
-  void apply(ChangeUpdate update) throws IOException, OrmException {
-    checkUpdate(update);
-    if (createChange) {
-      ChangeRebuilderImpl.createChange(update, change);
-    } else {
-      update.setSubject(change.getSubject());
-      update.setSubjectForCommit("Create patch set " + ps.getPatchSetId());
-    }
-    setRevision(update, ps);
-    update.setPsDescription(ps.getDescription());
-    List<String> groups = ps.getGroups();
-    if (!groups.isEmpty()) {
-      update.setGroups(ps.getGroups());
-    }
-  }
-
-  private void setRevision(ChangeUpdate update, PatchSet ps) throws IOException {
-    String rev = ps.getRevision().get();
-    String cert = ps.getPushCertificate();
-    ObjectId id;
-    try {
-      id = ObjectId.fromString(rev);
-    } catch (InvalidObjectIdException e) {
-      update.setRevisionForMissingCommit(rev, cert);
-      return;
-    }
-    try {
-      update.setCommit(rw, id, cert);
-    } catch (MissingObjectException e) {
-      update.setRevisionForMissingCommit(rev, cert);
-      return;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java b/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java
deleted file mode 100644
index 2ecf969..0000000
--- a/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.collect.Table;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gwtorm.server.OrmException;
-import java.io.IOException;
-import java.sql.Timestamp;
-
-class ReviewerEvent extends Event {
-  private Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer;
-
-  ReviewerEvent(
-      Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer,
-      Timestamp changeCreatedOn) {
-    super(
-        // Reviewers aren't generally associated with a particular patch set
-        // (although as an implementation detail they were in ReviewDb). Just
-        // use the latest patch set at the time of the event.
-        null,
-        reviewer.getColumnKey(),
-        // TODO(dborowitz): Real account ID shouldn't really matter for
-        // reviewers, but we might have to deal with this to avoid ChangeBundle
-        // diffs when run against real data.
-        reviewer.getColumnKey(),
-        reviewer.getValue(),
-        changeCreatedOn,
-        null);
-    this.reviewer = reviewer;
-  }
-
-  @Override
-  boolean uniquePerUpdate() {
-    return false;
-  }
-
-  @Override
-  void apply(ChangeUpdate update) throws IOException, OrmException {
-    checkUpdate(update);
-    update.putReviewer(reviewer.getColumnKey(), reviewer.getRowKey());
-  }
-
-  @Override
-  protected void addToString(ToStringHelper helper) {
-    helper.add("account", reviewer.getColumnKey()).add("state", reviewer.getRowKey());
-  }
-}
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index b1e0e3c..c583729 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -200,8 +200,8 @@
     }
     validatePatchSetId(psb);
 
-    PatchSet psEntityA = psa != null ? psUtil.get(db, notes, psa) : null;
-    PatchSet psEntityB = psb.get() == 0 ? new PatchSet(psb) : psUtil.get(db, notes, psb);
+    PatchSet psEntityA = psa != null ? psUtil.get(notes, psa) : null;
+    PatchSet psEntityB = psb.get() == 0 ? new PatchSet(psb) : psUtil.get(notes, psb);
     if (psEntityA != null || psEntityB != null) {
       try {
         permissionBackend.currentUser().change(notes).database(db).check(ChangePermission.READ);
@@ -308,7 +308,7 @@
       // proper rename detection between the patch sets.
       //
       history = new ArrayList<>();
-      for (PatchSet ps : psUtil.byChange(db, notes)) {
+      for (PatchSet ps : psUtil.byChange(notes)) {
         String name = fileName;
         if (psa != null) {
           switch (changeType) {
@@ -391,7 +391,7 @@
   }
 
   private void loadPublished(Map<Patch.Key, Patch> byKey, String file) throws OrmException {
-    for (Comment c : commentsUtil.publishedByChangeFile(db, notes, changeId, file)) {
+    for (Comment c : commentsUtil.publishedByChangeFile(notes, file)) {
       comments.include(notes.getChangeId(), c);
       PatchSet.Id psId = new PatchSet.Id(notes.getChangeId(), c.key.patchSetId);
       Patch.Key pKey = new Patch.Key(psId, c.key.filename);
@@ -404,7 +404,7 @@
 
   private void loadDrafts(Map<Patch.Key, Patch> byKey, Account.Id me, String file)
       throws OrmException {
-    for (Comment c : commentsUtil.draftByChangeFileAuthor(db, notes, file, me)) {
+    for (Comment c : commentsUtil.draftByChangeFileAuthor(notes, file, me)) {
       comments.include(notes.getChangeId(), c);
       PatchSet.Id psId = new PatchSet.Id(notes.getChangeId(), c.key.patchSetId);
       Patch.Key pKey = new Patch.Key(psId, c.key.filename);
diff --git a/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index 41bade6..f11f116 100644
--- a/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.client.UserIdentity;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.account.Emails;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -66,10 +65,10 @@
     return info;
   }
 
-  public PatchSetInfo get(ReviewDb db, ChangeNotes notes, PatchSet.Id psId)
+  public PatchSetInfo get(ChangeNotes notes, PatchSet.Id psId)
       throws PatchSetInfoNotAvailableException {
     try {
-      PatchSet patchSet = psUtil.get(db, notes, psId);
+      PatchSet patchSet = psUtil.get(notes, psId);
       return get(notes.getProjectName(), patchSet);
     } catch (OrmException e) {
       throw new PatchSetInfoNotAvailableException(e);
diff --git a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
index 47be6e3..249c872 100644
--- a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
+++ b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
@@ -345,7 +345,7 @@
     Project.NameKey p = projectState.getNameKey();
     Stream<ChangeNotesResult> s;
     try {
-      s = changeNotesFactory.scan(repo, db.get(), p);
+      s = changeNotesFactory.scan(repo, p);
     } catch (IOException e) {
       logger.atSevere().withCause(e).log(
           "Cannot load changes for project %s, assuming no changes are visible", p);
diff --git a/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java b/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java
index 4d89482..22cd84c 100644
--- a/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java
+++ b/java/com/google/gerrit/server/plugins/UniversalServerPluginProvider.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.plugins;
 
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.registration.PluginName;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.nio.file.Path;
@@ -28,10 +28,10 @@
 class UniversalServerPluginProvider implements ServerPluginProvider {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  private final DynamicSet<ServerPluginProvider> serverPluginProviders;
+  private final PluginSetContext<ServerPluginProvider> serverPluginProviders;
 
   @Inject
-  UniversalServerPluginProvider(DynamicSet<ServerPluginProvider> sf) {
+  UniversalServerPluginProvider(PluginSetContext<ServerPluginProvider> sf) {
     this.serverPluginProviders = sf;
   }
 
@@ -80,15 +80,16 @@
 
   private List<ServerPluginProvider> providersForHandlingPlugin(Path srcPath) {
     List<ServerPluginProvider> providers = new ArrayList<>();
-    for (ServerPluginProvider serverPluginProvider : serverPluginProviders) {
-      boolean handles = serverPluginProvider.handles(srcPath);
-      logger.atFine().log(
-          "File %s handled by %s ? => %s",
-          srcPath, serverPluginProvider.getProviderPluginName(), handles);
-      if (handles) {
-        providers.add(serverPluginProvider);
-      }
-    }
+    serverPluginProviders.runEach(
+        serverPluginProvider -> {
+          boolean handles = serverPluginProvider.handles(srcPath);
+          logger.atFine().log(
+              "File %s handled by %s ? => %s",
+              srcPath, serverPluginProvider.getProviderPluginName(), handles);
+          if (handles) {
+            providers.add(serverPluginProvider);
+          }
+        });
     return providers;
   }
 }
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index b16b076..36cffb3 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -229,7 +229,7 @@
   private Map<String, LabelType> labelSections;
   private ConfiguredMimeTypes mimeTypes;
   private Map<Project.NameKey, SubscribeSection> subscribeSections;
-  private List<CommentLinkInfoImpl> commentLinkSections;
+  private Map<String, CommentLinkInfoImpl> commentLinkSections;
   private List<ValidationError> validationErrors;
   private ObjectId rulesId;
   private long maxObjectSizeLimit;
@@ -278,7 +278,7 @@
   }
 
   public void addCommentLinkSection(CommentLinkInfoImpl commentLink) {
-    commentLinkSections.add(commentLink);
+    commentLinkSections.put(commentLink.name, commentLink);
   }
 
   private ProjectConfig(Project.NameKey projectName, @Nullable StoredConfig baseConfig) {
@@ -456,7 +456,7 @@
   }
 
   public Collection<CommentLinkInfoImpl> getCommentLinkSections() {
-    return commentLinkSections;
+    return commentLinkSections.values();
   }
 
   public ConfiguredMimeTypes getMimeTypes() {
@@ -1001,10 +1001,10 @@
 
   private void loadCommentLinkSections(Config rc) {
     Set<String> subsections = rc.getSubsections(COMMENTLINK);
-    commentLinkSections = new ArrayList<>(subsections.size());
+    commentLinkSections = new LinkedHashMap<>(subsections.size());
     for (String name : subsections) {
       try {
-        commentLinkSections.add(buildCommentLink(rc, name, false));
+        commentLinkSections.put(name, buildCommentLink(rc, name, false));
       } catch (PatternSyntaxException e) {
         error(
             new ValidationError(
@@ -1191,7 +1191,7 @@
 
   private void saveCommentLinkSections(Config rc) {
     if (commentLinkSections != null) {
-      for (CommentLinkInfoImpl cm : commentLinkSections) {
+      for (CommentLinkInfoImpl cm : commentLinkSections.values()) {
         rc.setString(COMMENTLINK, cm.name, KEY_MATCH, cm.match);
         if (!Strings.isNullOrEmpty(cm.html)) {
           rc.setString(COMMENTLINK, cm.name, KEY_HTML, cm.html);
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 0f5d938..4df953e 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -714,7 +714,7 @@
    */
   public Collection<PatchSet> patchSets() throws OrmException {
     if (patchSets == null) {
-      patchSets = psUtil.byChange(db, notes());
+      patchSets = psUtil.byChange(notes());
     }
     return patchSets;
   }
@@ -750,7 +750,7 @@
       if (!lazyLoad) {
         return ImmutableListMultimap.of();
       }
-      allApprovals = approvalsUtil.byChange(db, notes());
+      allApprovals = approvalsUtil.byChange(notes());
     }
     return allApprovals;
   }
@@ -858,7 +858,7 @@
       if (!lazyLoad) {
         return Collections.emptyList();
       }
-      publishedComments = commentsUtil.publishedByChange(db, notes());
+      publishedComments = commentsUtil.publishedByChange(notes());
     }
     return publishedComments;
   }
@@ -949,7 +949,7 @@
       if (!lazyLoad) {
         return Collections.emptyList();
       }
-      messages = cmUtil.byChange(db, notes());
+      messages = cmUtil.byChange(notes());
     }
     return messages;
   }
@@ -1097,7 +1097,7 @@
           }
         }
       } else {
-        for (Comment sc : commentsUtil.draftByChange(db, notes())) {
+        for (Comment sc : commentsUtil.draftByChange(notes())) {
           draftsByUser.put(sc.author.getId(), null);
         }
       }
diff --git a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index 17c23b6..a190ac5 100644
--- a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -250,7 +250,7 @@
     }
 
     if (includeAllReviewers) {
-      eventFactory.addAllReviewers(db, c, d.notes());
+      eventFactory.addAllReviewers(c, d.notes());
     }
 
     if (includeSubmitRecords) {
diff --git a/java/com/google/gerrit/server/quota/QuotaException.java b/java/com/google/gerrit/server/quota/QuotaException.java
index 56877b2..be13c0ec0 100644
--- a/java/com/google/gerrit/server/quota/QuotaException.java
+++ b/java/com/google/gerrit/server/quota/QuotaException.java
@@ -21,6 +21,8 @@
  * Can be propagated directly to the REST API.
  */
 public class QuotaException extends RestApiException {
+  private static final long serialVersionUID = 1L;
+
   public QuotaException(String reason) {
     super(reason);
   }
diff --git a/java/com/google/gerrit/server/restapi/account/CreateAccount.java b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
index 4185f36..2f9f2a9 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.api.accounts.AccountInput;
 import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -51,6 +50,7 @@
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
 import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
 import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.ssh.SshKeyCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -73,7 +73,7 @@
   private final SshKeyCache sshKeyCache;
   private final Provider<AccountsUpdate> accountsUpdateProvider;
   private final AccountLoader.Factory infoLoader;
-  private final DynamicSet<AccountExternalIdCreator> externalIdCreators;
+  private final PluginSetContext<AccountExternalIdCreator> externalIdCreators;
   private final Provider<GroupsUpdate> groupsUpdate;
   private final OutgoingEmailValidator validator;
 
@@ -85,7 +85,7 @@
       SshKeyCache sshKeyCache,
       @UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
       AccountLoader.Factory infoLoader,
-      DynamicSet<AccountExternalIdCreator> externalIdCreators,
+      PluginSetContext<AccountExternalIdCreator> externalIdCreators,
       @UserInitiated Provider<GroupsUpdate> groupsUpdate,
       OutgoingEmailValidator validator) {
     this.seq = seq;
@@ -131,9 +131,7 @@
     }
 
     extIds.add(ExternalId.createUsername(username, accountId, input.httpPassword));
-    for (AccountExternalIdCreator c : externalIdCreators) {
-      extIds.addAll(c.create(accountId, username, input.email));
-    }
+    externalIdCreators.runEach(c -> extIds.addAll(c.create(accountId, username, input.email)));
 
     try {
       accountsUpdateProvider
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java b/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
index 108ee0e..16ab812 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
@@ -184,12 +184,11 @@
         throws OrmException, PatchListNotAvailableException, PermissionBackendException {
       ImmutableList.Builder<CommentInfo> comments = ImmutableList.builder();
       boolean dirty = false;
-      for (Comment c : commentsUtil.draftByChangeAuthor(ctx.getDb(), ctx.getNotes(), accountId)) {
+      for (Comment c : commentsUtil.draftByChangeAuthor(ctx.getNotes(), accountId)) {
         dirty = true;
         PatchSet.Id psId = new PatchSet.Id(ctx.getChange().getId(), c.key.patchSetId);
-        setCommentRevId(
-            c, patchListCache, ctx.getChange(), psUtil.get(ctx.getDb(), ctx.getNotes(), psId));
-        commentsUtil.deleteComments(ctx.getDb(), ctx.getUpdate(psId), Collections.singleton(c));
+        setCommentRevId(c, patchListCache, ctx.getChange(), psUtil.get(ctx.getNotes(), psId));
+        commentsUtil.deleteComments(ctx.getUpdate(psId), Collections.singleton(c));
         comments.add(commentFormatter.format(c));
       }
       if (dirty) {
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java b/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
index 12b3797..59b3111 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
@@ -18,25 +18,21 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.IncludedIn;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
 
 @Singleton
 public class ChangeIncludedIn implements RestReadView<ChangeResource> {
-  private Provider<ReviewDb> db;
   private PatchSetUtil psUtil;
   private IncludedIn includedIn;
 
   @Inject
-  ChangeIncludedIn(Provider<ReviewDb> db, PatchSetUtil psUtil, IncludedIn includedIn) {
-    this.db = db;
+  ChangeIncludedIn(PatchSetUtil psUtil, IncludedIn includedIn) {
     this.psUtil = psUtil;
     this.includedIn = includedIn;
   }
@@ -44,7 +40,7 @@
   @Override
   public IncludedInInfo apply(ChangeResource rsrc)
       throws RestApiException, OrmException, IOException {
-    PatchSet ps = psUtil.current(db.get(), rsrc.getNotes());
+    PatchSet ps = psUtil.current(rsrc.getNotes());
     return includedIn.apply(rsrc.getProject(), ps.getRevision().get());
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 6399cde..c7a8a96 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -361,7 +361,7 @@
     if (input.keepReviewers && sourceChange != null) {
       ReviewerSet reviewerSet =
           approvalsUtil.getReviewers(
-              dbProvider.get(), changeNotesFactory.createChecked(dbProvider.get(), sourceChange));
+              changeNotesFactory.createChecked(dbProvider.get(), sourceChange));
       Set<Account.Id> reviewers =
           new HashSet<>(reviewerSet.byState(ReviewerStateInternal.REVIEWER));
       reviewers.add(sourceChange.getOwner());
diff --git a/java/com/google/gerrit/server/restapi/change/Comments.java b/java/com/google/gerrit/server/restapi/change/Comments.java
index f563cc6..22f376b 100644
--- a/java/com/google/gerrit/server/restapi/change/Comments.java
+++ b/java/com/google/gerrit/server/restapi/change/Comments.java
@@ -20,32 +20,27 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.CommentResource;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
 @Singleton
 public class Comments implements ChildCollection<RevisionResource, CommentResource> {
   private final DynamicMap<RestView<CommentResource>> views;
   private final ListRevisionComments list;
-  private final Provider<ReviewDb> dbProvider;
   private final CommentsUtil commentsUtil;
 
   @Inject
   Comments(
       DynamicMap<RestView<CommentResource>> views,
       ListRevisionComments list,
-      Provider<ReviewDb> dbProvider,
       CommentsUtil commentsUtil) {
     this.views = views;
     this.list = list;
-    this.dbProvider = dbProvider;
     this.commentsUtil = commentsUtil;
   }
 
@@ -65,8 +60,7 @@
     String uuid = id.get();
     ChangeNotes notes = rev.getNotes();
 
-    for (Comment c :
-        commentsUtil.publishedByPatchSet(dbProvider.get(), notes, rev.getPatchSet().getId())) {
+    for (Comment c : commentsUtil.publishedByPatchSet(notes, rev.getPatchSet().getId())) {
       if (uuid.equals(c.key.uuid)) {
         return new CommentResource(rev, c);
       }
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index fd25658..319a647 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -225,7 +225,7 @@
         } catch (AuthException e) {
           throw new UnprocessableEntityException("Read not permitted for " + input.baseChange);
         }
-        PatchSet ps = psUtil.current(db.get(), change);
+        PatchSet ps = psUtil.current(change);
         parentCommit = ObjectId.fromString(ps.getRevision().get());
         groups = ps.getGroups();
       } else if (input.baseCommit != null) {
diff --git a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
index 0e93c55..bab7fb0 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
@@ -112,7 +112,7 @@
     public boolean updateChange(ChangeContext ctx)
         throws ResourceNotFoundException, OrmException, UnprocessableEntityException,
             PatchListNotAvailableException {
-      PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+      PatchSet ps = psUtil.get(ctx.getNotes(), psId);
       if (ps == null) {
         throw new ResourceNotFoundException("patch set not found: " + psId);
       }
@@ -126,8 +126,7 @@
 
       setCommentRevId(comment, patchListCache, ctx.getChange(), ps);
 
-      commentsUtil.putComments(
-          ctx.getDb(), ctx.getUpdate(psId), Status.DRAFT, Collections.singleton(comment));
+      commentsUtil.putComments(ctx.getUpdate(psId), Status.DRAFT, Collections.singleton(comment));
       ctx.dontBumpLastUpdatedOn();
       return true;
     }
diff --git a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
index 1b85d0b..34b4383 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
@@ -141,7 +141,7 @@
     }
     in.baseChange = Strings.nullToEmpty(in.baseChange).trim();
 
-    PatchSet ps = psUtil.current(db.get(), rsrc.getNotes());
+    PatchSet ps = psUtil.current(rsrc.getNotes());
     Change change = rsrc.getChange();
     Project.NameKey project = change.getProject();
     Branch.NameKey dest = change.getDest();
@@ -216,7 +216,7 @@
     } catch (AuthException e) {
       throw new UnprocessableEntityException("Read not permitted for " + baseChange);
     }
-    return psUtil.current(db.get(), change);
+    return psUtil.current(change);
   }
 
   private RevCommit createMergeCommit(
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java b/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
index 7d68022..1e9b695 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
@@ -114,14 +114,14 @@
       return deletedAssignee != null ? deletedAssignee.getAccount().getId() : null;
     }
 
-    private void addMessage(ChangeContext ctx, ChangeUpdate update, IdentifiedUser deletedAssignee)
-        throws OrmException {
+    private void addMessage(
+        ChangeContext ctx, ChangeUpdate update, IdentifiedUser deletedAssignee) {
       ChangeMessage cmsg =
           ChangeMessagesUtil.newMessage(
               ctx,
               "Assignee deleted: " + deletedAssignee.getNameEmail(),
               ChangeMessagesUtil.TAG_DELETE_ASSIGNEE);
-      cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
+      cmUtil.addChangeMessage(update, cmsg);
     }
 
     @Override
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
index 881d106..c57f9ec 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
@@ -109,8 +109,7 @@
 
   private ChangeMessageInfo createUpdatedChangeMessageInfo(Change.Id id, int targetIdx)
       throws OrmException, PermissionBackendException {
-    List<ChangeMessage> messages =
-        changeMessagesUtil.byChange(dbProvider.get(), notesFactory.createChecked(id));
+    List<ChangeMessage> messages = changeMessagesUtil.byChange(notesFactory.createChecked(id));
     ChangeMessage updatedChangeMessage = messages.get(targetIdx);
     AccountLoader accountLoader = accountLoaderFactory.create(true);
     ChangeMessageInfo info = createChangeMessageInfo(updatedChangeMessage, accountLoader);
@@ -145,10 +144,9 @@
     }
 
     @Override
-    public boolean updateChange(ChangeContext ctx) throws OrmException {
+    public boolean updateChange(ChangeContext ctx) {
       PatchSet.Id psId = ctx.getChange().currentPatchSetId();
-      changeMessagesUtil.replaceChangeMessage(
-          ctx.getDb(), ctx.getUpdate(psId), targetMessageIdx, newMessage);
+      changeMessagesUtil.replaceChangeMessage(ctx.getUpdate(psId), targetMessageIdx, newMessage);
       return true;
     }
   }
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteChangeOp.java b/java/com/google/gerrit/server/restapi/change/DeleteChangeOp.java
index ae2d2eb..be7b0f7 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteChangeOp.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteChangeOp.java
@@ -70,7 +70,7 @@
     checkState(id == null, "cannot reuse DeleteChangeOp");
 
     id = ctx.getChange().getId();
-    Collection<PatchSet> patchSets = psUtil.byChange(ctx.getDb(), ctx.getNotes());
+    Collection<PatchSet> patchSets = psUtil.byChange(ctx.getNotes());
 
     ensureDeletable(ctx, id, patchSets);
     // Cleaning up is only possible as long as the change and its elements are
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteComment.java b/java/com/google/gerrit/server/restapi/change/DeleteComment.java
index 2ddf359..6279357 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteComment.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteComment.java
@@ -97,7 +97,7 @@
 
     ChangeNotes updatedNotes =
         notesFactory.createChecked(rsrc.getRevisionResource().getChange().getId());
-    List<Comment> changeComments = commentsUtil.publishedByChange(dbProvider.get(), updatedNotes);
+    List<Comment> changeComments = commentsUtil.publishedByChange(updatedNotes);
     Optional<Comment> updatedComment =
         changeComments.stream().filter(c -> c.key.equals(rsrc.getComment().key)).findFirst();
     if (!updatedComment.isPresent()) {
@@ -127,14 +127,10 @@
 
     @Override
     public boolean updateChange(ChangeContext ctx)
-        throws ResourceConflictException, OrmException, ResourceNotFoundException {
+        throws ResourceConflictException, ResourceNotFoundException {
       PatchSet.Id psId = ctx.getChange().currentPatchSetId();
       commentsUtil.deleteCommentByRewritingHistory(
-          ctx.getDb(),
-          ctx.getUpdate(psId),
-          rsrc.getComment().key,
-          rsrc.getPatchSet().getId(),
-          newMessage);
+          ctx.getUpdate(psId), rsrc.getComment().key, newMessage);
       return true;
     }
   }
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java b/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java
index f8e3add..0e62273 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java
@@ -91,18 +91,18 @@
     public boolean updateChange(ChangeContext ctx)
         throws ResourceNotFoundException, OrmException, PatchListNotAvailableException {
       Optional<Comment> maybeComment =
-          commentsUtil.getDraft(ctx.getDb(), ctx.getNotes(), ctx.getIdentifiedUser(), key);
+          commentsUtil.getDraft(ctx.getNotes(), ctx.getIdentifiedUser(), key);
       if (!maybeComment.isPresent()) {
         return false; // Nothing to do.
       }
       PatchSet.Id psId = new PatchSet.Id(ctx.getChange().getId(), key.patchSetId);
-      PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+      PatchSet ps = psUtil.get(ctx.getNotes(), psId);
       if (ps == null) {
         throw new ResourceNotFoundException("patch set not found: " + psId);
       }
       Comment c = maybeComment.get();
       setCommentRevId(c, patchListCache, ctx.getChange(), ps);
-      commentsUtil.deleteComments(ctx.getDb(), ctx.getUpdate(psId), Collections.singleton(c));
+      commentsUtil.deleteComments(ctx.getUpdate(psId), Collections.singleton(c));
       ctx.dontBumpLastUpdatedOn();
       return true;
     }
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteReviewerOp.java b/java/com/google/gerrit/server/restapi/change/DeleteReviewerOp.java
index 2cc4ce4..81ba1a0 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteReviewerOp.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteReviewerOp.java
@@ -125,11 +125,11 @@
     // Check of removing this reviewer (even if there is no vote processed by the loop below) is OK
     removeReviewerControl.checkRemoveReviewer(ctx.getNotes(), ctx.getUser(), reviewerId);
 
-    if (!approvalsUtil.getReviewers(ctx.getDb(), ctx.getNotes()).all().contains(reviewerId)) {
+    if (!approvalsUtil.getReviewers(ctx.getNotes()).all().contains(reviewerId)) {
       throw new ResourceNotFoundException();
     }
     currChange = ctx.getChange();
-    currPs = psUtil.current(ctx.getDb(), ctx.getNotes());
+    currPs = psUtil.current(ctx.getNotes());
 
     LabelTypes labelTypes = projectCache.checkedGet(ctx.getProject()).getLabelTypes(ctx.getNotes());
     // removing a reviewer will remove all her votes
@@ -171,7 +171,7 @@
 
     changeMessage =
         ChangeMessagesUtil.newMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_DELETE_REVIEWER);
-    cmUtil.addChangeMessage(ctx.getDb(), update, changeMessage);
+    cmUtil.addChangeMessage(update, changeMessage);
 
     return true;
   }
@@ -217,7 +217,7 @@
       db = ReviewDbUtil.unwrapDb(db);
       approvals = db.patchSetApprovals().byChange(changeId);
     } else {
-      approvals = approvalsUtil.byChange(ctx.getDb(), ctx.getNotes()).values();
+      approvals = approvalsUtil.byChange(ctx.getNotes()).values();
     }
 
     return Iterables.filter(approvals, psa -> accountId.equals(psa.getAccountId()));
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteVote.java b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
index 57649fad..1f770e6 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteVote.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
@@ -169,7 +169,7 @@
             PermissionBackendException {
       change = ctx.getChange();
       PatchSet.Id psId = change.currentPatchSetId();
-      ps = psUtil.current(db.get(), ctx.getNotes());
+      ps = psUtil.current(ctx.getNotes());
 
       boolean found = false;
       LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes());
@@ -218,7 +218,7 @@
       msg.append(" by ").append(userFactory.create(accountId).getNameEmail()).append("\n");
       changeMessage =
           ChangeMessagesUtil.newMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_DELETE_VOTE);
-      cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), changeMessage);
+      cmUtil.addChangeMessage(ctx.getUpdate(psId), changeMessage);
 
       return true;
     }
diff --git a/java/com/google/gerrit/server/restapi/change/DraftComments.java b/java/com/google/gerrit/server/restapi/change/DraftComments.java
index b8e24a5..9f06252 100644
--- a/java/com/google/gerrit/server/restapi/change/DraftComments.java
+++ b/java/com/google/gerrit/server/restapi/change/DraftComments.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.change.DraftCommentResource;
@@ -36,7 +35,6 @@
   private final DynamicMap<RestView<DraftCommentResource>> views;
   private final Provider<CurrentUser> user;
   private final ListRevisionDrafts list;
-  private final Provider<ReviewDb> dbProvider;
   private final CommentsUtil commentsUtil;
 
   @Inject
@@ -44,12 +42,10 @@
       DynamicMap<RestView<DraftCommentResource>> views,
       Provider<CurrentUser> user,
       ListRevisionDrafts list,
-      Provider<ReviewDb> dbProvider,
       CommentsUtil commentsUtil) {
     this.views = views;
     this.user = user;
     this.list = list;
-    this.dbProvider = dbProvider;
     this.commentsUtil = commentsUtil;
   }
 
@@ -71,7 +67,7 @@
     String uuid = id.get();
     for (Comment c :
         commentsUtil.draftByPatchSetAuthor(
-            dbProvider.get(), rev.getPatchSet().getId(), rev.getAccountId(), rev.getNotes())) {
+            rev.getPatchSet().getId(), rev.getAccountId(), rev.getNotes())) {
       if (uuid.equals(c.key.uuid)) {
         return new DraftCommentResource(rev, c);
       }
diff --git a/java/com/google/gerrit/server/restapi/change/Files.java b/java/com/google/gerrit/server/restapi/change/Files.java
index b374fdc..5f2c370 100644
--- a/java/com/google/gerrit/server/restapi/change/Files.java
+++ b/java/com/google/gerrit/server/restapi/change/Files.java
@@ -34,7 +34,6 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.AccountPatchReviewStore;
@@ -114,7 +113,6 @@
     @Option(name = "-q")
     String query;
 
-    private final Provider<ReviewDb> db;
     private final Provider<CurrentUser> self;
     private final FileInfoJson fileInfoJson;
     private final Revisions revisions;
@@ -125,7 +123,6 @@
 
     @Inject
     ListFiles(
-        Provider<ReviewDb> db,
         Provider<CurrentUser> self,
         FileInfoJson fileInfoJson,
         Revisions revisions,
@@ -133,7 +130,6 @@
         PatchListCache patchListCache,
         PatchSetUtil psUtil,
         PluginItemContext<AccountPatchReviewStore> accountPatchReviewStore) {
-      this.db = db;
       this.self = self;
       this.fileInfoJson = fileInfoJson;
       this.revisions = revisions;
@@ -268,7 +264,7 @@
           RevWalk rw = new RevWalk(reader);
           TreeWalk tw = new TreeWalk(reader)) {
         Change change = resource.getChange();
-        PatchSet patchSet = psUtil.get(db.get(), resource.getNotes(), old);
+        PatchSet patchSet = psUtil.get(resource.getNotes(), old);
         if (patchSet == null) {
           throw new PatchListNotAvailableException(
               String.format(
diff --git a/java/com/google/gerrit/server/restapi/change/GetContent.java b/java/com/google/gerrit/server/restapi/change/GetContent.java
index 6b9bf17..c133581 100644
--- a/java/com/google/gerrit/server/restapi/change/GetContent.java
+++ b/java/com/google/gerrit/server/restapi/change/GetContent.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.FileContentUtil;
 import com.google.gerrit.server.change.FileResource;
@@ -33,7 +32,6 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import java.io.IOException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
@@ -43,7 +41,6 @@
 import org.kohsuke.args4j.Option;
 
 public class GetContent implements RestReadView<FileResource> {
-  private final Provider<ReviewDb> db;
   private final GitRepositoryManager gitManager;
   private final PatchSetUtil psUtil;
   private final FileContentUtil fileContentUtil;
@@ -54,12 +51,10 @@
 
   @Inject
   GetContent(
-      Provider<ReviewDb> db,
       GitRepositoryManager gitManager,
       PatchSetUtil psUtil,
       FileContentUtil fileContentUtil,
       ProjectCache projectCache) {
-    this.db = db;
     this.gitManager = gitManager;
     this.psUtil = psUtil;
     this.fileContentUtil = fileContentUtil;
@@ -90,7 +85,7 @@
 
   private String getMessage(ChangeNotes notes) throws OrmException, IOException {
     Change.Id changeId = notes.getChangeId();
-    PatchSet ps = psUtil.current(db.get(), notes);
+    PatchSet ps = psUtil.current(notes);
     if (ps == null) {
       throw new NoSuchChangeException(changeId);
     }
@@ -106,7 +101,7 @@
 
   private byte[] getMergeList(ChangeNotes notes) throws OrmException, IOException {
     Change.Id changeId = notes.getChangeId();
-    PatchSet ps = psUtil.current(db.get(), notes);
+    PatchSet ps = psUtil.current(notes);
     if (ps == null) {
       throw new NoSuchChangeException(changeId);
     }
diff --git a/java/com/google/gerrit/server/restapi/change/GetRelated.java b/java/com/google/gerrit/server/restapi/change/GetRelated.java
index 30fbf39..9a65165 100644
--- a/java/com/google/gerrit/server/restapi/change/GetRelated.java
+++ b/java/com/google/gerrit/server/restapi/change/GetRelated.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommonConverters;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.RevisionResource;
@@ -50,7 +49,6 @@
 
 @Singleton
 public class GetRelated implements RestReadView<RevisionResource> {
-  private final Provider<ReviewDb> db;
   private final Provider<InternalChangeQuery> queryProvider;
   private final PatchSetUtil psUtil;
   private final RelatedChangesSorter sorter;
@@ -58,12 +56,10 @@
 
   @Inject
   GetRelated(
-      Provider<ReviewDb> db,
       Provider<InternalChangeQuery> queryProvider,
       PatchSetUtil psUtil,
       RelatedChangesSorter sorter,
       IndexConfig indexConfig) {
-    this.db = db;
     this.queryProvider = queryProvider;
     this.psUtil = psUtil;
     this.sorter = sorter;
@@ -81,7 +77,7 @@
 
   private List<RelatedChangeAndCommitInfo> getRelated(RevisionResource rsrc)
       throws OrmException, IOException, PermissionBackendException {
-    Set<String> groups = getAllGroups(rsrc.getNotes(), db.get(), psUtil);
+    Set<String> groups = getAllGroups(rsrc.getNotes(), psUtil);
     if (groups.isEmpty()) {
       return Collections.emptyList();
     }
@@ -125,13 +121,9 @@
   }
 
   @VisibleForTesting
-  public static Set<String> getAllGroups(ChangeNotes notes, ReviewDb db, PatchSetUtil psUtil)
+  public static Set<String> getAllGroups(ChangeNotes notes, PatchSetUtil psUtil)
       throws OrmException {
-    return psUtil
-        .byChange(db, notes)
-        .stream()
-        .flatMap(ps -> ps.getGroups().stream())
-        .collect(toSet());
+    return psUtil.byChange(notes).stream().flatMap(ps -> ps.getGroups().stream()).collect(toSet());
   }
 
   private void reloadChangeIfStale(List<ChangeData> cds, PatchSet wantedPs) throws OrmException {
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeComments.java b/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
index 40f4642..42af722 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
@@ -57,6 +57,6 @@
         .setFillAccounts(true)
         .setFillPatchSet(true)
         .newCommentFormatter()
-        .format(commentsUtil.publishedByChange(db.get(), cd.notes()));
+        .format(commentsUtil.publishedByChange(cd.notes()));
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java b/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java
index a524f6d..280277c 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java
@@ -57,7 +57,7 @@
     }
     ChangeData cd = changeDataFactory.create(db.get(), rsrc.getNotes());
     List<Comment> drafts =
-        commentsUtil.draftByChangeAuthor(db.get(), cd.notes(), rsrc.getUser().getAccountId());
+        commentsUtil.draftByChangeAuthor(cd.notes(), rsrc.getUser().getAccountId());
     return commentJson
         .get()
         .setFillAccounts(false)
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
index 39c12f7..ba09281 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
@@ -19,30 +19,24 @@
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.List;
 import java.util.stream.Collectors;
 
 @Singleton
 public class ListChangeMessages implements RestReadView<ChangeResource> {
-  private final Provider<ReviewDb> dbProvider;
   private final ChangeMessagesUtil changeMessagesUtil;
   private final AccountLoader accountLoader;
 
   @Inject
   public ListChangeMessages(
-      Provider<ReviewDb> dbProvider,
-      ChangeMessagesUtil changeMessagesUtil,
-      AccountLoader.Factory accountLoaderFactory) {
-    this.dbProvider = dbProvider;
+      ChangeMessagesUtil changeMessagesUtil, AccountLoader.Factory accountLoaderFactory) {
     this.changeMessagesUtil = changeMessagesUtil;
     this.accountLoader = accountLoaderFactory.create(true);
   }
@@ -50,8 +44,7 @@
   @Override
   public List<ChangeMessageInfo> apply(ChangeResource resource)
       throws OrmException, PermissionBackendException {
-    List<ChangeMessage> messages =
-        changeMessagesUtil.byChange(dbProvider.get(), resource.getNotes());
+    List<ChangeMessage> messages = changeMessagesUtil.byChange(resource.getNotes());
     List<ChangeMessageInfo> messageInfos =
         messages
             .stream()
diff --git a/java/com/google/gerrit/server/restapi/change/ListReviewers.java b/java/com/google/gerrit/server/restapi/change/ListReviewers.java
index 99d8746..725a89b 100644
--- a/java/com/google/gerrit/server/restapi/change/ListReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/ListReviewers.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.ReviewerJson;
@@ -26,7 +25,6 @@
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -34,18 +32,13 @@
 
 @Singleton
 class ListReviewers implements RestReadView<ChangeResource> {
-  private final Provider<ReviewDb> dbProvider;
   private final ApprovalsUtil approvalsUtil;
   private final ReviewerJson json;
   private final ReviewerResource.Factory resourceFactory;
 
   @Inject
   ListReviewers(
-      Provider<ReviewDb> dbProvider,
-      ApprovalsUtil approvalsUtil,
-      ReviewerResource.Factory resourceFactory,
-      ReviewerJson json) {
-    this.dbProvider = dbProvider;
+      ApprovalsUtil approvalsUtil, ReviewerResource.Factory resourceFactory, ReviewerJson json) {
     this.approvalsUtil = approvalsUtil;
     this.resourceFactory = resourceFactory;
     this.json = json;
@@ -55,8 +48,7 @@
   public List<ReviewerInfo> apply(ChangeResource rsrc)
       throws OrmException, PermissionBackendException {
     Map<String, ReviewerResource> reviewers = new LinkedHashMap<>();
-    ReviewDb db = dbProvider.get();
-    for (Account.Id accountId : approvalsUtil.getReviewers(db, rsrc.getNotes()).all()) {
+    for (Account.Id accountId : approvalsUtil.getReviewers(rsrc.getNotes()).all()) {
       if (!reviewers.containsKey(accountId.toString())) {
         reviewers.put(accountId.toString(), resourceFactory.create(rsrc, accountId));
       }
diff --git a/java/com/google/gerrit/server/restapi/change/ListRevisionComments.java b/java/com/google/gerrit/server/restapi/change/ListRevisionComments.java
index 964e560..f10d92b 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRevisionComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRevisionComments.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.change;
 
 import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.notedb.ChangeNotes;
@@ -27,9 +26,8 @@
 @Singleton
 public class ListRevisionComments extends ListRevisionDrafts {
   @Inject
-  ListRevisionComments(
-      Provider<ReviewDb> db, Provider<CommentJson> commentJson, CommentsUtil commentsUtil) {
-    super(db, commentJson, commentsUtil);
+  ListRevisionComments(Provider<CommentJson> commentJson, CommentsUtil commentsUtil) {
+    super(commentJson, commentsUtil);
   }
 
   @Override
@@ -40,6 +38,6 @@
   @Override
   protected Iterable<Comment> listComments(RevisionResource rsrc) throws OrmException {
     ChangeNotes notes = rsrc.getNotes();
-    return commentsUtil.publishedByPatchSet(db.get(), notes, rsrc.getPatchSet().getId());
+    return commentsUtil.publishedByPatchSet(notes, rsrc.getPatchSet().getId());
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java b/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
index dbd0ccf..3df7e9c 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -31,21 +30,18 @@
 
 @Singleton
 public class ListRevisionDrafts implements RestReadView<RevisionResource> {
-  protected final Provider<ReviewDb> db;
   protected final Provider<CommentJson> commentJson;
   protected final CommentsUtil commentsUtil;
 
   @Inject
-  ListRevisionDrafts(
-      Provider<ReviewDb> db, Provider<CommentJson> commentJson, CommentsUtil commentsUtil) {
-    this.db = db;
+  ListRevisionDrafts(Provider<CommentJson> commentJson, CommentsUtil commentsUtil) {
     this.commentJson = commentJson;
     this.commentsUtil = commentsUtil;
   }
 
   protected Iterable<Comment> listComments(RevisionResource rsrc) throws OrmException {
     return commentsUtil.draftByPatchSetAuthor(
-        db.get(), rsrc.getPatchSet().getId(), rsrc.getAccountId(), rsrc.getNotes());
+        rsrc.getPatchSet().getId(), rsrc.getAccountId(), rsrc.getNotes());
   }
 
   protected boolean includeAuthorInfo() {
diff --git a/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java b/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java
index 7add548..6e7ffd9 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.change.ReviewerJson;
 import com.google.gerrit.server.change.ReviewerResource;
@@ -27,7 +26,6 @@
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -35,18 +33,13 @@
 
 @Singleton
 class ListRevisionReviewers implements RestReadView<RevisionResource> {
-  private final Provider<ReviewDb> dbProvider;
   private final ApprovalsUtil approvalsUtil;
   private final ReviewerJson json;
   private final ReviewerResource.Factory resourceFactory;
 
   @Inject
   ListRevisionReviewers(
-      Provider<ReviewDb> dbProvider,
-      ApprovalsUtil approvalsUtil,
-      ReviewerResource.Factory resourceFactory,
-      ReviewerJson json) {
-    this.dbProvider = dbProvider;
+      ApprovalsUtil approvalsUtil, ReviewerResource.Factory resourceFactory, ReviewerJson json) {
     this.approvalsUtil = approvalsUtil;
     this.resourceFactory = resourceFactory;
     this.json = json;
@@ -60,8 +53,7 @@
     }
 
     Map<String, ReviewerResource> reviewers = new LinkedHashMap<>();
-    ReviewDb db = dbProvider.get();
-    for (Account.Id accountId : approvalsUtil.getReviewers(db, rsrc.getNotes()).all()) {
+    for (Account.Id accountId : approvalsUtil.getReviewers(rsrc.getNotes()).all()) {
       if (!reviewers.containsKey(accountId.toString())) {
         reviewers.put(accountId.toString(), resourceFactory.create(rsrc, accountId));
       }
diff --git a/java/com/google/gerrit/server/restapi/change/Module.java b/java/com/google/gerrit/server/restapi/change/Module.java
index a45a6d8..c7fe653 100644
--- a/java/com/google/gerrit/server/restapi/change/Module.java
+++ b/java/com/google/gerrit/server/restapi/change/Module.java
@@ -96,7 +96,6 @@
     get(CHANGE_KIND, "submitted_together").to(SubmittedTogether.class);
     post(CHANGE_KIND, "rebase").to(Rebase.CurrentRevision.class);
     post(CHANGE_KIND, "index").to(Index.class);
-    post(CHANGE_KIND, "rebuild.notedb").to(Rebuild.class);
     post(CHANGE_KIND, "move").to(Move.class);
     post(CHANGE_KIND, "private").to(PostPrivate.class);
     post(CHANGE_KIND, "private.delete").to(DeletePrivateByPost.class);
diff --git a/java/com/google/gerrit/server/restapi/change/Move.java b/java/com/google/gerrit/server/restapi/change/Move.java
index df1d9b9..d781370 100644
--- a/java/com/google/gerrit/server/restapi/change/Move.java
+++ b/java/com/google/gerrit/server/restapi/change/Move.java
@@ -202,8 +202,7 @@
           RevWalk revWalk = new RevWalk(repo)) {
         RevCommit currPatchsetRevCommit =
             revWalk.parseCommit(
-                ObjectId.fromString(
-                    psUtil.current(ctx.getDb(), ctx.getNotes()).getRevision().get()));
+                ObjectId.fromString(psUtil.current(ctx.getNotes()).getRevision().get()));
         if (currPatchsetRevCommit.getParentCount() > 1) {
           throw new ResourceConflictException("Merge commit cannot be moved");
         }
@@ -252,7 +251,7 @@
       }
       ChangeMessage cmsg =
           ChangeMessagesUtil.newMessage(ctx, msgBuf.toString(), ChangeMessagesUtil.TAG_MOVE);
-      cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
+      cmUtil.addChangeMessage(update, cmsg);
 
       return true;
     }
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index d06766d..c2868fd 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -322,7 +322,7 @@
       if (!ccOrReviewer) {
         // Check if user was already CCed or reviewing prior to this review.
         ReviewerSet currentReviewers =
-            approvalsUtil.getReviewers(db.get(), revision.getChangeResource().getNotes());
+            approvalsUtil.getReviewers(revision.getChangeResource().getNotes());
         ccOrReviewer = currentReviewers.all().contains(id);
       }
 
@@ -896,7 +896,7 @@
             PatchListNotAvailableException {
       user = ctx.getIdentifiedUser();
       notes = ctx.getNotes();
-      ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+      ps = psUtil.get(ctx.getNotes(), psId);
       boolean dirty = false;
       dirty |= insertComments(ctx);
       dirty |= insertRobotComments(ctx);
@@ -990,7 +990,7 @@
           break;
       }
       ChangeUpdate u = ctx.getUpdate(psId);
-      commentsUtil.putComments(ctx.getDb(), u, Status.PUBLISHED, toPublish);
+      commentsUtil.putComments(u, Status.PUBLISHED, toPublish);
       comments.addAll(toPublish);
       return !toPublish.isEmpty();
     }
@@ -1079,7 +1079,7 @@
 
     private Set<CommentSetEntry> readExistingComments(ChangeContext ctx) throws OrmException {
       return commentsUtil
-          .publishedByChange(ctx.getDb(), ctx.getNotes())
+          .publishedByChange(ctx.getNotes())
           .stream()
           .map(CommentSetEntry::create)
           .collect(toSet());
@@ -1095,8 +1095,7 @@
 
     private Map<String, Comment> changeDrafts(ChangeContext ctx) throws OrmException {
       Map<String, Comment> drafts = new HashMap<>();
-      for (Comment c :
-          commentsUtil.draftByChangeAuthor(ctx.getDb(), ctx.getNotes(), user.getAccountId())) {
+      for (Comment c : commentsUtil.draftByChangeAuthor(ctx.getNotes(), user.getAccountId())) {
         c.tag = in.tag;
         drafts.put(c.key.uuid, c);
       }
@@ -1106,8 +1105,7 @@
     private Map<String, Comment> patchSetDrafts(ChangeContext ctx) throws OrmException {
       Map<String, Comment> drafts = new HashMap<>();
       for (Comment c :
-          commentsUtil.draftByPatchSetAuthor(
-              ctx.getDb(), psId, user.getAccountId(), ctx.getNotes())) {
+          commentsUtil.draftByPatchSetAuthor(psId, user.getAccountId(), ctx.getNotes())) {
         drafts.put(c.key.uuid, c);
       }
       return drafts;
@@ -1387,7 +1385,7 @@
       return current;
     }
 
-    private boolean insertMessage(ChangeContext ctx) throws OrmException {
+    private boolean insertMessage(ChangeContext ctx) {
       String msg = Strings.nullToEmpty(in.message).trim();
 
       StringBuilder buf = new StringBuilder();
@@ -1411,7 +1409,7 @@
       message =
           ChangeMessagesUtil.newMessage(
               psId, user, ctx.getWhen(), "Patch Set " + psId.get() + ":" + buf, in.tag);
-      cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), message);
+      cmUtil.addChangeMessage(ctx.getUpdate(psId), message);
       return true;
     }
 
diff --git a/java/com/google/gerrit/server/restapi/change/PutDescription.java b/java/com/google/gerrit/server/restapi/change/PutDescription.java
index 3b5edb2..e04a314 100644
--- a/java/com/google/gerrit/server/restapi/change/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/change/PutDescription.java
@@ -93,7 +93,7 @@
 
     @Override
     public boolean updateChange(ChangeContext ctx) throws OrmException {
-      PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+      PatchSet ps = psUtil.get(ctx.getNotes(), psId);
       ChangeUpdate update = ctx.getUpdate(psId);
       newDescription = Strings.nullToEmpty(input.description);
       oldDescription = Strings.nullToEmpty(ps.getDescription());
@@ -117,7 +117,7 @@
       ChangeMessage cmsg =
           ChangeMessagesUtil.newMessage(
               psId, ctx.getUser(), ctx.getWhen(), summary, ChangeMessagesUtil.TAG_SET_DESCRIPTION);
-      cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
+      cmUtil.addChangeMessage(update, cmsg);
       return true;
     }
   }
diff --git a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
index 72358bd..76fb889 100644
--- a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
@@ -118,7 +118,7 @@
     public boolean updateChange(ChangeContext ctx)
         throws ResourceNotFoundException, OrmException, PatchListNotAvailableException {
       Optional<Comment> maybeComment =
-          commentsUtil.getDraft(ctx.getDb(), ctx.getNotes(), ctx.getIdentifiedUser(), key);
+          commentsUtil.getDraft(ctx.getNotes(), ctx.getIdentifiedUser(), key);
       if (!maybeComment.isPresent()) {
         // Disappeared out from under us. Can't easily fall back to insert,
         // because the input might be missing required fields. Just give up.
@@ -133,7 +133,7 @@
       PatchSet.Id psId = new PatchSet.Id(ctx.getChange().getId(), origComment.key.patchSetId);
       ChangeUpdate update = ctx.getUpdate(psId);
 
-      PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+      PatchSet ps = psUtil.get(ctx.getNotes(), psId);
       if (ps == null) {
         throw new ResourceNotFoundException("patch set not found: " + psId);
       }
@@ -141,15 +141,12 @@
         // Updating the path alters the primary key, which isn't possible.
         // Delete then recreate the comment instead of an update.
 
-        commentsUtil.deleteComments(ctx.getDb(), update, Collections.singleton(origComment));
+        commentsUtil.deleteComments(update, Collections.singleton(origComment));
         comment.key.filename = in.path;
       }
       setCommentRevId(comment, patchListCache, ctx.getChange(), ps);
       commentsUtil.putComments(
-          ctx.getDb(),
-          update,
-          Status.DRAFT,
-          Collections.singleton(update(comment, in, ctx.getWhen())));
+          update, Status.DRAFT, Collections.singleton(update(comment, in, ctx.getWhen())));
       ctx.dontBumpLastUpdatedOn();
       return true;
     }
diff --git a/java/com/google/gerrit/server/restapi/change/PutMessage.java b/java/com/google/gerrit/server/restapi/change/PutMessage.java
index bcd0e9e..756d3d5 100644
--- a/java/com/google/gerrit/server/restapi/change/PutMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/PutMessage.java
@@ -105,7 +105,7 @@
       BatchUpdate.Factory updateFactory, ChangeResource resource, CommitMessageInput input)
       throws IOException, RestApiException, UpdateException, PermissionBackendException,
           OrmException, ConfigInvalidException {
-    PatchSet ps = psUtil.current(db.get(), resource.getNotes());
+    PatchSet ps = psUtil.current(resource.getNotes());
     if (ps == null) {
       throw new ResourceConflictException("current revision is missing");
     }
diff --git a/java/com/google/gerrit/server/restapi/change/PutTopic.java b/java/com/google/gerrit/server/restapi/change/PutTopic.java
index 7f56c91..60d5088 100644
--- a/java/com/google/gerrit/server/restapi/change/PutTopic.java
+++ b/java/com/google/gerrit/server/restapi/change/PutTopic.java
@@ -123,7 +123,7 @@
 
       ChangeMessage cmsg =
           ChangeMessagesUtil.newMessage(ctx, summary, ChangeMessagesUtil.TAG_SET_TOPIC);
-      cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
+      cmUtil.addChangeMessage(update, cmsg);
       return true;
     }
 
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index eed0896..7671fd4 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -273,7 +273,7 @@
         BatchUpdate.Factory updateFactory, ChangeResource rsrc, RebaseInput input)
         throws OrmException, UpdateException, RestApiException, IOException,
             PermissionBackendException {
-      PatchSet ps = psUtil.current(rebase.dbProvider.get(), rsrc.getNotes());
+      PatchSet ps = psUtil.current(rsrc.getNotes());
       if (ps == null) {
         throw new ResourceConflictException("current revision is missing");
       }
diff --git a/java/com/google/gerrit/server/restapi/change/Rebuild.java b/java/com/google/gerrit/server/restapi/change/Rebuild.java
deleted file mode 100644
index dc390cc..0000000
--- a/java/com/google/gerrit/server/restapi/change/Rebuild.java
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright (C) 2016 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.restapi.change;
-
-import static java.util.stream.Collectors.joining;
-
-import com.google.gerrit.extensions.common.Input;
-import com.google.gerrit.extensions.restapi.BinaryResult;
-import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.notedb.ChangeBundle;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.util.List;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-
-@Singleton
-public class Rebuild implements RestModifyView<ChangeResource, Input> {
-
-  private final Provider<ReviewDb> db;
-  private final NotesMigration migration;
-  private final ChangeRebuilder rebuilder;
-  private final ChangeBundleReader bundleReader;
-  private final CommentsUtil commentsUtil;
-  private final ChangeNotes.Factory notesFactory;
-
-  @Inject
-  Rebuild(
-      Provider<ReviewDb> db,
-      NotesMigration migration,
-      ChangeRebuilder rebuilder,
-      ChangeBundleReader bundleReader,
-      CommentsUtil commentsUtil,
-      ChangeNotes.Factory notesFactory) {
-    this.db = db;
-    this.migration = migration;
-    this.rebuilder = rebuilder;
-    this.bundleReader = bundleReader;
-    this.commentsUtil = commentsUtil;
-    this.notesFactory = notesFactory;
-  }
-
-  @Override
-  public BinaryResult apply(ChangeResource rsrc, Input input)
-      throws ResourceNotFoundException, IOException, OrmException, ConfigInvalidException,
-          ResourceConflictException {
-    if (!migration.commitChangeWrites()) {
-      throw new ResourceNotFoundException();
-    }
-    if (!migration.readChanges()) {
-      // ChangeBundle#fromNotes currently doesn't work if reading isn't enabled,
-      // so don't attempt a diff.
-      rebuild(rsrc);
-      return BinaryResult.create("Rebuilt change successfully");
-    }
-
-    // Not the same transaction as the rebuild, so may result in spurious diffs
-    // in the case of races. This should be easy enough to detect by rerunning.
-    ChangeBundle reviewDbBundle =
-        bundleReader.fromReviewDb(ReviewDbUtil.unwrapDb(db.get()), rsrc.getId());
-    if (reviewDbBundle == null) {
-      throw new ResourceConflictException("change is missing in ReviewDb");
-    }
-    rebuild(rsrc);
-    ChangeNotes notes = notesFactory.create(db.get(), rsrc.getChange().getProject(), rsrc.getId());
-    ChangeBundle noteDbBundle = ChangeBundle.fromNotes(commentsUtil, notes);
-    List<String> diffs = reviewDbBundle.differencesFrom(noteDbBundle);
-    if (diffs.isEmpty()) {
-      return BinaryResult.create("No differences between ReviewDb and NoteDb");
-    }
-    return BinaryResult.create(
-        diffs.stream().collect(joining("\n", "Differences between ReviewDb and NoteDb:\n", "\n")));
-  }
-
-  private void rebuild(ChangeResource rsrc)
-      throws ResourceNotFoundException, OrmException, IOException {
-    try {
-      rebuilder.rebuild(db.get(), rsrc.getId());
-    } catch (NoSuchChangeException e) {
-      throw new ResourceNotFoundException(IdString.fromDecoded(rsrc.getId().toString()));
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/restapi/change/Restore.java b/java/com/google/gerrit/server/restapi/change/Restore.java
index d6f9e2b..d40cf2d 100644
--- a/java/com/google/gerrit/server/restapi/change/Restore.java
+++ b/java/com/google/gerrit/server/restapi/change/Restore.java
@@ -124,13 +124,13 @@
       }
       PatchSet.Id psId = change.currentPatchSetId();
       ChangeUpdate update = ctx.getUpdate(psId);
-      patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+      patchSet = psUtil.get(ctx.getNotes(), psId);
       change.setStatus(Status.NEW);
       change.setLastUpdatedOn(ctx.getWhen());
       update.setStatus(change.getStatus());
 
       message = newMessage(ctx);
-      cmUtil.addChangeMessage(ctx.getDb(), update, message);
+      cmUtil.addChangeMessage(update, message);
       return true;
     }
 
diff --git a/java/com/google/gerrit/server/restapi/change/Revert.java b/java/com/google/gerrit/server/restapi/change/Revert.java
index 7309fde..1d79227 100644
--- a/java/com/google/gerrit/server/restapi/change/Revert.java
+++ b/java/com/google/gerrit/server/restapi/change/Revert.java
@@ -168,7 +168,7 @@
     String message = Strings.emptyToNull(input.message);
     Change.Id changeIdToRevert = notes.getChangeId();
     PatchSet.Id patchSetId = notes.getChange().currentPatchSetId();
-    PatchSet patch = psUtil.get(db.get(), notes, patchSetId);
+    PatchSet patch = psUtil.get(notes, patchSetId);
     if (patch == null) {
       throw new ResourceNotFoundException(changeIdToRevert.toString());
     }
@@ -231,7 +231,7 @@
       ins.setNotify(input.notify);
       ins.setAccountsToNotify(accountsToNotify);
 
-      ReviewerSet reviewerSet = approvalsUtil.getReviewers(db.get(), notes);
+      ReviewerSet reviewerSet = approvalsUtil.getReviewers(notes);
 
       Set<Account.Id> reviewers = new HashSet<>();
       reviewers.add(changeToRevert.getOwner());
@@ -318,7 +318,7 @@
     }
 
     @Override
-    public boolean updateChange(ChangeContext ctx) throws Exception {
+    public boolean updateChange(ChangeContext ctx) {
       Change change = ctx.getChange();
       PatchSet.Id patchSetId = change.currentPatchSetId();
       ChangeMessage changeMessage =
@@ -326,7 +326,7 @@
               ctx,
               "Created a revert of this change as I" + computedChangeId.name(),
               ChangeMessagesUtil.TAG_REVERT);
-      cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(patchSetId), changeMessage);
+      cmUtil.addChangeMessage(ctx.getUpdate(patchSetId), changeMessage);
       return true;
     }
   }
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
index d88489e..eb79b79 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.FanOutExecutor;
 import com.google.gerrit.server.change.ReviewerSuggestion;
@@ -80,7 +79,6 @@
   private final PluginMapContext<ReviewerSuggestion> reviewerSuggestionPluginMap;
   private final Provider<InternalChangeQuery> queryProvider;
   private final ExecutorService executor;
-  private final Provider<ReviewDb> dbProvider;
   private final ApprovalsUtil approvalsUtil;
 
   @Inject
@@ -89,7 +87,6 @@
       PluginMapContext<ReviewerSuggestion> reviewerSuggestionPluginMap,
       Provider<InternalChangeQuery> queryProvider,
       @FanOutExecutor ExecutorService executor,
-      Provider<ReviewDb> dbProvider,
       ApprovalsUtil approvalsUtil,
       @GerritServerConfig Config config) {
     this.changeQueryBuilder = changeQueryBuilder;
@@ -97,7 +94,6 @@
     this.queryProvider = queryProvider;
     this.reviewerSuggestionPluginMap = reviewerSuggestionPluginMap;
     this.executor = executor;
-    this.dbProvider = dbProvider;
     this.approvalsUtil = approvalsUtil;
   }
 
@@ -183,7 +179,7 @@
 
       // Remove existing reviewers
       approvalsUtil
-          .getReviewers(dbProvider.get(), changeNotes)
+          .getReviewers(changeNotes)
           .byState(REVIEWER)
           .forEach(
               r -> {
diff --git a/java/com/google/gerrit/server/restapi/change/Reviewers.java b/java/com/google/gerrit/server/restapi/change/Reviewers.java
index f0aef13..cf69080 100644
--- a/java/com/google/gerrit/server/restapi/change/Reviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/Reviewers.java
@@ -23,14 +23,12 @@
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.ReviewerResource;
 import com.google.gerrit.server.restapi.account.AccountsCollection;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.util.Collection;
@@ -39,7 +37,6 @@
 @Singleton
 public class Reviewers implements ChildCollection<ChangeResource, ReviewerResource> {
   private final DynamicMap<RestView<ReviewerResource>> views;
-  private final Provider<ReviewDb> dbProvider;
   private final ApprovalsUtil approvalsUtil;
   private final AccountsCollection accounts;
   private final ReviewerResource.Factory resourceFactory;
@@ -47,13 +44,11 @@
 
   @Inject
   Reviewers(
-      Provider<ReviewDb> dbProvider,
       ApprovalsUtil approvalsUtil,
       AccountsCollection accounts,
       ReviewerResource.Factory resourceFactory,
       DynamicMap<RestView<ReviewerResource>> views,
       ListReviewers list) {
-    this.dbProvider = dbProvider;
     this.approvalsUtil = approvalsUtil;
     this.accounts = accounts;
     this.resourceFactory = resourceFactory;
@@ -99,6 +94,6 @@
   }
 
   private Collection<Account.Id> fetchAccountIds(ChangeResource rsrc) throws OrmException {
-    return approvalsUtil.getReviewers(dbProvider.get(), rsrc.getNotes()).all();
+    return approvalsUtil.getReviewers(rsrc.getNotes()).all();
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
index f7959c8..854125c 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
@@ -402,7 +402,8 @@
         return result;
       }
 
-      boolean needsConfirmation = result.size > maxAllowedWithoutConfirmation;
+      boolean needsConfirmation =
+          maxAllowedWithoutConfirmation > 0 && result.size > maxAllowedWithoutConfirmation;
       if (needsConfirmation) {
         logger.atFine().log(
             "group %s needs confirmation to be added as reviewer, it has %d members",
diff --git a/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java b/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java
index b9b7a4f..60c9a54 100644
--- a/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java
@@ -24,14 +24,12 @@
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.change.ReviewerResource;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.restapi.account.AccountsCollection;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.util.Collection;
@@ -40,7 +38,6 @@
 @Singleton
 public class RevisionReviewers implements ChildCollection<RevisionResource, ReviewerResource> {
   private final DynamicMap<RestView<ReviewerResource>> views;
-  private final Provider<ReviewDb> dbProvider;
   private final ApprovalsUtil approvalsUtil;
   private final AccountsCollection accounts;
   private final ReviewerResource.Factory resourceFactory;
@@ -48,13 +45,11 @@
 
   @Inject
   RevisionReviewers(
-      Provider<ReviewDb> dbProvider,
       ApprovalsUtil approvalsUtil,
       AccountsCollection accounts,
       ReviewerResource.Factory resourceFactory,
       DynamicMap<RestView<ReviewerResource>> views,
       ListRevisionReviewers list) {
-    this.dbProvider = dbProvider;
     this.approvalsUtil = approvalsUtil;
     this.accounts = accounts;
     this.resourceFactory = resourceFactory;
@@ -89,8 +84,7 @@
         throw e;
       }
     }
-    Collection<Account.Id> reviewers =
-        approvalsUtil.getReviewers(dbProvider.get(), rsrc.getNotes()).all();
+    Collection<Account.Id> reviewers = approvalsUtil.getReviewers(rsrc.getNotes()).all();
     // See if the id exists as a reviewer for this change
     if (reviewers.contains(accountId)) {
       return resourceFactory.create(rsrc, accountId);
diff --git a/java/com/google/gerrit/server/restapi/change/Revisions.java b/java/com/google/gerrit/server/restapi/change/Revisions.java
index 557d77a..50bd66f 100644
--- a/java/com/google/gerrit/server/restapi/change/Revisions.java
+++ b/java/com/google/gerrit/server/restapi/change/Revisions.java
@@ -85,7 +85,7 @@
       throws ResourceNotFoundException, AuthException, OrmException, IOException,
           PermissionBackendException {
     if (id.get().equals("current")) {
-      PatchSet ps = psUtil.current(dbProvider.get(), change.getNotes());
+      PatchSet ps = psUtil.current(change.getNotes());
       if (ps != null && visible(change)) {
         return RevisionResource.createNonCachable(change, ps);
       }
@@ -135,7 +135,7 @@
       return Collections.emptyList();
     } else {
       List<RevisionResource> out = new ArrayList<>();
-      for (PatchSet ps : psUtil.byChange(dbProvider.get(), change.getNotes())) {
+      for (PatchSet ps : psUtil.byChange(change.getNotes())) {
         if (ps.getRevision() != null && ps.getRevision().get().startsWith(id)) {
           out.add(new RevisionResource(change, ps));
         }
@@ -151,10 +151,7 @@
   private List<RevisionResource> byLegacyPatchSetId(ChangeResource change, String id)
       throws OrmException {
     PatchSet ps =
-        psUtil.get(
-            dbProvider.get(),
-            change.getNotes(),
-            new PatchSet.Id(change.getId(), Integer.parseInt(id)));
+        psUtil.get(change.getNotes(), new PatchSet.Id(change.getId(), Integer.parseInt(id)));
     if (ps != null) {
       return Collections.singletonList(new RevisionResource(change, ps));
     }
diff --git a/java/com/google/gerrit/server/restapi/change/SetPrivateOp.java b/java/com/google/gerrit/server/restapi/change/SetPrivateOp.java
index 8aac92c..04c94be 100644
--- a/java/com/google/gerrit/server/restapi/change/SetPrivateOp.java
+++ b/java/com/google/gerrit/server/restapi/change/SetPrivateOp.java
@@ -73,7 +73,7 @@
   public boolean updateChange(ChangeContext ctx) throws ResourceConflictException, OrmException {
     change = ctx.getChange();
     ChangeNotes notes = ctx.getNotes();
-    ps = psUtil.get(ctx.getDb(), notes, change.currentPatchSetId());
+    ps = psUtil.get(notes, change.currentPatchSetId());
     ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
     change.setPrivate(isPrivate);
     change.setLastUpdatedOn(ctx.getWhen());
@@ -87,7 +87,7 @@
     privateStateChanged.fire(change, ps, ctx.getAccount(), ctx.getWhen());
   }
 
-  private void addMessage(ChangeContext ctx, ChangeUpdate update) throws OrmException {
+  private void addMessage(ChangeContext ctx, ChangeUpdate update) {
     Change c = ctx.getChange();
     StringBuilder buf = new StringBuilder(c.isPrivate() ? "Set private" : "Unset private");
 
@@ -104,6 +104,6 @@
             c.isPrivate()
                 ? ChangeMessagesUtil.TAG_SET_PRIVATE
                 : ChangeMessagesUtil.TAG_UNSET_PRIVATE);
-    cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
+    cmUtil.addChangeMessage(update, cmsg);
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/Submit.java b/java/com/google/gerrit/server/restapi/change/Submit.java
index 773d12d..d426df3 100644
--- a/java/com/google/gerrit/server/restapi/change/Submit.java
+++ b/java/com/google/gerrit/server/restapi/change/Submit.java
@@ -456,8 +456,7 @@
       for (ChangeData change : changes) {
         RevCommit commit =
             walk.parseCommit(
-                ObjectId.fromString(
-                    psUtil.current(dbProvider.get(), change.notes()).getRevision().get()));
+                ObjectId.fromString(psUtil.current(change.notes()).getRevision().get()));
         commits.put(change.getId(), commit);
       }
     }
@@ -495,18 +494,12 @@
   }
 
   public static class CurrentRevision implements RestModifyView<ChangeResource, SubmitInput> {
-    private final Provider<ReviewDb> dbProvider;
     private final Submit submit;
     private final ChangeJson.Factory json;
     private final PatchSetUtil psUtil;
 
     @Inject
-    CurrentRevision(
-        Provider<ReviewDb> dbProvider,
-        Submit submit,
-        ChangeJson.Factory json,
-        PatchSetUtil psUtil) {
-      this.dbProvider = dbProvider;
+    CurrentRevision(Submit submit, ChangeJson.Factory json, PatchSetUtil psUtil) {
       this.submit = submit;
       this.json = json;
       this.psUtil = psUtil;
@@ -516,7 +509,7 @@
     public ChangeInfo apply(ChangeResource rsrc, SubmitInput input)
         throws RestApiException, RepositoryNotFoundException, IOException, OrmException,
             PermissionBackendException, UpdateException, ConfigInvalidException {
-      PatchSet ps = psUtil.current(dbProvider.get(), rsrc.getNotes());
+      PatchSet ps = psUtil.current(rsrc.getNotes());
       if (ps == null) {
         throw new ResourceConflictException("current revision is missing");
       }
diff --git a/java/com/google/gerrit/server/restapi/config/ListTopMenus.java b/java/com/google/gerrit/server/restapi/config/ListTopMenus.java
index 7a85bcd..c296a7d 100644
--- a/java/com/google/gerrit/server/restapi/config/ListTopMenus.java
+++ b/java/com/google/gerrit/server/restapi/config/ListTopMenus.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.restapi.config;
 
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.webui.TopMenu;
 import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.util.ArrayList;
@@ -25,19 +25,17 @@
 
 @Singleton
 class ListTopMenus implements RestReadView<ConfigResource> {
-  private final DynamicSet<TopMenu> extensions;
+  private final PluginSetContext<TopMenu> extensions;
 
   @Inject
-  ListTopMenus(DynamicSet<TopMenu> extensions) {
+  ListTopMenus(PluginSetContext<TopMenu> extensions) {
     this.extensions = extensions;
   }
 
   @Override
   public List<TopMenu.MenuEntry> apply(ConfigResource resource) {
     List<TopMenu.MenuEntry> entries = new ArrayList<>();
-    for (TopMenu extension : extensions) {
-      entries.addAll(extension.getEntries());
-    }
+    extensions.runEach(extension -> entries.addAll(extension.getEntries()));
     return entries;
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/project/GetAccess.java b/java/com/google/gerrit/server/restapi/project/GetAccess.java
index 8875d40..0f46535 100644
--- a/java/com/google/gerrit/server/restapi/project/GetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/GetAccess.java
@@ -30,13 +30,11 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.RefConfigSection;
-import com.google.gerrit.common.data.WebLinkInfoCommon;
 import com.google.gerrit.extensions.api.access.AccessSectionInfo;
 import com.google.gerrit.extensions.api.access.PermissionInfo;
 import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
 import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
 import com.google.gerrit.extensions.common.GroupInfo;
-import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -149,13 +147,9 @@
 
       // config may have a null revision if the repo doesn't have its own refs/meta/config.
       if (config.getRevision() != null) {
-        // WebLinks operates in terms of the data types used in the GWT UI. Once the GWT UI is
-        // gone, WebLinks should be fixed to use the extension data types.
-        for (WebLinkInfoCommon wl :
+        info.configWebLinks.addAll(
             webLinks.getFileHistoryLinks(
-                projectName.get(), config.getRevision().getName(), ProjectConfig.PROJECT_CONFIG)) {
-          info.configWebLinks.add(new WebLinkInfo(wl.name, wl.imageUrl, wl.url, wl.target));
-        }
+                projectName.get(), config.getRevision().getName(), ProjectConfig.PROJECT_CONFIG));
       }
 
       if (config.updateGroupNames(groupBackend)) {
diff --git a/java/com/google/gerrit/server/schema/AbstractDisabledAccess.java b/java/com/google/gerrit/server/schema/AbstractDisabledAccess.java
index 17eb56e..72cd706 100644
--- a/java/com/google/gerrit/server/schema/AbstractDisabledAccess.java
+++ b/java/com/google/gerrit/server/schema/AbstractDisabledAccess.java
@@ -14,11 +14,8 @@
 
 package com.google.gerrit.server.schema;
 
-import static com.google.common.base.Preconditions.checkState;
-
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.Futures;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
 import com.google.gwtorm.client.Key;
 import com.google.gwtorm.server.Access;
 import com.google.gwtorm.server.AtomicUpdate;
@@ -26,9 +23,10 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import java.util.Map;
-import java.util.function.Function;
 
 abstract class AbstractDisabledAccess<T, K extends Key<?>> implements Access<T, K> {
+  private static final String GONE = "ReviewDb is gone";
+
   private static <T> ResultSet<T> empty() {
     return new ListResultSet<>(ImmutableList.of());
   }
@@ -39,40 +37,24 @@
     return Futures.immediateCheckedFuture(null);
   }
 
-  // Don't even hold a reference to delegate, so it's not possible to use it
-  // accidentally.
-  private final ReviewDbWrapper wrapper;
-  private final String relationName;
-  private final int relationId;
-  private final Function<T, K> primaryKey;
-  private final Function<Iterable<T>, Map<K, T>> toMap;
-
-  AbstractDisabledAccess(ReviewDbWrapper wrapper, Access<T, K> delegate) {
-    this.wrapper = wrapper;
-    this.relationName = delegate.getRelationName();
-    this.relationId = delegate.getRelationID();
-    this.primaryKey = delegate::primaryKey;
-    this.toMap = delegate::toMap;
-  }
-
   @Override
   public final int getRelationID() {
-    return relationId;
+    throw new UnsupportedOperationException(GONE);
   }
 
   @Override
   public final String getRelationName() {
-    return relationName;
+    throw new UnsupportedOperationException(GONE);
   }
 
   @Override
   public final K primaryKey(T entity) {
-    return primaryKey.apply(entity);
+    throw new UnsupportedOperationException(GONE);
   }
 
   @Override
   public final Map<K, T> toMap(Iterable<T> iterable) {
-    return toMap.apply(iterable);
+    throw new UnsupportedOperationException(GONE);
   }
 
   @Override
@@ -118,15 +100,7 @@
 
   @Override
   public final void beginTransaction(K key) {
-    // Keep track of when we've started a transaction so that we can avoid calling commit/rollback
-    // on the underlying ReviewDb. This is just a simple arm's-length approach, and may produce
-    // slightly different results from a native ReviewDb in corner cases like:
-    // * beginning transactions on different tables simultaneously
-    // * doing work between commit and rollback
-    // These kinds of things are already misuses of ReviewDb, and shouldn't be happening in current
-    // code anyway.
-    checkState(!wrapper.inTransaction(), "already in transaction");
-    wrapper.beginTransaction();
+    // Do nothing.
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index 85965ef..14da9eb 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -48,9 +48,11 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.notedb.NoteDbSchemaVersionManager;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.notedb.RepoSequence;
 import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -73,6 +75,7 @@
   private final AllProjectsName allProjectsName;
   private final PersonIdent serverUser;
   private final NotesMigration notesMigration;
+  private final NoteDbSchemaVersionManager versionManager;
   private final ProjectConfig.Factory projectConfigFactory;
   private final GroupReference anonymous;
   private final GroupReference registered;
@@ -91,12 +94,14 @@
       AllProjectsName allProjectsName,
       @GerritPersonIdent PersonIdent serverUser,
       NotesMigration notesMigration,
+      NoteDbSchemaVersionManager versionManager,
       SystemGroupBackend systemGroupBackend,
       ProjectConfig.Factory projectConfigFactory) {
     this.repositoryManager = repositoryManager;
     this.allProjectsName = allProjectsName;
     this.serverUser = serverUser;
     this.notesMigration = notesMigration;
+    this.versionManager = versionManager;
     this.projectConfigFactory = projectConfigFactory;
 
     this.anonymous = systemGroupBackend.getGroup(ANONYMOUS_USERS);
@@ -145,7 +150,7 @@
     return this;
   }
 
-  public void create() throws IOException, ConfigInvalidException {
+  public void create() throws IOException, ConfigInvalidException, OrmException {
     try (Repository git = repositoryManager.openRepository(allProjectsName)) {
       initAllProjects(git);
     } catch (RepositoryNotFoundException notFound) {
@@ -162,7 +167,8 @@
     }
   }
 
-  private void initAllProjects(Repository git) throws IOException, ConfigInvalidException {
+  private void initAllProjects(Repository git)
+      throws IOException, ConfigInvalidException, OrmException {
     BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
     try (MetaDataUpdate md =
         new MetaDataUpdate(GitReferenceUpdated.DISABLED, allProjectsName, git, bru)) {
@@ -231,6 +237,7 @@
 
       config.commitToNewRef(md, RefNames.REFS_CONFIG);
       initSequences(git, bru);
+      initSchemaVersion();
       execute(git, bru);
     }
   }
@@ -268,6 +275,12 @@
     }
   }
 
+  private void initSchemaVersion() throws IOException, OrmException {
+    if (notesMigration.commitChangeWrites()) {
+      versionManager.init();
+    }
+  }
+
   private void execute(Repository git, BatchRefUpdate bru) throws IOException {
     try (RevWalk rw = new RevWalk(git)) {
       bru.execute(rw, NullProgressMonitor.INSTANCE);
diff --git a/java/com/google/gerrit/server/schema/BaseDataSourceType.java b/java/com/google/gerrit/server/schema/BaseDataSourceType.java
deleted file mode 100644
index 4b3a570..0000000
--- a/java/com/google/gerrit/server/schema/BaseDataSourceType.java
+++ /dev/null
@@ -1,63 +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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import java.io.IOException;
-import java.io.InputStream;
-
-public abstract class BaseDataSourceType implements DataSourceType {
-
-  private static final String DEFAULT_VALIDATION_QUERY = "select 1";
-  private final String driver;
-
-  protected BaseDataSourceType(String driver) {
-    this.driver = driver;
-  }
-
-  @Override
-  public final String getDriver() {
-    return driver;
-  }
-
-  @Override
-  public boolean usePool() {
-    return true;
-  }
-
-  @Override
-  public String getValidationQuery() {
-    return DEFAULT_VALIDATION_QUERY;
-  }
-
-  @Override
-  public ScriptRunner getIndexScript() throws IOException {
-    return getScriptRunner("index_generic.sql");
-  }
-
-  protected static final ScriptRunner getScriptRunner(String path) throws IOException {
-    if (path == null) {
-      return ScriptRunner.NOOP;
-    }
-    ScriptRunner runner;
-    try (InputStream in = ReviewDb.class.getResourceAsStream(path)) {
-      if (in == null) {
-        throw new IllegalStateException("SQL script " + path + " not found");
-      }
-      runner = new ScriptRunner(path, in);
-    }
-    return runner;
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/DB2.java b/java/com/google/gerrit/server/schema/DB2.java
deleted file mode 100644
index fcf8c1f..0000000
--- a/java/com/google/gerrit/server/schema/DB2.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import static com.google.gerrit.server.schema.JdbcUtil.hostname;
-import static com.google.gerrit.server.schema.JdbcUtil.port;
-
-import com.google.gerrit.server.config.ConfigSection;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
-
-public class DB2 extends BaseDataSourceType {
-  private Config cfg;
-
-  @Inject
-  public DB2(@GerritServerConfig Config cfg) {
-    super("com.ibm.db2.jcc.DB2Driver");
-    this.cfg = cfg;
-  }
-
-  @Override
-  public String getUrl() {
-    final StringBuilder b = new StringBuilder();
-    final ConfigSection dbc = new ConfigSection(cfg, "database");
-    b.append("jdbc:db2://");
-    b.append(hostname(dbc.optional("hostname")));
-    b.append(port(dbc.optional("port")));
-    b.append("/");
-    b.append(dbc.required("database"));
-    return b.toString();
-  }
-
-  @Override
-  public String getValidationQuery() {
-    return "SELECT 1 FROM SYSIBM.SYSDUMMY1";
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/DataSourceModule.java b/java/com/google/gerrit/server/schema/DataSourceModule.java
deleted file mode 100644
index ee57c8b..0000000
--- a/java/com/google/gerrit/server/schema/DataSourceModule.java
+++ /dev/null
@@ -1,41 +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.schema;
-
-import com.google.inject.AbstractModule;
-import com.google.inject.name.Names;
-
-public class DataSourceModule extends AbstractModule {
-
-  @Override
-  protected void configure() {
-    bind(DataSourceType.class).annotatedWith(Names.named("db2")).to(DB2.class);
-    bind(DataSourceType.class).annotatedWith(Names.named("derby")).to(Derby.class);
-    bind(DataSourceType.class).annotatedWith(Names.named("h2")).to(H2.class);
-    bind(DataSourceType.class).annotatedWith(Names.named("jdbc")).to(JDBC.class);
-    bind(DataSourceType.class).annotatedWith(Names.named("mariadb")).to(MariaDb.class);
-    bind(DataSourceType.class).annotatedWith(Names.named("mysql")).to(MySql.class);
-    bind(DataSourceType.class).annotatedWith(Names.named("oracle")).to(Oracle.class);
-    bind(DataSourceType.class).annotatedWith(Names.named("postgresql")).to(PostgreSQL.class);
-    /*
-     * DatabaseMetaData.getDatabaseProductName() returns "sap db" for MaxDB.
-     * For auto-detection of the DB type (com.google.gerrit.pgm.util.SiteProgram#getDbType)
-     * we have to map "sap db" additionally to "maxdb", which is used for explicit configuration.
-     */
-    bind(DataSourceType.class).annotatedWith(Names.named("maxdb")).to(MaxDb.class);
-    bind(DataSourceType.class).annotatedWith(Names.named("sap db")).to(MaxDb.class);
-    bind(DataSourceType.class).annotatedWith(Names.named("hana")).to(HANA.class);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/DataSourceProvider.java b/java/com/google/gerrit/server/schema/DataSourceProvider.java
deleted file mode 100644
index d4cfaa6..0000000
--- a/java/com/google/gerrit/server/schema/DataSourceProvider.java
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright (C) 2009 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.schema;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import com.google.common.base.Strings;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.extensions.persistence.DataSourceInterceptor;
-import com.google.gerrit.metrics.CallbackMetric1;
-import com.google.gerrit.metrics.Description;
-import com.google.gerrit.metrics.Field;
-import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.server.config.ConfigSection;
-import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.ThreadSettingsConfig;
-import com.google.gwtorm.jdbc.SimpleDataSource;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-import com.google.inject.Singleton;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.sql.SQLException;
-import java.util.Properties;
-import javax.sql.DataSource;
-import org.apache.commons.dbcp.BasicDataSource;
-import org.eclipse.jgit.lib.Config;
-
-/** Provides access to the DataSource. */
-@Singleton
-public class DataSourceProvider implements Provider<DataSource>, LifecycleListener {
-  private static final String DATABASE_KEY = "database";
-
-  private final Config cfg;
-  private final MetricMaker metrics;
-  private final Context ctx;
-  private final DataSourceType dst;
-  private final ThreadSettingsConfig threadSettingsConfig;
-  private DataSource ds;
-
-  @Inject
-  protected DataSourceProvider(
-      @GerritServerConfig Config cfg,
-      MetricMaker metrics,
-      ThreadSettingsConfig threadSettingsConfig,
-      Context ctx,
-      DataSourceType dst) {
-    this.cfg = cfg;
-    this.metrics = metrics;
-    this.threadSettingsConfig = threadSettingsConfig;
-    this.ctx = ctx;
-    this.dst = dst;
-  }
-
-  @Override
-  public synchronized DataSource get() {
-    if (ds == null) {
-      ds = open(cfg, ctx, dst);
-    }
-    return ds;
-  }
-
-  @Override
-  public void start() {}
-
-  @Override
-  public synchronized void stop() {
-    if (ds instanceof BasicDataSource) {
-      try {
-        ((BasicDataSource) ds).close();
-      } catch (SQLException e) {
-        // Ignore the close failure.
-      }
-    }
-  }
-
-  public enum Context {
-    SINGLE_USER,
-    MULTI_USER
-  }
-
-  private DataSource open(Config cfg, Context context, DataSourceType dst) {
-    ConfigSection dbs = new ConfigSection(cfg, DATABASE_KEY);
-    String driver = dbs.optional("driver");
-    if (Strings.isNullOrEmpty(driver)) {
-      driver = dst.getDriver();
-    }
-
-    String url = dbs.optional("url");
-    if (Strings.isNullOrEmpty(url)) {
-      url = dst.getUrl();
-    }
-
-    String username = dbs.optional("username");
-    String password = dbs.optional("password");
-    String interceptor = dbs.optional("dataSourceInterceptorClass");
-
-    boolean usePool;
-    if (context == Context.SINGLE_USER) {
-      usePool = false;
-    } else {
-      usePool = cfg.getBoolean(DATABASE_KEY, "connectionpool", dst.usePool());
-    }
-
-    if (usePool) {
-      final BasicDataSource lds = new BasicDataSource();
-      lds.setDriverClassName(driver);
-      lds.setUrl(url);
-      if (username != null && !username.isEmpty()) {
-        lds.setUsername(username);
-      }
-      if (password != null && !password.isEmpty()) {
-        lds.setPassword(password);
-      }
-      int poolLimit = threadSettingsConfig.getDatabasePoolLimit();
-      lds.setMaxActive(poolLimit);
-      lds.setMinIdle(cfg.getInt(DATABASE_KEY, "poolminidle", 4));
-      lds.setMaxIdle(cfg.getInt(DATABASE_KEY, "poolmaxidle", Math.min(poolLimit, 16)));
-      lds.setMaxWait(
-          ConfigUtil.getTimeUnit(
-              cfg,
-              DATABASE_KEY,
-              null,
-              "poolmaxwait",
-              MILLISECONDS.convert(30, SECONDS),
-              MILLISECONDS));
-      lds.setInitialSize(lds.getMinIdle());
-      long evictIdleTimeMs = 1000L * 60;
-      lds.setMinEvictableIdleTimeMillis(evictIdleTimeMs);
-      lds.setTimeBetweenEvictionRunsMillis(evictIdleTimeMs / 2);
-      lds.setTestOnBorrow(true);
-      lds.setTestOnReturn(true);
-      lds.setValidationQuery(dst.getValidationQuery());
-      lds.setValidationQueryTimeout(5);
-      exportPoolMetrics(lds);
-      return intercept(interceptor, lds);
-    }
-    // Don't use the connection pool.
-    //
-    try {
-      final Properties p = new Properties();
-      p.setProperty("driver", driver);
-      p.setProperty("url", url);
-      if (username != null) {
-        p.setProperty("user", username);
-      }
-      if (password != null) {
-        p.setProperty("password", password);
-      }
-      return intercept(interceptor, new SimpleDataSource(p));
-    } catch (SQLException se) {
-      throw new ProvisionException("Database unavailable", se);
-    }
-  }
-
-  private void exportPoolMetrics(BasicDataSource pool) {
-    CallbackMetric1<Boolean, Integer> cnt =
-        metrics.newCallbackMetric(
-            "sql/connection_pool/connections",
-            Integer.class,
-            new Description("SQL database connections").setGauge().setUnit("connections"),
-            Field.ofBoolean("active"));
-    metrics.newTrigger(
-        cnt,
-        () -> {
-          synchronized (pool) {
-            cnt.set(true, pool.getNumActive());
-            cnt.set(false, pool.getNumIdle());
-          }
-        });
-  }
-
-  private DataSource intercept(String interceptor, DataSource ds) {
-    if (interceptor == null) {
-      return ds;
-    }
-    try {
-      Constructor<?> c = Class.forName(interceptor).getConstructor();
-      DataSourceInterceptor datasourceInterceptor = (DataSourceInterceptor) c.newInstance();
-      return datasourceInterceptor.intercept("reviewDb", ds);
-    } catch (ClassNotFoundException
-        | SecurityException
-        | NoSuchMethodException
-        | IllegalArgumentException
-        | InstantiationException
-        | IllegalAccessException
-        | InvocationTargetException e) {
-      throw new ProvisionException("Cannot intercept datasource", e);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/DataSourceType.java b/java/com/google/gerrit/server/schema/DataSourceType.java
deleted file mode 100644
index cbdcf0f..0000000
--- a/java/com/google/gerrit/server/schema/DataSourceType.java
+++ /dev/null
@@ -1,37 +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.schema;
-
-import java.io.IOException;
-
-/** Abstraction of a supported database platform */
-public interface DataSourceType {
-
-  String getDriver();
-
-  String getUrl();
-
-  String getValidationQuery();
-
-  boolean usePool();
-
-  /**
-   * Return a ScriptRunner that runs the index script. Must not return {@code null}, but may return
-   * a ScriptRunner that does nothing.
-   *
-   * @throws IOException
-   */
-  ScriptRunner getIndexScript() throws IOException;
-}
diff --git a/java/com/google/gerrit/server/schema/DatabaseModule.java b/java/com/google/gerrit/server/schema/DatabaseModule.java
index 38a7751..c65b20e 100644
--- a/java/com/google/gerrit/server/schema/DatabaseModule.java
+++ b/java/com/google/gerrit/server/schema/DatabaseModule.java
@@ -14,13 +14,9 @@
 
 package com.google.gerrit.server.schema;
 
-import static com.google.inject.Scopes.SINGLETON;
-
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.GwtormChangeBundleReader;
-import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Key;
 import com.google.inject.TypeLiteral;
@@ -31,11 +27,11 @@
   protected void configure() {
     TypeLiteral<SchemaFactory<ReviewDb>> schemaFactory =
         new TypeLiteral<SchemaFactory<ReviewDb>>() {};
-    TypeLiteral<Database<ReviewDb>> database = new TypeLiteral<Database<ReviewDb>>() {};
-
     bind(schemaFactory).to(NotesMigrationSchemaFactory.class);
-    bind(Key.get(schemaFactory, ReviewDbFactory.class)).to(database).in(SINGLETON);
-    bind(database).toProvider(ReviewDbDatabaseProvider.class);
-    bind(ChangeBundleReader.class).to(GwtormChangeBundleReader.class);
+    bind(Key.get(schemaFactory, ReviewDbFactory.class))
+        .toInstance(
+            () -> {
+              throw new OrmException("ReviewDb no longer exists");
+            });
   }
 }
diff --git a/java/com/google/gerrit/server/schema/Derby.java b/java/com/google/gerrit/server/schema/Derby.java
deleted file mode 100644
index 9fb761d..0000000
--- a/java/com/google/gerrit/server/schema/Derby.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
-
-class Derby extends BaseDataSourceType {
-
-  protected final Config cfg;
-  private final SitePaths site;
-
-  @Inject
-  Derby(@GerritServerConfig Config cfg, SitePaths site) {
-    super("org.apache.derby.jdbc.EmbeddedDriver");
-    this.cfg = cfg;
-    this.site = site;
-  }
-
-  @Override
-  public String getUrl() {
-    String database = cfg.getString("database", null, "database");
-    if (database == null || database.isEmpty()) {
-      database = "db/ReviewDB";
-    }
-    return "jdbc:derby:" + site.resolve(database).toString() + ";create=true";
-  }
-
-  @Override
-  public String getValidationQuery() {
-    return "values 1";
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/GroupBundle.java b/java/com/google/gerrit/server/schema/GroupBundle.java
deleted file mode 100644
index 26cd96a..0000000
--- a/java/com/google/gerrit/server/schema/GroupBundle.java
+++ /dev/null
@@ -1,778 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static com.google.gerrit.reviewdb.server.ReviewDbUtil.checkColumns;
-import static java.util.Comparator.naturalOrder;
-import static java.util.Comparator.nullsLast;
-import static java.util.stream.Collectors.toList;
-
-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.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.Streams;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.group.InternalGroup;
-import com.google.gerrit.server.group.db.AuditLogReader;
-import com.google.gerrit.server.group.db.GroupConfig;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Stream;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Repository;
-
-/**
- * A bundle of all entities rooted at a single {@link AccountGroup} entity.
- *
- * <p>Used primarily during the migration process. Most callers should prefer {@link InternalGroup}
- * instead.
- */
-@AutoValue
-abstract class GroupBundle {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  static {
-    // Initialization-time checks that the column set hasn't changed since the
-    // last time this file was updated.
-    checkColumns(AccountGroup.NameKey.class, 1);
-    checkColumns(AccountGroup.UUID.class, 1);
-    checkColumns(AccountGroup.Id.class, 1);
-    checkColumns(AccountGroup.class, 1, 2, 4, 7, 9, 10, 11);
-
-    checkColumns(AccountGroupById.Key.class, 1, 2);
-    checkColumns(AccountGroupById.class, 1);
-
-    checkColumns(AccountGroupByIdAud.Key.class, 1, 2, 3);
-    checkColumns(AccountGroupByIdAud.class, 1, 2, 3, 4);
-
-    checkColumns(AccountGroupMember.Key.class, 1, 2);
-    checkColumns(AccountGroupMember.class, 1);
-
-    checkColumns(AccountGroupMemberAudit.Key.class, 1, 2, 3);
-    checkColumns(AccountGroupMemberAudit.class, 1, 2, 3, 4);
-  }
-
-  public enum Source {
-    REVIEW_DB("ReviewDb"),
-    NOTE_DB("NoteDb");
-
-    private final String name;
-
-    private Source(String name) {
-      this.name = name;
-    }
-
-    @Override
-    public String toString() {
-      return name;
-    }
-  }
-
-  @Singleton
-  public static class Factory {
-    private final AuditLogReader auditLogReader;
-
-    @Inject
-    Factory(AuditLogReader auditLogReader) {
-      this.auditLogReader = auditLogReader;
-    }
-
-    public GroupBundle fromNoteDb(
-        Project.NameKey projectName, Repository repo, AccountGroup.UUID uuid)
-        throws ConfigInvalidException, IOException {
-      GroupConfig groupConfig = GroupConfig.loadForGroup(projectName, repo, uuid);
-      InternalGroup internalGroup = groupConfig.getLoadedGroup().get();
-      AccountGroup.Id groupId = internalGroup.getId();
-
-      AccountGroup accountGroup =
-          new AccountGroup(
-              internalGroup.getNameKey(),
-              internalGroup.getId(),
-              internalGroup.getGroupUUID(),
-              internalGroup.getCreatedOn());
-      accountGroup.setDescription(internalGroup.getDescription());
-      accountGroup.setOwnerGroupUUID(internalGroup.getOwnerGroupUUID());
-      accountGroup.setVisibleToAll(internalGroup.isVisibleToAll());
-
-      return create(
-          Source.NOTE_DB,
-          accountGroup,
-          internalGroup
-              .getMembers()
-              .stream()
-              .map(
-                  accountId ->
-                      new AccountGroupMember(new AccountGroupMember.Key(accountId, groupId)))
-              .collect(toImmutableSet()),
-          auditLogReader.getMembersAudit(repo, uuid),
-          internalGroup
-              .getSubgroups()
-              .stream()
-              .map(
-                  subgroupUuid ->
-                      new AccountGroupById(new AccountGroupById.Key(groupId, subgroupUuid)))
-              .collect(toImmutableSet()),
-          auditLogReader.getSubgroupsAudit(repo, uuid));
-    }
-
-    public static GroupBundle fromReviewDb(ReviewDb db, AccountGroup.UUID groupUuid)
-        throws OrmException {
-      JdbcSchema jdbcSchema = ReviewDbWrapper.unwrapJbdcSchema(db);
-      AccountGroup group = readAccountGroupFromReviewDb(jdbcSchema, groupUuid);
-      AccountGroup.Id groupId = group.getId();
-
-      return create(
-          Source.REVIEW_DB,
-          group,
-          readAccountGroupMembersFromReviewDb(jdbcSchema, groupId),
-          readAccountGroupMemberAuditsFromReviewDb(jdbcSchema, groupId),
-          readAccountGroupSubgroupsFromReviewDb(jdbcSchema, groupId),
-          readAccountGroupSubgroupAuditsFromReviewDb(jdbcSchema, groupId));
-    }
-
-    private static AccountGroup readAccountGroupFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.UUID groupUuid) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT group_id,"
-                      + " name,"
-                      + " created_on,"
-                      + " description,"
-                      + " owner_group_uuid,"
-                      + " visible_to_all"
-                      + " FROM account_groups"
-                      + " WHERE group_uuid = '"
-                      + groupUuid.get()
-                      + "'")) {
-        if (!rs.next()) {
-          throw new OrmException(String.format("Group %s not found", groupUuid));
-        }
-
-        AccountGroup.Id groupId = new AccountGroup.Id(rs.getInt(1));
-        AccountGroup.NameKey groupName = new AccountGroup.NameKey(rs.getString(2));
-        Timestamp createdOn = rs.getTimestamp(3);
-        String description = rs.getString(4);
-        AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID(rs.getString(5));
-        boolean visibleToAll = "Y".equals(rs.getString(6));
-
-        AccountGroup group = new AccountGroup(groupName, groupId, groupUuid, createdOn);
-        group.setDescription(description);
-        group.setOwnerGroupUUID(ownerGroupUuid);
-        group.setVisibleToAll(visibleToAll);
-
-        if (rs.next()) {
-          throw new OrmException(String.format("Group UUID %s is ambiguous", groupUuid));
-        }
-
-        return group;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format("Failed to read account group %s from ReviewDb", groupUuid.get()), e);
-      }
-    }
-
-    private static List<AccountGroupMember> readAccountGroupMembersFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.Id groupId) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT account_id"
-                      + " FROM account_group_members"
-                      + " WHERE group_id = '"
-                      + groupId.get()
-                      + "'")) {
-        List<AccountGroupMember> members = new ArrayList<>();
-        while (rs.next()) {
-          Account.Id accountId = new Account.Id(rs.getInt(1));
-          members.add(new AccountGroupMember(new AccountGroupMember.Key(accountId, groupId)));
-        }
-        return members;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format(
-                "Failed to read members of account group %s from ReviewDb", groupId.get()),
-            e);
-      }
-    }
-
-    private static List<AccountGroupMemberAudit> readAccountGroupMemberAuditsFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.Id groupId) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT account_id, added_by, added_on, removed_by, removed_on"
-                      + " FROM account_group_members_audit"
-                      + " WHERE group_id = '"
-                      + groupId.get()
-                      + "'")) {
-        List<AccountGroupMemberAudit> audits = new ArrayList<>();
-        while (rs.next()) {
-          Account.Id accountId = new Account.Id(rs.getInt(1));
-
-          Account.Id addedBy = new Account.Id(rs.getInt(2));
-          Timestamp addedOn = rs.getTimestamp(3);
-
-          Timestamp removedOn = rs.getTimestamp(5);
-          Account.Id removedBy = removedOn != null ? new Account.Id(rs.getInt(4)) : null;
-
-          AccountGroupMemberAudit.Key key =
-              new AccountGroupMemberAudit.Key(accountId, groupId, addedOn);
-          AccountGroupMemberAudit audit = new AccountGroupMemberAudit(key, addedBy);
-          audit.removed(removedBy, removedOn);
-          audits.add(audit);
-        }
-        return audits;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format(
-                "Failed to read member audits of account group %s from ReviewDb", groupId.get()),
-            e);
-      }
-    }
-
-    private static List<AccountGroupById> readAccountGroupSubgroupsFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.Id groupId) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT include_uuid"
-                      + " FROM account_group_by_id"
-                      + " WHERE group_id = '"
-                      + groupId.get()
-                      + "'")) {
-        List<AccountGroupById> subgroups = new ArrayList<>();
-        while (rs.next()) {
-          AccountGroup.UUID includedGroupUuid = new AccountGroup.UUID(rs.getString(1));
-          subgroups.add(new AccountGroupById(new AccountGroupById.Key(groupId, includedGroupUuid)));
-        }
-        return subgroups;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format(
-                "Failed to read subgroups of account group %s from ReviewDb", groupId.get()),
-            e);
-      }
-    }
-
-    private static List<AccountGroupByIdAud> readAccountGroupSubgroupAuditsFromReviewDb(
-        JdbcSchema jdbcSchema, AccountGroup.Id groupId) throws OrmException {
-      try (Statement stmt = jdbcSchema.getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT include_uuid, added_by, added_on, removed_by, removed_on"
-                      + " FROM account_group_by_id_aud"
-                      + " WHERE group_id = '"
-                      + groupId.get()
-                      + "'")) {
-        List<AccountGroupByIdAud> audits = new ArrayList<>();
-        while (rs.next()) {
-          AccountGroup.UUID includedGroupUuid = new AccountGroup.UUID(rs.getString(1));
-
-          Account.Id addedBy = new Account.Id(rs.getInt(2));
-          Timestamp addedOn = rs.getTimestamp(3);
-
-          Timestamp removedOn = rs.getTimestamp(5);
-          Account.Id removedBy = removedOn != null ? new Account.Id(rs.getInt(4)) : null;
-
-          AccountGroupByIdAud.Key key =
-              new AccountGroupByIdAud.Key(groupId, includedGroupUuid, addedOn);
-          AccountGroupByIdAud audit = new AccountGroupByIdAud(key, addedBy);
-          audit.removed(removedBy, removedOn);
-          audits.add(audit);
-        }
-        return audits;
-      } catch (SQLException e) {
-        throw new OrmException(
-            String.format(
-                "Failed to read subgroup audits of account group %s from ReviewDb", groupId.get()),
-            e);
-      }
-    }
-  }
-
-  private static final Comparator<AccountGroupMember> ACCOUNT_GROUP_MEMBER_COMPARATOR =
-      Comparator.comparingInt((AccountGroupMember m) -> m.getAccountGroupId().get())
-          .thenComparingInt(m -> m.getAccountId().get());
-
-  private static final Comparator<AccountGroupMemberAudit> ACCOUNT_GROUP_MEMBER_AUDIT_COMPARATOR =
-      Comparator.comparingInt((AccountGroupMemberAudit a) -> a.getGroupId().get())
-          .thenComparing(AccountGroupMemberAudit::getAddedOn)
-          .thenComparingInt(a -> a.getAddedBy().get())
-          .thenComparingInt(a -> a.getMemberId().get())
-          .thenComparing(
-              a -> a.getRemovedBy() != null ? a.getRemovedBy().get() : null,
-              nullsLast(naturalOrder()))
-          .thenComparing(AccountGroupMemberAudit::getRemovedOn, nullsLast(naturalOrder()));
-
-  private static final Comparator<AccountGroupById> ACCOUNT_GROUP_BY_ID_COMPARATOR =
-      Comparator.comparingInt((AccountGroupById m) -> m.getGroupId().get())
-          .thenComparing(AccountGroupById::getIncludeUUID);
-
-  private static final Comparator<AccountGroupByIdAud> ACCOUNT_GROUP_BY_ID_AUD_COMPARATOR =
-      Comparator.comparingInt((AccountGroupByIdAud a) -> a.getGroupId().get())
-          .thenComparing(AccountGroupByIdAud::getAddedOn)
-          .thenComparingInt(a -> a.getAddedBy().get())
-          .thenComparing(AccountGroupByIdAud::getIncludeUUID)
-          .thenComparing(
-              a -> a.getRemovedBy() != null ? a.getRemovedBy().get() : null,
-              nullsLast(naturalOrder()))
-          .thenComparing(AccountGroupByIdAud::getRemovedOn, nullsLast(naturalOrder()));
-
-  private static final Comparator<AuditEntry> AUDIT_ENTRY_COMPARATOR =
-      Comparator.comparing(AuditEntry::getTimestamp)
-          .thenComparing(AuditEntry::getAction, Comparator.comparingInt(Action::getOrder));
-
-  public static GroupBundle create(
-      Source source,
-      AccountGroup group,
-      Iterable<AccountGroupMember> members,
-      Iterable<AccountGroupMemberAudit> memberAudit,
-      Iterable<AccountGroupById> byId,
-      Iterable<AccountGroupByIdAud> byIdAudit) {
-    AccountGroup.UUID uuid = group.getGroupUUID();
-    return new AutoValue_GroupBundle.Builder()
-        .source(source)
-        .group(group)
-        .members(
-            logIfNotUnique(
-                source, uuid, members, ACCOUNT_GROUP_MEMBER_COMPARATOR, AccountGroupMember.class))
-        .memberAudit(
-            logIfNotUnique(
-                source,
-                uuid,
-                memberAudit,
-                ACCOUNT_GROUP_MEMBER_AUDIT_COMPARATOR,
-                AccountGroupMemberAudit.class))
-        .byId(
-            logIfNotUnique(
-                source, uuid, byId, ACCOUNT_GROUP_BY_ID_COMPARATOR, AccountGroupById.class))
-        .byIdAudit(
-            logIfNotUnique(
-                source,
-                uuid,
-                byIdAudit,
-                ACCOUNT_GROUP_BY_ID_AUD_COMPARATOR,
-                AccountGroupByIdAud.class))
-        .build();
-  }
-
-  private static <T> ImmutableSet<T> logIfNotUnique(
-      Source source,
-      AccountGroup.UUID uuid,
-      Iterable<T> iterable,
-      Comparator<T> comparator,
-      Class<T> clazz) {
-    List<T> list = Streams.stream(iterable).sorted(comparator).collect(toList());
-    ImmutableSet<T> set = ImmutableSet.copyOf(list);
-    if (set.size() != list.size()) {
-      // One way this can happen is that distinct audit entities can compare equal, because
-      // AccountGroup{MemberAudit,ByIdAud}.Key does not include the addedOn timestamp in its
-      // members() list. However, this particular issue only applies to pure adds, since removedOn
-      // *is* included in equality. As a result, if this happens, it means the audit log is already
-      // corrupt, and it's not clear if we can programmatically repair it. For migrating to NoteDb,
-      // we'll try our best to recreate it, but no guarantees it will match the real sequence of
-      // attempted operations, which is in any case lost in the mists of time.
-      logger.atWarning().log(
-          "group %s in %s has duplicate %s entities: %s",
-          uuid, source, clazz.getSimpleName(), iterable);
-    }
-    return set;
-  }
-
-  static Builder builder() {
-    return new AutoValue_GroupBundle.Builder().members().memberAudit().byId().byIdAudit();
-  }
-
-  public static ImmutableList<String> compareWithAudits(
-      GroupBundle reviewDbBundle, GroupBundle noteDbBundle) {
-    return compare(reviewDbBundle, noteDbBundle, true);
-  }
-
-  public static ImmutableList<String> compareWithoutAudits(
-      GroupBundle reviewDbBundle, GroupBundle noteDbBundle) {
-    return compare(reviewDbBundle, noteDbBundle, false);
-  }
-
-  private static ImmutableList<String> compare(
-      GroupBundle reviewDbBundle, GroupBundle noteDbBundle, boolean compareAudits) {
-    // Normalize the ReviewDb bundle to what we expect in NoteDb. This means that values in error
-    // messages will not reflect the actual data in ReviewDb, but it will make it easier for humans
-    // to see the difference.
-    reviewDbBundle = reviewDbBundle.truncateToSecond();
-    AccountGroup reviewDbGroup = new AccountGroup(reviewDbBundle.group());
-    reviewDbGroup.setDescription(Strings.emptyToNull(reviewDbGroup.getDescription()));
-    reviewDbBundle = reviewDbBundle.toBuilder().group(reviewDbGroup).build();
-
-    checkArgument(
-        reviewDbBundle.source() == Source.REVIEW_DB,
-        "first bundle's source must be %s: %s",
-        Source.REVIEW_DB,
-        reviewDbBundle);
-    checkArgument(
-        noteDbBundle.source() == Source.NOTE_DB,
-        "second bundle's source must be %s: %s",
-        Source.NOTE_DB,
-        noteDbBundle);
-
-    ImmutableList.Builder<String> result = ImmutableList.builder();
-    if (!reviewDbBundle.group().equals(noteDbBundle.group())) {
-      result.add(
-          "AccountGroups differ\n"
-              + ("ReviewDb: " + reviewDbBundle.group() + "\n")
-              + ("NoteDb  : " + noteDbBundle.group()));
-    }
-    if (!reviewDbBundle.members().equals(noteDbBundle.members())) {
-      result.add(
-          "AccountGroupMembers differ\n"
-              + ("ReviewDb: " + reviewDbBundle.members() + "\n")
-              + ("NoteDb  : " + noteDbBundle.members()));
-    }
-    if (compareAudits
-        && !areMemberAuditsConsideredEqual(
-            reviewDbBundle.memberAudit(), noteDbBundle.memberAudit())) {
-      result.add(
-          "AccountGroupMemberAudits differ\n"
-              + ("ReviewDb: " + reviewDbBundle.memberAudit() + "\n")
-              + ("NoteDb  : " + noteDbBundle.memberAudit()));
-    }
-    if (!reviewDbBundle.byId().equals(noteDbBundle.byId())) {
-      result.add(
-          "AccountGroupByIds differ\n"
-              + ("ReviewDb: " + reviewDbBundle.byId() + "\n")
-              + ("NoteDb  : " + noteDbBundle.byId()));
-    }
-    if (compareAudits
-        && !areByIdAuditsConsideredEqual(reviewDbBundle.byIdAudit(), noteDbBundle.byIdAudit())) {
-      result.add(
-          "AccountGroupByIdAudits differ\n"
-              + ("ReviewDb: " + reviewDbBundle.byIdAudit() + "\n")
-              + ("NoteDb  : " + noteDbBundle.byIdAudit()));
-    }
-    return result.build();
-  }
-
-  private static boolean areMemberAuditsConsideredEqual(
-      ImmutableSet<AccountGroupMemberAudit> reviewDbMemberAudits,
-      ImmutableSet<AccountGroupMemberAudit> noteDbMemberAudits) {
-    ListMultimap<String, AuditEntry> reviewDbMemberAuditsByMemberId =
-        toMemberAuditEntriesByMemberId(reviewDbMemberAudits);
-    ListMultimap<String, AuditEntry> noteDbMemberAuditsByMemberId =
-        toMemberAuditEntriesByMemberId(noteDbMemberAudits);
-
-    return areConsideredEqual(reviewDbMemberAuditsByMemberId, noteDbMemberAuditsByMemberId);
-  }
-
-  private static boolean areByIdAuditsConsideredEqual(
-      ImmutableSet<AccountGroupByIdAud> reviewDbByIdAudits,
-      ImmutableSet<AccountGroupByIdAud> noteDbByIdAudits) {
-    ListMultimap<String, AuditEntry> reviewDbByIdAuditsById =
-        toByIdAuditEntriesById(reviewDbByIdAudits);
-    ListMultimap<String, AuditEntry> noteDbByIdAuditsById =
-        toByIdAuditEntriesById(noteDbByIdAudits);
-
-    return areConsideredEqual(reviewDbByIdAuditsById, noteDbByIdAuditsById);
-  }
-
-  private static ListMultimap<String, AuditEntry> toMemberAuditEntriesByMemberId(
-      ImmutableSet<AccountGroupMemberAudit> memberAudits) {
-    return memberAudits
-        .stream()
-        .flatMap(GroupBundle::toAuditEntries)
-        .collect(
-            Multimaps.toMultimap(
-                AuditEntry::getTarget,
-                Function.identity(),
-                MultimapBuilder.hashKeys().arrayListValues()::build));
-  }
-
-  private static Stream<AuditEntry> toAuditEntries(AccountGroupMemberAudit memberAudit) {
-    AuditEntry additionAuditEntry =
-        AuditEntry.create(
-            Action.ADD,
-            memberAudit.getAddedBy(),
-            memberAudit.getMemberId(),
-            memberAudit.getAddedOn());
-    if (memberAudit.isActive()) {
-      return Stream.of(additionAuditEntry);
-    }
-
-    AuditEntry removalAuditEntry =
-        AuditEntry.create(
-            Action.REMOVE,
-            memberAudit.getRemovedBy(),
-            memberAudit.getMemberId(),
-            memberAudit.getRemovedOn());
-    return Stream.of(additionAuditEntry, removalAuditEntry);
-  }
-
-  private static ListMultimap<String, AuditEntry> toByIdAuditEntriesById(
-      ImmutableSet<AccountGroupByIdAud> byIdAudits) {
-    return byIdAudits
-        .stream()
-        .flatMap(GroupBundle::toAuditEntries)
-        .collect(
-            Multimaps.toMultimap(
-                AuditEntry::getTarget,
-                Function.identity(),
-                MultimapBuilder.hashKeys().arrayListValues()::build));
-  }
-
-  private static Stream<AuditEntry> toAuditEntries(AccountGroupByIdAud byIdAudit) {
-    AuditEntry additionAuditEntry =
-        AuditEntry.create(
-            Action.ADD, byIdAudit.getAddedBy(), byIdAudit.getIncludeUUID(), byIdAudit.getAddedOn());
-    if (byIdAudit.isActive()) {
-      return Stream.of(additionAuditEntry);
-    }
-
-    AuditEntry removalAuditEntry =
-        AuditEntry.create(
-            Action.REMOVE,
-            byIdAudit.getRemovedBy(),
-            byIdAudit.getIncludeUUID(),
-            byIdAudit.getRemovedOn());
-    return Stream.of(additionAuditEntry, removalAuditEntry);
-  }
-
-  /**
-   * Determines whether the audit log entries are equal except for redundant entries. Entries of the
-   * same type (addition/removal) which follow directly on each other according to their timestamp
-   * are considered redundant.
-   */
-  private static boolean areConsideredEqual(
-      ListMultimap<String, AuditEntry> reviewDbMemberAuditsByTarget,
-      ListMultimap<String, AuditEntry> noteDbMemberAuditsByTarget) {
-    for (String target : reviewDbMemberAuditsByTarget.keySet()) {
-      ImmutableList<AuditEntry> reviewDbAuditEntries =
-          reviewDbMemberAuditsByTarget
-              .get(target)
-              .stream()
-              .sorted(AUDIT_ENTRY_COMPARATOR)
-              .collect(toImmutableList());
-      ImmutableSet<AuditEntry> noteDbAuditEntries =
-          noteDbMemberAuditsByTarget
-              .get(target)
-              .stream()
-              .sorted(AUDIT_ENTRY_COMPARATOR)
-              .collect(toImmutableSet());
-
-      int reviewDbIndex = 0;
-      for (AuditEntry noteDbAuditEntry : noteDbAuditEntries) {
-        Set<AuditEntry> redundantReviewDbAuditEntries = new HashSet<>();
-        while (reviewDbIndex < reviewDbAuditEntries.size()) {
-          AuditEntry reviewDbAuditEntry = reviewDbAuditEntries.get(reviewDbIndex);
-          if (!reviewDbAuditEntry.getAction().equals(noteDbAuditEntry.getAction())) {
-            break;
-          }
-          redundantReviewDbAuditEntries.add(reviewDbAuditEntry);
-          reviewDbIndex++;
-        }
-
-        // The order of the entries is not perfect as ReviewDb included milliseconds for timestamps
-        // and we cut off everything below seconds due to NoteDb/git. Consequently, we don't have a
-        // way to know in this method in which exact order additions/removals within the same second
-        // happened. The best we can do is to group all additions within the same second as
-        // redundant entries and the removals afterward. To compensate that we possibly group
-        // non-redundant additions/removals, we also accept NoteDb audit entries which just occur
-        // anywhere as ReviewDb audit entries.
-        if (!redundantReviewDbAuditEntries.contains(noteDbAuditEntry)
-            && !reviewDbAuditEntries.contains(noteDbAuditEntry)) {
-          return false;
-        }
-      }
-
-      if (reviewDbIndex < reviewDbAuditEntries.size()) {
-        // Some of the ReviewDb audit log entries aren't matched by NoteDb audit log entries.
-        return false;
-      }
-    }
-    return true;
-  }
-
-  public AccountGroup.Id id() {
-    return group().getId();
-  }
-
-  public AccountGroup.UUID uuid() {
-    return group().getGroupUUID();
-  }
-
-  public abstract Source source();
-
-  public abstract AccountGroup group();
-
-  public abstract ImmutableSet<AccountGroupMember> members();
-
-  public abstract ImmutableSet<AccountGroupMemberAudit> memberAudit();
-
-  public abstract ImmutableSet<AccountGroupById> byId();
-
-  public abstract ImmutableSet<AccountGroupByIdAud> byIdAudit();
-
-  public abstract Builder toBuilder();
-
-  public GroupBundle truncateToSecond() {
-    AccountGroup newGroup = new AccountGroup(group());
-    if (newGroup.getCreatedOn() != null) {
-      newGroup.setCreatedOn(TimeUtil.truncateToSecond(newGroup.getCreatedOn()));
-    }
-    return toBuilder()
-        .group(newGroup)
-        .memberAudit(
-            memberAudit().stream().map(GroupBundle::truncateToSecond).collect(toImmutableSet()))
-        .byIdAudit(
-            byIdAudit().stream().map(GroupBundle::truncateToSecond).collect(toImmutableSet()))
-        .build();
-  }
-
-  private static AccountGroupMemberAudit truncateToSecond(AccountGroupMemberAudit a) {
-    AccountGroupMemberAudit result =
-        new AccountGroupMemberAudit(
-            new AccountGroupMemberAudit.Key(
-                a.getKey().getParentKey(),
-                a.getKey().getGroupId(),
-                TimeUtil.truncateToSecond(a.getKey().getAddedOn())),
-            a.getAddedBy());
-    if (a.getRemovedOn() != null) {
-      result.removed(a.getRemovedBy(), TimeUtil.truncateToSecond(a.getRemovedOn()));
-    }
-    return result;
-  }
-
-  private static AccountGroupByIdAud truncateToSecond(AccountGroupByIdAud a) {
-    AccountGroupByIdAud result =
-        new AccountGroupByIdAud(
-            new AccountGroupByIdAud.Key(
-                a.getKey().getParentKey(),
-                a.getKey().getIncludeUUID(),
-                TimeUtil.truncateToSecond(a.getKey().getAddedOn())),
-            a.getAddedBy());
-    if (a.getRemovedOn() != null) {
-      result.removed(a.getRemovedBy(), TimeUtil.truncateToSecond(a.getRemovedOn()));
-    }
-    return result;
-  }
-
-  public InternalGroup toInternalGroup() {
-    return InternalGroup.create(
-        group(),
-        members().stream().map(AccountGroupMember::getAccountId).collect(toImmutableSet()),
-        byId().stream().map(AccountGroupById::getIncludeUUID).collect(toImmutableSet()));
-  }
-
-  @Override
-  public int hashCode() {
-    throw new UnsupportedOperationException(
-        "hashCode is not supported because equals is not supported");
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    throw new UnsupportedOperationException("Use GroupBundle.compare(a, b) instead of equals");
-  }
-
-  @AutoValue
-  abstract static class AuditEntry {
-    private static AuditEntry create(
-        Action action, Account.Id userId, Account.Id memberId, Timestamp timestamp) {
-      return new AutoValue_GroupBundle_AuditEntry(
-          action, userId, String.valueOf(memberId.get()), timestamp);
-    }
-
-    private static AuditEntry create(
-        Action action, Account.Id userId, AccountGroup.UUID subgroupId, Timestamp timestamp) {
-      return new AutoValue_GroupBundle_AuditEntry(action, userId, subgroupId.get(), timestamp);
-    }
-
-    abstract Action getAction();
-
-    abstract Account.Id getUserId();
-
-    abstract String getTarget();
-
-    abstract Timestamp getTimestamp();
-  }
-
-  enum Action {
-    ADD(1),
-    REMOVE(2);
-
-    private final int order;
-
-    Action(int order) {
-      this.order = order;
-    }
-
-    public int getOrder() {
-      return order;
-    }
-  }
-
-  @AutoValue.Builder
-  abstract static class Builder {
-    abstract Builder source(Source source);
-
-    abstract Builder group(AccountGroup group);
-
-    abstract Builder members(AccountGroupMember... member);
-
-    abstract Builder members(Iterable<AccountGroupMember> member);
-
-    abstract Builder memberAudit(AccountGroupMemberAudit... audit);
-
-    abstract Builder memberAudit(Iterable<AccountGroupMemberAudit> audit);
-
-    abstract Builder byId(AccountGroupById... byId);
-
-    abstract Builder byId(Iterable<AccountGroupById> byId);
-
-    abstract Builder byIdAudit(AccountGroupByIdAud... audit);
-
-    abstract Builder byIdAudit(Iterable<AccountGroupByIdAud> audit);
-
-    abstract GroupBundle build();
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/GroupRebuilder.java b/java/com/google/gerrit/server/schema/GroupRebuilder.java
deleted file mode 100644
index 0157025a..0000000
--- a/java/com/google/gerrit/server/schema/GroupRebuilder.java
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
-
-import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Sets;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.git.meta.VersionedMetaData.BatchMetaDataUpdate;
-import com.google.gerrit.server.group.db.AuditLogFormatter;
-import com.google.gerrit.server.group.db.GroupConfig;
-import com.google.gerrit.server.group.db.InternalGroupCreation;
-import com.google.gerrit.server.group.db.InternalGroupUpdate;
-import com.google.gerrit.server.group.db.InternalGroupUpdate.MemberModification;
-import com.google.gerrit.server.group.db.InternalGroupUpdate.SubgroupModification;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Map;
-import java.util.NavigableSet;
-import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-/** Helper for rebuilding an entire group's NoteDb refs. */
-class GroupRebuilder {
-  private final PersonIdent serverIdent;
-  private final AllUsersName allUsers;
-  private final AuditLogFormatter auditLogFormatter;
-
-  public GroupRebuilder(
-      PersonIdent serverIdent, AllUsersName allUsers, AuditLogFormatter auditLogFormatter) {
-    this.serverIdent = serverIdent;
-    this.allUsers = allUsers;
-    this.auditLogFormatter = auditLogFormatter;
-  }
-
-  public void rebuild(Repository allUsersRepo, GroupBundle bundle, @Nullable BatchRefUpdate bru)
-      throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
-    AccountGroup group = bundle.group();
-    InternalGroupCreation groupCreation =
-        InternalGroupCreation.builder()
-            .setId(bundle.id())
-            .setNameKey(group.getNameKey())
-            .setGroupUUID(group.getGroupUUID())
-            .build();
-    GroupConfig groupConfig = GroupConfig.createForNewGroup(allUsers, allUsersRepo, groupCreation);
-    groupConfig.setAllowSaveEmptyName();
-
-    InternalGroupUpdate.Builder updateBuilder =
-        InternalGroupUpdate.builder()
-            .setOwnerGroupUUID(group.getOwnerGroupUUID())
-            .setVisibleToAll(group.isVisibleToAll())
-            .setUpdatedOn(group.getCreatedOn());
-    if (bundle.group().getDescription() != null) {
-      updateBuilder.setDescription(group.getDescription());
-    }
-    groupConfig.setGroupUpdate(updateBuilder.build(), auditLogFormatter);
-
-    Map<Key, Collection<Event>> events = toEvents(bundle).asMap();
-    PersonIdent nowServerIdent = getServerIdent(events);
-
-    MetaDataUpdate md = createMetaDataUpdate(allUsers, allUsersRepo, bru);
-
-    // Creation is done by the server (unlike later audit events).
-    PersonIdent created = new PersonIdent(nowServerIdent, group.getCreatedOn());
-    md.getCommitBuilder().setAuthor(created);
-    md.getCommitBuilder().setCommitter(created);
-
-    // Rebuild group ref.
-    try (BatchMetaDataUpdate batch = groupConfig.openUpdate(md)) {
-      batch.write(groupConfig, md.getCommitBuilder());
-
-      for (Map.Entry<Key, Collection<Event>> e : events.entrySet()) {
-        InternalGroupUpdate.Builder ub = InternalGroupUpdate.builder();
-        e.getValue().forEach(event -> event.update().accept(ub));
-        ub.setUpdatedOn(e.getKey().when());
-        groupConfig.setGroupUpdate(ub.build(), auditLogFormatter);
-
-        PersonIdent currServerIdent = new PersonIdent(nowServerIdent, e.getKey().when());
-        CommitBuilder cb = new CommitBuilder();
-        cb.setAuthor(
-            e.getKey()
-                .accountId()
-                .map(id -> auditLogFormatter.getParsableAuthorIdent(id, currServerIdent))
-                .orElse(currServerIdent));
-        cb.setCommitter(currServerIdent);
-        batch.write(groupConfig, cb);
-      }
-
-      batch.createRef(groupConfig.getRefName());
-    }
-  }
-
-  private ListMultimap<Key, Event> toEvents(GroupBundle bundle) {
-    ListMultimap<Key, Event> result =
-        MultimapBuilder.treeKeys(Key.COMPARATOR).arrayListValues(1).build();
-    Event e;
-
-    for (AccountGroupMemberAudit a : bundle.memberAudit()) {
-      checkArgument(
-          a.getKey().getGroupId().equals(bundle.id()),
-          "key %s does not match group %s",
-          a.getKey(),
-          bundle.id());
-      Account.Id accountId = a.getKey().getParentKey();
-      e = event(Type.ADD_MEMBER, a.getAddedBy(), a.getKey().getAddedOn(), addMember(accountId));
-      result.put(e.key(), e);
-      if (!a.isActive()) {
-        e = event(Type.REMOVE_MEMBER, a.getRemovedBy(), a.getRemovedOn(), removeMember(accountId));
-        result.put(e.key(), e);
-      }
-    }
-
-    for (AccountGroupByIdAud a : bundle.byIdAudit()) {
-      checkArgument(
-          a.getKey().getParentKey().equals(bundle.id()),
-          "key %s does not match group %s",
-          a.getKey(),
-          bundle.id());
-      AccountGroup.UUID uuid = a.getKey().getIncludeUUID();
-      e = event(Type.ADD_GROUP, a.getAddedBy(), a.getKey().getAddedOn(), addGroup(uuid));
-      result.put(e.key(), e);
-      if (!a.isActive()) {
-        e = event(Type.REMOVE_GROUP, a.getRemovedBy(), a.getRemovedOn(), removeGroup(uuid));
-        result.put(e.key(), e);
-      }
-    }
-
-    // Due to clock skew, audit events may be in the future relative to this machine. Ensure the
-    // fixup event happens after any other events, both for the purposes of sorting Keys correctly
-    // and to avoid non-monotonic timestamps in the commit history.
-    Timestamp maxTs =
-        Stream.concat(result.keySet().stream().map(Key::when), Stream.of(TimeUtil.nowTs()))
-            .max(Comparator.naturalOrder())
-            .get();
-    Timestamp fixupTs = new Timestamp(maxTs.getTime() + 1);
-    e = serverEvent(Type.FIXUP, fixupTs, setCurrentMembership(bundle));
-    result.put(e.key(), e);
-
-    return result;
-  }
-
-  private PersonIdent getServerIdent(Map<Key, Collection<Event>> events) {
-    // Created with MultimapBuilder.treeKeys, so the keySet is navigable.
-    Key lastKey = ((NavigableSet<Key>) events.keySet()).last();
-    checkState(lastKey.type() == Type.FIXUP);
-    return new PersonIdent(
-        serverIdent.getName(),
-        serverIdent.getEmailAddress(),
-        Iterables.getOnlyElement(events.get(lastKey)).when(),
-        serverIdent.getTimeZone());
-  }
-
-  private static MetaDataUpdate createMetaDataUpdate(
-      Project.NameKey projectName, Repository repository, @Nullable BatchRefUpdate batchRefUpdate) {
-    return new MetaDataUpdate(
-        GitReferenceUpdated.DISABLED, projectName, repository, batchRefUpdate);
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> addMember(Account.Id toAdd) {
-    return b -> {
-      MemberModification prev = b.getMemberModification();
-      b.setMemberModification(in -> Sets.union(prev.apply(in), ImmutableSet.of(toAdd)));
-    };
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> removeMember(Account.Id toRemove) {
-    return b -> {
-      MemberModification prev = b.getMemberModification();
-      b.setMemberModification(in -> Sets.difference(prev.apply(in), ImmutableSet.of(toRemove)));
-    };
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> addGroup(AccountGroup.UUID toAdd) {
-    return b -> {
-      SubgroupModification prev = b.getSubgroupModification();
-      b.setSubgroupModification(in -> Sets.union(prev.apply(in), ImmutableSet.of(toAdd)));
-    };
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> removeGroup(AccountGroup.UUID toRemove) {
-    return b -> {
-      SubgroupModification prev = b.getSubgroupModification();
-      b.setSubgroupModification(in -> Sets.difference(prev.apply(in), ImmutableSet.of(toRemove)));
-    };
-  }
-
-  private static Consumer<InternalGroupUpdate.Builder> setCurrentMembership(GroupBundle bundle) {
-    // Overwrite members and subgroups with the current values. The storage layer will do the
-    // set differences to compute the appropriate delta, if any.
-    return b ->
-        b.setMemberModification(
-                in ->
-                    bundle
-                        .members()
-                        .stream()
-                        .map(AccountGroupMember::getAccountId)
-                        .collect(toImmutableSet()))
-            .setSubgroupModification(
-                in ->
-                    bundle
-                        .byId()
-                        .stream()
-                        .map(AccountGroupById::getIncludeUUID)
-                        .collect(toImmutableSet()));
-  }
-
-  private static Event event(
-      Type type,
-      Account.Id accountId,
-      Timestamp when,
-      Consumer<InternalGroupUpdate.Builder> update) {
-    return new AutoValue_GroupRebuilder_Event(type, Optional.of(accountId), when, update);
-  }
-
-  private static Event serverEvent(
-      Type type, Timestamp when, Consumer<InternalGroupUpdate.Builder> update) {
-    return new AutoValue_GroupRebuilder_Event(type, Optional.empty(), when, update);
-  }
-
-  @AutoValue
-  abstract static class Event {
-    abstract Type type();
-
-    abstract Optional<Account.Id> accountId();
-
-    abstract Timestamp when();
-
-    abstract Consumer<InternalGroupUpdate.Builder> update();
-
-    Key key() {
-      return new AutoValue_GroupRebuilder_Key(accountId(), when(), type());
-    }
-  }
-
-  /**
-   * Distinct event types.
-   *
-   * <p>Events at the same time by the same user are batched together by type. The types should
-   * correspond to the possible batch operations supported by AuditService.
-   */
-  enum Type {
-    ADD_MEMBER,
-    REMOVE_MEMBER,
-    ADD_GROUP,
-    REMOVE_GROUP,
-    FIXUP;
-  }
-
-  @AutoValue
-  abstract static class Key {
-    static final Comparator<Key> COMPARATOR =
-        Comparator.comparing(Key::when)
-            .thenComparing(
-                k -> k.accountId().map(Account.Id::get).orElse(null),
-                Comparator.nullsFirst(Comparator.naturalOrder()))
-            .thenComparing(Key::type);
-
-    abstract Optional<Account.Id> accountId();
-
-    abstract Timestamp when();
-
-    abstract Type type();
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/H2.java b/java/com/google/gerrit/server/schema/H2.java
deleted file mode 100644
index 840eaf0..0000000
--- a/java/com/google/gerrit/server/schema/H2.java
+++ /dev/null
@@ -1,63 +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.schema;
-
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import java.nio.file.Path;
-import org.eclipse.jgit.lib.Config;
-
-class H2 extends BaseDataSourceType {
-
-  protected final Config cfg;
-  private final SitePaths site;
-
-  @Inject
-  H2(SitePaths site, @GerritServerConfig Config cfg) {
-    super("org.h2.Driver");
-    this.cfg = cfg;
-    this.site = site;
-  }
-
-  @Override
-  public String getUrl() {
-    String database = cfg.getString("database", null, "database");
-    if (database == null || database.isEmpty()) {
-      database = "db/ReviewDB";
-    }
-    return appendUrlOptions(cfg, createUrl(site.resolve(database)));
-  }
-
-  public static String createUrl(Path path) {
-    return new StringBuilder().append("jdbc:h2:").append(path.toUri().toString()).toString();
-  }
-
-  public static String appendUrlOptions(Config cfg, String url) {
-    long h2CacheSize = cfg.getLong("database", "h2", "cacheSize", -1);
-    boolean h2AutoServer = cfg.getBoolean("database", "h2", "autoServer", false);
-
-    StringBuilder urlBuilder = new StringBuilder().append(url);
-
-    if (h2CacheSize >= 0) {
-      // H2 CACHE_SIZE is always given in KB
-      urlBuilder.append(";CACHE_SIZE=").append(h2CacheSize / 1024);
-    }
-    if (h2AutoServer) {
-      urlBuilder.append(";AUTO_SERVER=TRUE");
-    }
-    return urlBuilder.toString();
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/HANA.java b/java/com/google/gerrit/server/schema/HANA.java
deleted file mode 100644
index f9811c6..0000000
--- a/java/com/google/gerrit/server/schema/HANA.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import static com.google.gerrit.server.schema.JdbcUtil.hostname;
-import static com.google.gerrit.server.schema.JdbcUtil.port;
-
-import com.google.common.base.Strings;
-import com.google.gerrit.server.config.ConfigSection;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import java.io.IOException;
-import org.eclipse.jgit.lib.Config;
-
-class HANA extends BaseDataSourceType {
-
-  private Config cfg;
-
-  @Inject
-  HANA(@GerritServerConfig Config cfg) {
-    super("com.sap.db.jdbc.Driver");
-    this.cfg = cfg;
-  }
-
-  @Override
-  public String getUrl() {
-    final StringBuilder b = new StringBuilder();
-    final ConfigSection dbs = new ConfigSection(cfg, "database");
-    b.append("jdbc:sap://");
-    b.append(hostname(dbs.required("hostname")));
-    b.append(port(dbs.optional("port")));
-    String database = dbs.optional("database");
-    if (!Strings.isNullOrEmpty(database)) {
-      b.append("?databaseName=").append(database);
-    }
-    return b.toString();
-  }
-
-  @Override
-  public ScriptRunner getIndexScript() throws IOException {
-    // HANA uses column tables and should not require additional indices
-    return ScriptRunner.NOOP;
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/JDBC.java b/java/com/google/gerrit/server/schema/JDBC.java
deleted file mode 100644
index d188df4..0000000
--- a/java/com/google/gerrit/server/schema/JDBC.java
+++ /dev/null
@@ -1,36 +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.schema;
-
-import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
-
-class JDBC extends BaseDataSourceType {
-
-  protected final Config cfg;
-
-  @Inject
-  JDBC(@GerritServerConfig Config cfg) {
-    super(ConfigUtil.getRequired(cfg, "database", "driver"));
-    this.cfg = cfg;
-  }
-
-  @Override
-  public String getUrl() {
-    return ConfigUtil.getRequired(cfg, "database", "url");
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java b/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
index 83a0986..1ef69db 100644
--- a/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
+++ b/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.server.config.ThreadSettingsConfig;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.gwtorm.server.OrmException;
+import java.nio.file.Path;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -115,7 +116,7 @@
   private static String getUrl(@GerritServerConfig Config cfg, SitePaths sitePaths) {
     String url = cfg.getString(ACCOUNT_PATCH_REVIEW_DB, null, URL);
     if (url == null) {
-      return H2.createUrl(sitePaths.db_dir.resolve("account_patch_reviews"));
+      return createH2Url(sitePaths.db_dir.resolve("account_patch_reviews"));
     }
     return url;
   }
@@ -352,4 +353,8 @@
     }
     return 0;
   }
+
+  private static String createH2Url(Path path) {
+    return new StringBuilder().append("jdbc:h2:").append(path.toUri().toString()).toString();
+  }
 }
diff --git a/java/com/google/gerrit/server/schema/JdbcUtil.java b/java/com/google/gerrit/server/schema/JdbcUtil.java
index dddf23a..3995339 100644
--- a/java/com/google/gerrit/server/schema/JdbcUtil.java
+++ b/java/com/google/gerrit/server/schema/JdbcUtil.java
@@ -28,6 +28,7 @@
     return hostname;
   }
 
+  // TODO(dborowitz): Still used by plugins post-ReviewDb?
   @UsedAt(UsedAt.Project.PLUGINS_ALL)
   public static String port(String port) {
     if (port != null && !port.isEmpty()) {
diff --git a/java/com/google/gerrit/server/schema/MariaDb.java b/java/com/google/gerrit/server/schema/MariaDb.java
deleted file mode 100644
index 6c5dd35..0000000
--- a/java/com/google/gerrit/server/schema/MariaDb.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.gerrit.server.schema.JdbcUtil.hostname;
-import static com.google.gerrit.server.schema.JdbcUtil.port;
-
-import com.google.gerrit.server.config.ConfigSection;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
-
-class MariaDb extends BaseDataSourceType {
-  private final Config cfg;
-
-  @Inject
-  MariaDb(@GerritServerConfig Config cfg) {
-    super("org.mariadb.jdbc.Driver");
-    this.cfg = cfg;
-  }
-
-  @Override
-  public String getUrl() {
-    StringBuilder b = new StringBuilder();
-    ConfigSection dbs = new ConfigSection(cfg, "database");
-    b.append("jdbc:mariadb://");
-    b.append(hostname(dbs.optional("hostname")));
-    b.append(port(dbs.optional("port")));
-    b.append("/");
-    b.append(dbs.required("database"));
-    b.append("?useBulkStmts=false");
-    return b.toString();
-  }
-
-  @Override
-  public boolean usePool() {
-    // MariaDB has given us trouble with the connection pool,
-    // sometimes the backend disconnects and the pool winds
-    // up with a stale connection. Fortunately opening up
-    // a new MariaDB connection is usually very fast.
-    return false;
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/MaxDb.java b/java/com/google/gerrit/server/schema/MaxDb.java
deleted file mode 100644
index d552eb65..0000000
--- a/java/com/google/gerrit/server/schema/MaxDb.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import static com.google.gerrit.server.schema.JdbcUtil.hostname;
-
-import com.google.gerrit.server.config.ConfigSection;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import java.io.IOException;
-import org.eclipse.jgit.lib.Config;
-
-class MaxDb extends BaseDataSourceType {
-
-  private Config cfg;
-
-  @Inject
-  MaxDb(@GerritServerConfig Config cfg) {
-    super("com.sap.dbtech.jdbc.DriverSapDB");
-    this.cfg = cfg;
-  }
-
-  @Override
-  public String getUrl() {
-    final StringBuilder b = new StringBuilder();
-    final ConfigSection dbs = new ConfigSection(cfg, "database");
-    b.append("jdbc:sapdb://");
-    b.append(hostname(dbs.optional("hostname")));
-    b.append("/");
-    b.append(dbs.required("database"));
-    return b.toString();
-  }
-
-  @Override
-  public ScriptRunner getIndexScript() throws IOException {
-    return getScriptRunner("index_maxdb.sql");
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/MySql.java b/java/com/google/gerrit/server/schema/MySql.java
deleted file mode 100644
index e5f59d7..0000000
--- a/java/com/google/gerrit/server/schema/MySql.java
+++ /dev/null
@@ -1,58 +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.schema;
-
-import static com.google.gerrit.server.schema.JdbcUtil.hostname;
-import static com.google.gerrit.server.schema.JdbcUtil.port;
-
-import com.google.gerrit.server.config.ConfigSection;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
-
-class MySql extends BaseDataSourceType {
-
-  private Config cfg;
-
-  @Inject
-  MySql(@GerritServerConfig Config cfg) {
-    super("com.mysql.jdbc.Driver");
-    this.cfg = cfg;
-  }
-
-  @Override
-  public String getUrl() {
-    final StringBuilder b = new StringBuilder();
-    final ConfigSection dbs = new ConfigSection(cfg, "database");
-    b.append("jdbc:mysql://");
-    b.append(hostname(dbs.optional("hostname")));
-    b.append(port(dbs.optional("port")));
-    b.append("/");
-    b.append(dbs.required("database"));
-    // See
-    // https://stackoverflow.com/questions/42084633/table-name-pattern-can-not-be-null-or-empty-in-java
-    b.append("?nullNamePatternMatchesAll=true");
-    return b.toString();
-  }
-
-  @Override
-  public boolean usePool() {
-    // MySQL has given us trouble with the connection pool,
-    // sometimes the backend disconnects and the pool winds
-    // up with a stale connection. Fortunately opening up
-    // a new MySQL connection is usually very fast.
-    return false;
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/NoChangesReviewDbWrapper.java b/java/com/google/gerrit/server/schema/NoChangesReviewDb.java
similarity index 78%
rename from java/com/google/gerrit/server/schema/NoChangesReviewDbWrapper.java
rename to java/com/google/gerrit/server/schema/NoChangesReviewDb.java
index 7247490..bdb2b0d 100644
--- a/java/com/google/gerrit/server/schema/NoChangesReviewDbWrapper.java
+++ b/java/com/google/gerrit/server/schema/NoChangesReviewDb.java
@@ -27,17 +27,19 @@
 import com.google.gerrit.reviewdb.server.PatchSetAccess;
 import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
+import com.google.gwtorm.server.Access;
 import com.google.gwtorm.server.ListResultSet;
-import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
+import com.google.gwtorm.server.StatementExecutor;
 
 /**
  * Wrapper for ReviewDb that never calls the underlying change tables.
  *
  * <p>See {@link NotesMigrationSchemaFactory} for discussion.
  */
-class NoChangesReviewDbWrapper extends ReviewDbWrapper {
+class NoChangesReviewDb implements ReviewDb {
+  private static final String GONE = "ReviewDb is gone";
+
   private static <T> ResultSet<T> empty() {
     return new ListResultSet<>(ImmutableList.of());
   }
@@ -48,13 +50,12 @@
   private final PatchSetAccess patchSets;
   private final PatchLineCommentAccess patchComments;
 
-  NoChangesReviewDbWrapper(ReviewDb db) {
-    super(db);
-    changes = new Changes(this, delegate);
-    patchSetApprovals = new PatchSetApprovals(this, delegate);
-    changeMessages = new ChangeMessages(this, delegate);
-    patchSets = new PatchSets(this, delegate);
-    patchComments = new PatchLineComments(this, delegate);
+  NoChangesReviewDb() {
+    changes = new Changes();
+    patchSetApprovals = new PatchSetApprovals();
+    changeMessages = new ChangeMessages();
+    patchSets = new PatchSets();
+    patchComments = new PatchLineComments();
   }
 
   @Override
@@ -82,12 +83,37 @@
     return patchComments;
   }
 
+  @Override
+  public int nextChangeId() {
+    throw new UnsupportedOperationException(GONE);
+  }
+
+  @Override
+  public void commit() {}
+
+  @Override
+  public void rollback() {}
+
+  @Override
+  public void updateSchema(StatementExecutor e) {
+    throw new UnsupportedOperationException(GONE);
+  }
+
+  @Override
+  public void pruneSchema(StatementExecutor e) {
+    throw new UnsupportedOperationException(GONE);
+  }
+
+  @Override
+  public Access<?, ?>[] allRelations() {
+    throw new UnsupportedOperationException(GONE);
+  }
+
+  @Override
+  public void close() {}
+
   private static class Changes extends AbstractDisabledAccess<Change, Change.Id>
       implements ChangeAccess {
-    private Changes(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
-      super(wrapper, db.changes());
-    }
-
     @Override
     public ResultSet<Change> all() {
       return empty();
@@ -97,32 +123,24 @@
   private static class ChangeMessages
       extends AbstractDisabledAccess<ChangeMessage, ChangeMessage.Key>
       implements ChangeMessageAccess {
-    private ChangeMessages(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
-      super(wrapper, db.changeMessages());
-    }
-
     @Override
-    public ResultSet<ChangeMessage> byChange(Change.Id id) throws OrmException {
+    public ResultSet<ChangeMessage> byChange(Change.Id id) {
       return empty();
     }
 
     @Override
-    public ResultSet<ChangeMessage> byPatchSet(PatchSet.Id id) throws OrmException {
+    public ResultSet<ChangeMessage> byPatchSet(PatchSet.Id id) {
       return empty();
     }
 
     @Override
-    public ResultSet<ChangeMessage> all() throws OrmException {
+    public ResultSet<ChangeMessage> all() {
       return empty();
     }
   }
 
   private static class PatchSets extends AbstractDisabledAccess<PatchSet, PatchSet.Id>
       implements PatchSetAccess {
-    private PatchSets(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
-      super(wrapper, db.patchSets());
-    }
-
     @Override
     public ResultSet<PatchSet> byChange(Change.Id id) {
       return empty();
@@ -137,10 +155,6 @@
   private static class PatchSetApprovals
       extends AbstractDisabledAccess<PatchSetApproval, PatchSetApproval.Key>
       implements PatchSetApprovalAccess {
-    private PatchSetApprovals(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
-      super(wrapper, db.patchSetApprovals());
-    }
-
     @Override
     public ResultSet<PatchSetApproval> byChange(Change.Id id) {
       return empty();
@@ -165,10 +179,6 @@
   private static class PatchLineComments
       extends AbstractDisabledAccess<PatchLineComment, PatchLineComment.Key>
       implements PatchLineCommentAccess {
-    private PatchLineComments(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
-      super(wrapper, db.patchComments());
-    }
-
     @Override
     public ResultSet<PatchLineComment> byChange(Change.Id id) {
       return empty();
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
new file mode 100644
index 0000000..602b639
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
@@ -0,0 +1,215 @@
+// Copyright (C) 2018 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.schema;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
+import static com.google.gerrit.server.notedb.NotesMigration.DISABLE_REVIEW_DB;
+import static com.google.gerrit.server.notedb.NotesMigration.PRIMARY_STORAGE;
+import static com.google.gerrit.server.notedb.NotesMigration.READ;
+import static com.google.gerrit.server.notedb.NotesMigration.SECTION_NOTE_DB;
+import static com.google.gerrit.server.notedb.NotesMigration.WRITE;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
+import com.google.gerrit.server.notedb.NoteDbSchemaVersionManager;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.stream.IntStream;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+
+public class NoteDbSchemaUpdater {
+  private final Config cfg;
+  private final AllProjectsName allProjectsName;
+  private final AllUsersName allUsersName;
+  private final GitRepositoryManager repoManager;
+  private final SchemaCreator schemaCreator;
+  private final NotesMigration notesMigration;
+  private final NoteDbSchemaVersionManager versionManager;
+  private final NoteDbSchemaVersion.Arguments args;
+  private final ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions;
+
+  @Inject
+  NoteDbSchemaUpdater(
+      @GerritServerConfig Config cfg,
+      AllUsersName allUsersName,
+      AllProjectsName allProjectsName,
+      GitRepositoryManager repoManager,
+      SchemaCreator schemaCreator,
+      NotesMigration notesMigration,
+      NoteDbSchemaVersionManager versionManager,
+      NoteDbSchemaVersion.Arguments args) {
+    this(
+        cfg,
+        allProjectsName,
+        allUsersName,
+        repoManager,
+        schemaCreator,
+        notesMigration,
+        versionManager,
+        args,
+        NoteDbSchemaVersions.ALL);
+  }
+
+  NoteDbSchemaUpdater(
+      Config cfg,
+      AllProjectsName allProjectsName,
+      AllUsersName allUsersName,
+      GitRepositoryManager repoManager,
+      SchemaCreator schemaCreator,
+      NotesMigration notesMigration,
+      NoteDbSchemaVersionManager versionManager,
+      NoteDbSchemaVersion.Arguments args,
+      ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions) {
+    this.cfg = cfg;
+    this.allProjectsName = allProjectsName;
+    this.allUsersName = allUsersName;
+    this.repoManager = repoManager;
+    this.schemaCreator = schemaCreator;
+    this.notesMigration = notesMigration;
+    this.versionManager = versionManager;
+    this.args = args;
+    this.schemaVersions = schemaVersions;
+  }
+
+  public void update(UpdateUI ui) throws OrmException {
+    if (!notesMigration.commitChangeWrites()) {
+      // TODO(dborowitz): Only necessary to make migration tests pass; remove when NoteDb is the
+      // only option.
+      return;
+    }
+
+    ensureSchemaCreated();
+
+    int currentVersion = versionManager.read();
+    if (currentVersion == 0) {
+      // The only valid case where there is no refs/meta/version is when running 3.x init for the
+      // first time on a site that previously ran init on 2.16. A freshly created 3.x site will have
+      // seeded refs/meta/version during AllProjectsCreator, so it won't hit this block.
+      checkNoteDbConfigFor216();
+    }
+
+    for (int nextVersion : requiredUpgrades(currentVersion, schemaVersions.keySet())) {
+      try {
+        ui.message(String.format("Migrating data to schema %d ...", nextVersion));
+        NoteDbSchemaVersions.get(schemaVersions, nextVersion, args).upgrade(ui);
+        versionManager.increment(nextVersion - 1);
+      } catch (Exception e) {
+        throw new OrmException(
+            String.format("Failed to upgrade to schema version %d", nextVersion), e);
+      }
+    }
+  }
+
+  private void ensureSchemaCreated() throws OrmException {
+    try {
+      try {
+        repoManager.openRepository(allProjectsName).close();
+      } catch (RepositoryNotFoundException e) {
+        schemaCreator.create();
+      }
+    } catch (IOException | ConfigInvalidException e) {
+      throw new OrmException("Cannot initialize Gerrit site");
+    }
+  }
+
+  private void checkNoteDbConfigFor216() throws OrmException {
+    // Check that the NoteDb migration config matches what we expect from a site that both:
+    // * Completed the change migration to NoteDB.
+    // * Ran schema upgrades from a 2.16 final release.
+
+    if (!cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), WRITE, false)
+        || !cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), READ, false)
+        || cfg.getEnum(SECTION_NOTE_DB, CHANGES.key(), PRIMARY_STORAGE, PrimaryStorage.REVIEW_DB)
+            != PrimaryStorage.NOTE_DB
+        || !cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), DISABLE_REVIEW_DB, false)) {
+      throw new OrmException(
+          "You appear to be upgrading from a 2.x site, but the NoteDb change migration was"
+              + " not completed. See documentation:\n"
+              + "https://gerrit-review.googlesource.com/Documentation/note-db.html#migration");
+    }
+
+    // We don't have a direct way to check that 2.16 init was run; the most obvious side effect
+    // would be upgrading the *ReviewDb* schema to the latest 2.16 schema version. But in 3.x we can
+    // no longer access ReviewDb, so we can't check that directly.
+    //
+    // Instead, check for a NoteDb-specific side effect of the migration process: the presence of
+    // the NoteDb group sequence ref. This is created by the schema 163 migration, which was part of
+    // 2.16 and not 2.15.
+    //
+    // There are a few corner cases where we will proceed even if the schema is not fully up to
+    // date:
+    //  * If a user happened to run init from master after schema 163 was added but before 2.16
+    //    final. We assume that someone savvy enough to do that has followed the documented
+    //    requirement of upgrading to 2.16 final before 3.0.
+    //  * If a user ran init in 2.16.x and the upgrade to 163 succeeded but a later update failed.
+    //    In this case the server literally will not start under 2.16. We assume the user will fix
+    //    this and get 2.16 running rather than abandoning 2.16 and jumping to 3.0 at this point.
+    try (Repository allUsers = repoManager.openRepository(allUsersName)) {
+      if (allUsers.exactRef(RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS) == null) {
+        throw new OrmException(
+            "You appear to be upgrading to 3.x from a version prior to 2.16; you must upgrade to"
+                + " 2.16.x first");
+      }
+    } catch (IOException e) {
+      throw new OrmException("Failed to check NoteDb migration state", e);
+    }
+  }
+
+  @VisibleForTesting
+  static ImmutableList<Integer> requiredUpgrades(
+      int currentVersion, ImmutableSortedSet<Integer> allVersions) throws OrmException {
+    int firstVersion = allVersions.first();
+    int latestVersion = allVersions.last();
+    if (currentVersion == latestVersion) {
+      return ImmutableList.of();
+    } else if (currentVersion > latestVersion) {
+      throw new OrmException(
+          String.format(
+              "Cannot downgrade NoteDb schema from version %d to %d",
+              currentVersion, latestVersion));
+    }
+
+    int firstUpgradeVersion;
+    if (currentVersion == 0) {
+      // Bootstrap NoteDb version to minimum supported schema number.
+      firstUpgradeVersion = firstVersion;
+    } else {
+      if (currentVersion < firstVersion - 1) {
+        throw new OrmException(
+            String.format(
+                "Cannot skip NoteDb schema from version %d to %d", currentVersion, firstVersion));
+      }
+      firstUpgradeVersion = currentVersion + 1;
+    }
+    return IntStream.rangeClosed(firstUpgradeVersion, latestVersion)
+        .boxed()
+        .collect(toImmutableList());
+  }
+}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersion.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersion.java
new file mode 100644
index 0000000..e90b2b8
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersion.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2018 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.schema;
+
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Schema upgrade implementation.
+ *
+ * <p>Implementations must define a single public constructor that takes an {@link Arguments}. The
+ * recommended idiom is to pull out whichever individual fields from the {@code Arguments} are
+ * required by this implementation.
+ */
+interface NoteDbSchemaVersion {
+  @Singleton
+  class Arguments {
+    final GitRepositoryManager repoManager;
+    final AllProjectsName allProjects;
+
+    @Inject
+    Arguments(GitRepositoryManager repoManager, AllProjectsName allProjects) {
+      this.repoManager = repoManager;
+      this.allProjects = allProjects;
+    }
+  }
+
+  void upgrade(UpdateUI ui) throws Exception;
+}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java
new file mode 100644
index 0000000..9e26ee9
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2018 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.schema;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.notedb.NoteDbSchemaVersionManager;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.ProvisionException;
+
+public class NoteDbSchemaVersionCheck implements LifecycleListener {
+  public static Module module() {
+    return new LifecycleModule() {
+      @Override
+      protected void configure() {
+        listener().to(NoteDbSchemaVersionCheck.class);
+      }
+    };
+  }
+
+  private final NotesMigration notesMigration;
+  private final NoteDbSchemaVersionManager versionManager;
+  private final SitePaths sitePaths;
+
+  @Inject
+  NoteDbSchemaVersionCheck(
+      NotesMigration notesMigration,
+      NoteDbSchemaVersionManager versionManager,
+      SitePaths sitePaths) {
+    this.notesMigration = notesMigration;
+    this.versionManager = versionManager;
+    this.sitePaths = sitePaths;
+  }
+
+  @Override
+  public void start() {
+    if (!notesMigration.commitChangeWrites()) {
+      // TODO(dborowitz): Only necessary to make migration tests pass; remove when NoteDb is the
+      // only option.
+      return;
+    }
+    try {
+      int current = versionManager.read();
+      if (current == 0) {
+        throw new ProvisionException(
+            String.format(
+                "Schema not yet initialized. Run init to initialize the schema:\n"
+                    + "$ java -jar gerrit.war init -d %s",
+                sitePaths.site_path.toAbsolutePath()));
+      }
+      int expected = NoteDbSchemaVersions.LATEST;
+      if (current != expected) {
+        String advice =
+            current > expected
+                ? "Downgrade is not supported"
+                : String.format(
+                    "Run init to upgrade:\n$ java -jar %s init -d %s",
+                    sitePaths.gerrit_war.toAbsolutePath(), sitePaths.site_path.toAbsolutePath());
+        throw new ProvisionException(
+            String.format(
+                "Unsupported schema version %d; expected schema version %d. %s",
+                current, expected, advice));
+      }
+    } catch (OrmException e) {
+      throw new ProvisionException("Failed to read NoteDb schema version", e);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // Do nothing.
+  }
+}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
new file mode 100644
index 0000000..ffd4760
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2018 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.schema;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
+import static java.util.Comparator.naturalOrder;
+
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.primitives.Ints;
+import com.google.gerrit.server.UsedAt;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class NoteDbSchemaVersions {
+  static final ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> ALL =
+      // List all supported NoteDb schema versions here.
+      Stream.of(Schema_180.class)
+          .collect(toImmutableSortedMap(naturalOrder(), v -> guessVersion(v).get(), v -> v));
+
+  public static final int FIRST = ALL.firstKey();
+  public static final int LATEST = ALL.lastKey();
+
+  // TODO(dborowitz): Migrate delete-project plugin to use this implementation.
+  @UsedAt(UsedAt.Project.PLUGIN_DELETE_PROJECT)
+  public static Optional<Integer> guessVersion(Class<?> c) {
+    String prefix = "Schema_";
+    if (!c.getSimpleName().startsWith(prefix)) {
+      return Optional.empty();
+    }
+    return Optional.ofNullable(Ints.tryParse(c.getSimpleName().substring(prefix.length())));
+  }
+
+  public static NoteDbSchemaVersion get(
+      ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions,
+      int i,
+      NoteDbSchemaVersion.Arguments args) {
+    Class<? extends NoteDbSchemaVersion> clazz = schemaVersions.get(i);
+    checkArgument(clazz != null, "Schema version not found: %s", i);
+    try {
+      return clazz.getDeclaredConstructor(NoteDbSchemaVersion.Arguments.class).newInstance(args);
+    } catch (InstantiationException
+        | IllegalAccessException
+        | NoSuchMethodException
+        | InvocationTargetException e) {
+      throw new IllegalStateException("failed to invoke constructor on " + clazz.getName(), e);
+    }
+  }
+}
diff --git a/java/com/google/gerrit/server/schema/NotesMigrationSchemaFactory.java b/java/com/google/gerrit/server/schema/NotesMigrationSchemaFactory.java
index 0d95610..c533619 100644
--- a/java/com/google/gerrit/server/schema/NotesMigrationSchemaFactory.java
+++ b/java/com/google/gerrit/server/schema/NotesMigrationSchemaFactory.java
@@ -14,7 +14,9 @@
 
 package com.google.gerrit.server.schema;
 
-import com.google.gerrit.reviewdb.server.DisallowReadFromChangesReviewDbWrapper;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.gerrit.reviewdb.server.DisallowedReviewDb;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gwtorm.server.OrmException;
@@ -24,13 +26,10 @@
 
 @Singleton
 public class NotesMigrationSchemaFactory implements SchemaFactory<ReviewDb> {
-  private final SchemaFactory<ReviewDb> delegate;
   private final NotesMigration migration;
 
   @Inject
-  NotesMigrationSchemaFactory(
-      @ReviewDbFactory SchemaFactory<ReviewDb> delegate, NotesMigration migration) {
-    this.delegate = delegate;
+  NotesMigrationSchemaFactory(NotesMigration migration) {
     this.migration = migration;
   }
 
@@ -43,7 +42,7 @@
     //    in ReviewDb, it is generally programmer error to read changes from ReviewDb. However,
     //    since ReviewDb is still the primary storage for most or all changes, we still need to
     //    support writing to ReviewDb. This behavior is accomplished by wrapping in a
-    //    DisallowReadFromChangesReviewDbWrapper.
+    //    DisallowedReviewDb.
     //
     //    Some codepaths might need to be able to read from ReviewDb if they really need to,
     //    because they need to operate on the underlying source of truth, for example when reading
@@ -56,22 +55,22 @@
     //    continue to function.
     //
     //    This is accomplished by setting the delegate ReviewDb *underneath*
-    //    DisallowReadFromChanges to be a complete no-op, with NoChangesReviewDbWrapper. With this
-    //    wrapper, all read operations return no results, and write operations silently do nothing.
-    //    This wrapper is not a public class and nobody should ever attempt to unwrap it.
+    //    DisallowReadFromChanges to be a complete no-op, with NoChangesReviewDb. With this
+    //    stub implementation, all read operations return no results, and write operations silently
+    //    do nothing. This implementation is not a public class and callers couldn't do anything
+    //    useful with it even if it were.
 
-    // First create the wrappers which can not be removed by ReviewDbUtil#unwrapDb(ReviewDb).
-    ReviewDb db = delegate.open();
-    if (migration.readChanges() && migration.disableChangeReviewDb()) {
-      // Disable writes to change tables in ReviewDb (ReviewDb access for changes are No-Ops).
-      db = new NoChangesReviewDbWrapper(db);
-    }
+    // First create the underlying stub.
+    checkState(migration.readChanges() && migration.disableChangeReviewDb());
+    // Disable writes to change tables in ReviewDb (ReviewDb access for changes are No-Ops); all
+    // other table accesses throw runtime exceptions.
+    ReviewDb db = new NoChangesReviewDb();
 
     // Second create the wrappers which can be removed by ReviewDbUtil#unwrapDb(ReviewDb).
     if (migration.readChanges()) {
       // If reading changes from NoteDb is configured, changes should not be read from ReviewDb.
       // Make sure that any attempt to read a change from ReviewDb anyway fails with an exception.
-      db = new DisallowReadFromChangesReviewDbWrapper(db);
+      db = new DisallowedReviewDb(db);
     }
     return db;
   }
diff --git a/java/com/google/gerrit/server/schema/Oracle.java b/java/com/google/gerrit/server/schema/Oracle.java
deleted file mode 100644
index 4ff7243..0000000
--- a/java/com/google/gerrit/server/schema/Oracle.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import static com.google.gerrit.server.schema.JdbcUtil.hostname;
-import static com.google.gerrit.server.schema.JdbcUtil.port;
-
-import com.google.gerrit.server.config.ConfigSection;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import org.eclipse.jgit.lib.Config;
-
-public class Oracle extends BaseDataSourceType {
-  private Config cfg;
-
-  @Inject
-  public Oracle(@GerritServerConfig Config cfg) {
-    super("oracle.jdbc.driver.OracleDriver");
-    this.cfg = cfg;
-  }
-
-  @Override
-  public String getUrl() {
-    final StringBuilder b = new StringBuilder();
-    final ConfigSection dbc = new ConfigSection(cfg, "database");
-    b.append("jdbc:oracle:thin:@");
-    b.append(hostname(dbc.optional("hostname")));
-    b.append(port(dbc.optional("port")));
-    b.append(":");
-    b.append(dbc.required("instance"));
-    return b.toString();
-  }
-
-  @Override
-  public String getValidationQuery() {
-    return "select 1 from dual";
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/PostgreSQL.java b/java/com/google/gerrit/server/schema/PostgreSQL.java
deleted file mode 100644
index d6aee94..0000000
--- a/java/com/google/gerrit/server/schema/PostgreSQL.java
+++ /dev/null
@@ -1,52 +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.schema;
-
-import static com.google.gerrit.server.schema.JdbcUtil.hostname;
-import static com.google.gerrit.server.schema.JdbcUtil.port;
-
-import com.google.gerrit.server.config.ConfigSection;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.inject.Inject;
-import java.io.IOException;
-import org.eclipse.jgit.lib.Config;
-
-class PostgreSQL extends BaseDataSourceType {
-
-  private Config cfg;
-
-  @Inject
-  PostgreSQL(@GerritServerConfig Config cfg) {
-    super("org.postgresql.Driver");
-    this.cfg = cfg;
-  }
-
-  @Override
-  public String getUrl() {
-    final StringBuilder b = new StringBuilder();
-    final ConfigSection dbc = new ConfigSection(cfg, "database");
-    b.append("jdbc:postgresql://");
-    b.append(hostname(dbc.optional("hostname")));
-    b.append(port(dbc.optional("port")));
-    b.append("/");
-    b.append(dbc.required("database"));
-    return b.toString();
-  }
-
-  @Override
-  public ScriptRunner getIndexScript() throws IOException {
-    return getScriptRunner("index_postgres.sql");
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/ReviewDbDatabaseProvider.java b/java/com/google/gerrit/server/schema/ReviewDbDatabaseProvider.java
deleted file mode 100644
index 0fbaeca..0000000
--- a/java/com/google/gerrit/server/schema/ReviewDbDatabaseProvider.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2009 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.Database;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-import com.google.inject.name.Named;
-import javax.sql.DataSource;
-
-/** Provides the {@code Database<ReviewDb>} database handle. */
-final class ReviewDbDatabaseProvider implements Provider<Database<ReviewDb>> {
-  private final DataSource datasource;
-
-  @Inject
-  ReviewDbDatabaseProvider(@Named("ReviewDb") final DataSource ds) {
-    datasource = ds;
-  }
-
-  @Override
-  public Database<ReviewDb> get() {
-    try {
-      return new Database<>(datasource, ReviewDb.class);
-    } catch (OrmException e) {
-      throw new ProvisionException("Cannot create ReviewDb", e);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/ReviewDbSchemaModule.java b/java/com/google/gerrit/server/schema/ReviewDbSchemaModule.java
index da8898a4..c777133 100644
--- a/java/com/google/gerrit/server/schema/ReviewDbSchemaModule.java
+++ b/java/com/google/gerrit/server/schema/ReviewDbSchemaModule.java
@@ -27,8 +27,10 @@
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
 import com.google.gerrit.server.config.GerritServerId;
 import com.google.gerrit.server.config.GerritServerIdProvider;
+import com.google.gerrit.server.index.group.GroupIndexCollection;
 import org.eclipse.jgit.lib.PersonIdent;
 
+// TODO(dborowitz): Rename, since this no longer has anything to do with ReviewDb.
 /** Validate the schema and connect to Git. */
 public class ReviewDbSchemaModule extends FactoryModule {
   @Override
@@ -49,5 +51,11 @@
         .annotatedWith(GerritServerId.class)
         .toProvider(GerritServerIdProvider.class)
         .in(SINGLETON);
+
+    // It feels wrong to have this binding in a seemingly unrelated module, but it's a dependency of
+    // SchemaCreatorImpl, so it's needed.
+    // TODO(dborowitz): Is there any way to untangle this?
+    bind(GroupIndexCollection.class);
+    bind(SchemaCreator.class).to(SchemaCreatorImpl.class);
   }
 }
diff --git a/java/com/google/gerrit/server/schema/ReviewDbSchemaUpdater.java b/java/com/google/gerrit/server/schema/ReviewDbSchemaUpdater.java
deleted file mode 100644
index 86f4560..0000000
--- a/java/com/google/gerrit/server/schema/ReviewDbSchemaUpdater.java
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (C) 2009 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.schema;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.AnonymousCowardName;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.Provider;
-import com.google.inject.Stage;
-import java.io.IOException;
-import java.sql.SQLException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.PersonIdent;
-
-/** Creates or updates the current database schema. */
-public class ReviewDbSchemaUpdater {
-  private final SchemaFactory<ReviewDb> schema;
-  private final ReviewDbSchemaCreator creator;
-  private final Provider<ReviewDbSchemaVersion> updater;
-
-  @Inject
-  ReviewDbSchemaUpdater(
-      @ReviewDbFactory SchemaFactory<ReviewDb> schema,
-      ReviewDbSchemaCreator creator,
-      Injector parent) {
-    this.schema = schema;
-    this.creator = creator;
-    this.updater = buildInjector(parent).getProvider(ReviewDbSchemaVersion.class);
-  }
-
-  private static Injector buildInjector(Injector parent) {
-    // Use DEVELOPMENT mode to allow lazy initialization of the
-    // graph. This avoids touching ancient schema versions that
-    // are behind this installation's current version.
-    return Guice.createInjector(
-        Stage.DEVELOPMENT,
-        new AbstractModule() {
-          @Override
-          protected void configure() {
-            bind(ReviewDbSchemaVersion.class).to(ReviewDbSchemaVersion.C);
-
-            for (Key<?> k :
-                new Key<?>[] {
-                  Key.get(PersonIdent.class, GerritPersonIdent.class),
-                  Key.get(String.class, AnonymousCowardName.class),
-                  Key.get(Config.class, GerritServerConfig.class),
-                }) {
-              rebind(parent, k);
-            }
-
-            for (Class<?> c :
-                new Class<?>[] {
-                  AllProjectsName.class,
-                  AllUsersCreator.class,
-                  AllUsersName.class,
-                  GitRepositoryManager.class,
-                  SitePaths.class,
-                  SystemGroupBackend.class,
-                }) {
-              rebind(parent, Key.get(c));
-            }
-          }
-
-          private <T> void rebind(Injector parent, Key<T> c) {
-            bind(c).toProvider(parent.getProvider(c));
-          }
-        });
-  }
-
-  public void update(UpdateUI ui) throws OrmException {
-    try (ReviewDb db = ReviewDbUtil.unwrapDb(schema.open())) {
-
-      final ReviewDbSchemaVersion u = updater.get();
-      final CurrentSchemaVersion version = getSchemaVersion(db);
-      if (version == null) {
-        try {
-          creator.create(db);
-        } catch (IOException | ConfigInvalidException e) {
-          throw new OrmException("Cannot initialize schema", e);
-        }
-
-      } else {
-        try {
-          u.check(ui, version, db);
-        } catch (SQLException e) {
-          throw new OrmException("Cannot upgrade schema", e);
-        }
-      }
-    }
-  }
-
-  @VisibleForTesting
-  public ReviewDbSchemaVersion getLatestSchemaVersion() {
-    return updater.get();
-  }
-
-  private CurrentSchemaVersion getSchemaVersion(ReviewDb db) {
-    try {
-      return db.schemaVersion().get(new CurrentSchemaVersion.Key());
-    } catch (OrmException e) {
-      return null;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/ReviewDbSchemaVersion.java b/java/com/google/gerrit/server/schema/ReviewDbSchemaVersion.java
deleted file mode 100644
index 10a2d39..0000000
--- a/java/com/google/gerrit/server/schema/ReviewDbSchemaVersion.java
+++ /dev/null
@@ -1,216 +0,0 @@
-// Copyright (C) 2009 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.schema;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.Lists;
-import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.UsedAt;
-import com.google.gwtorm.jdbc.JdbcExecutor;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Provider;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/** A version of the database schema. */
-public abstract class ReviewDbSchemaVersion {
-  /** The current schema version. */
-  public static final Class<Schema_170> C = Schema_170.class;
-
-  public static int getBinaryVersion() {
-    return guessVersion(C);
-  }
-
-  private final Provider<? extends ReviewDbSchemaVersion> prior;
-  private final int versionNbr;
-
-  protected ReviewDbSchemaVersion(Provider<? extends ReviewDbSchemaVersion> prior) {
-    this.prior = prior;
-    this.versionNbr = guessVersion(getClass());
-  }
-
-  @UsedAt(UsedAt.Project.PLUGIN_DELETE_PROJECT)
-  public static int guessVersion(Class<?> c) {
-    String n = c.getName();
-    n = n.substring(n.lastIndexOf('_') + 1);
-    while (n.startsWith("0")) {
-      n = n.substring(1);
-    }
-    return Integer.parseInt(n);
-  }
-
-  /** @return the {@link CurrentSchemaVersion#versionNbr} this step targets. */
-  public final int getVersionNbr() {
-    return versionNbr;
-  }
-
-  @VisibleForTesting
-  public final ReviewDbSchemaVersion getPrior() {
-    return prior.get();
-  }
-
-  public final void check(UpdateUI ui, CurrentSchemaVersion curr, ReviewDb db)
-      throws OrmException, SQLException {
-    if (curr.versionNbr == versionNbr) {
-      // Nothing to do, we are at the correct schema.
-    } else if (curr.versionNbr > versionNbr) {
-      throw new OrmException(
-          "Cannot downgrade database schema from version "
-              + curr.versionNbr
-              + " to "
-              + versionNbr
-              + ".");
-    } else {
-      upgradeFrom(ui, curr, db);
-    }
-  }
-
-  /** Runs check on the prior schema version, and then upgrades. */
-  private void upgradeFrom(UpdateUI ui, CurrentSchemaVersion curr, ReviewDb db)
-      throws OrmException, SQLException {
-    List<ReviewDbSchemaVersion> pending = pending(curr.versionNbr);
-    updateSchema(pending, ui, db);
-    migrateData(pending, ui, curr, db);
-
-    JdbcSchema s = (JdbcSchema) db;
-    final List<String> pruneList = new ArrayList<>();
-    s.pruneSchema(
-        new StatementExecutor() {
-          @Override
-          public void execute(String sql) {
-            pruneList.add(sql);
-          }
-
-          @Override
-          public void close() {
-            // Do nothing.
-          }
-        });
-
-    try (JdbcExecutor e = new JdbcExecutor(s)) {
-      if (!pruneList.isEmpty()) {
-        ui.pruneSchema(e, pruneList);
-      }
-    }
-  }
-
-  private List<ReviewDbSchemaVersion> pending(int curr) {
-    List<ReviewDbSchemaVersion> r = Lists.newArrayListWithCapacity(versionNbr - curr);
-    for (ReviewDbSchemaVersion v = this; curr < v.getVersionNbr(); v = v.prior.get()) {
-      r.add(v);
-    }
-    Collections.reverse(r);
-    return r;
-  }
-
-  private void updateSchema(List<ReviewDbSchemaVersion> pending, UpdateUI ui, ReviewDb db)
-      throws OrmException, SQLException {
-    for (ReviewDbSchemaVersion v : pending) {
-      ui.message(String.format("Upgrading schema to %d ...", v.getVersionNbr()));
-      v.preUpdateSchema(db);
-    }
-
-    JdbcSchema s = (JdbcSchema) db;
-    try (JdbcExecutor e = new JdbcExecutor(s)) {
-      s.updateSchema(e);
-    }
-  }
-
-  /**
-   * Invoked before updateSchema adds new columns/tables.
-   *
-   * @param db open database handle.
-   * @throws OrmException if a Gerrit-specific exception occurred.
-   * @throws SQLException if an underlying SQL exception occurred.
-   */
-  protected void preUpdateSchema(ReviewDb db) throws OrmException, SQLException {}
-
-  private void migrateData(
-      List<ReviewDbSchemaVersion> pending, UpdateUI ui, CurrentSchemaVersion curr, ReviewDb db)
-      throws OrmException, SQLException {
-    for (ReviewDbSchemaVersion v : pending) {
-      Stopwatch sw = Stopwatch.createStarted();
-      ui.message(String.format("Migrating data to schema %d ...", v.getVersionNbr()));
-      v.migrateData(db, ui);
-      v.finish(curr, db);
-      ui.message(String.format("\t> Done (%.3f s)", sw.elapsed(TimeUnit.MILLISECONDS) / 1000d));
-    }
-  }
-
-  /**
-   * Invoked between updateSchema (adds new columns/tables) and pruneSchema (removes deleted
-   * columns/tables).
-   *
-   * @param db open database handle.
-   * @param ui interface for interacting with the user.
-   * @throws OrmException if a Gerrit-specific exception occurred.
-   * @throws SQLException if an underlying SQL exception occurred.
-   */
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {}
-
-  /** Mark the current schema version. */
-  protected void finish(CurrentSchemaVersion curr, ReviewDb db) throws OrmException {
-    curr.versionNbr = versionNbr;
-    db.schemaVersion().update(Collections.singleton(curr));
-  }
-
-  /** Rename an existing table. */
-  protected static void renameTable(ReviewDb db, String from, String to) throws OrmException {
-    JdbcSchema s = (JdbcSchema) db;
-    try (JdbcExecutor e = new JdbcExecutor(s)) {
-      s.renameTable(e, from, to);
-    }
-  }
-
-  /** Rename an existing column. */
-  protected static void renameColumn(ReviewDb db, String table, String from, String to)
-      throws OrmException {
-    JdbcSchema s = (JdbcSchema) db;
-    try (JdbcExecutor e = new JdbcExecutor(s)) {
-      s.renameColumn(e, table, from, to);
-    }
-  }
-
-  /** Execute an SQL statement. */
-  protected static void execute(ReviewDb db, String sql) throws SQLException {
-    try (Statement s = newStatement(db)) {
-      s.execute(sql);
-    }
-  }
-
-  /** Open a new single statement. */
-  protected static Statement newStatement(ReviewDb db) throws SQLException {
-    return ((JdbcSchema) db).getConnection().createStatement();
-  }
-
-  /** Open a new prepared statement. */
-  protected static PreparedStatement prepareStatement(ReviewDb db, String sql) throws SQLException {
-    return ((JdbcSchema) db).getConnection().prepareStatement(sql);
-  }
-
-  /** Open a new statement executor. */
-  protected static JdbcExecutor newExecutor(ReviewDb db) throws OrmException {
-    return new JdbcExecutor(((JdbcSchema) db).getConnection());
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/ReviewDbSchemaVersionCheck.java b/java/com/google/gerrit/server/schema/ReviewDbSchemaVersionCheck.java
deleted file mode 100644
index 12abc7e..0000000
--- a/java/com/google/gerrit/server/schema/ReviewDbSchemaVersionCheck.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (C) 2009 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.schema;
-
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Module;
-import com.google.inject.ProvisionException;
-
-/** Validates the current schema version. */
-public class ReviewDbSchemaVersionCheck implements LifecycleListener {
-  public static Module module() {
-    return new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().to(ReviewDbSchemaVersionCheck.class);
-      }
-    };
-  }
-
-  private final SchemaFactory<ReviewDb> schema;
-  private final SitePaths site;
-
-  @Inject
-  public ReviewDbSchemaVersionCheck(SchemaFactory<ReviewDb> schemaFactory, SitePaths site) {
-    this.schema = schemaFactory;
-    this.site = site;
-  }
-
-  @Override
-  public void start() {
-    try (ReviewDb db = schema.open()) {
-      final CurrentSchemaVersion currentVer = getSchemaVersion(db);
-      final int expectedVer = ReviewDbSchemaVersion.getBinaryVersion();
-
-      if (currentVer == null) {
-        throw new ProvisionException(
-            "Schema not yet initialized."
-                + "  Run init to initialize the schema:\n"
-                + "$ java -jar gerrit.war init -d "
-                + site.site_path.toAbsolutePath());
-      }
-      if (currentVer.versionNbr < expectedVer) {
-        throw new ProvisionException(
-            "Unsupported schema version "
-                + currentVer.versionNbr
-                + "; expected schema version "
-                + expectedVer
-                + ".  Run init to upgrade:\n"
-                + "$ java -jar "
-                + site.gerrit_war.toAbsolutePath()
-                + " init -d "
-                + site.site_path.toAbsolutePath());
-      } else if (currentVer.versionNbr > expectedVer) {
-        throw new ProvisionException(
-            "Unsupported schema version "
-                + currentVer.versionNbr
-                + "; expected schema version "
-                + expectedVer
-                + ". Downgrade is not supported.");
-      }
-    } catch (OrmException e) {
-      throw new ProvisionException("Cannot read schema_version", e);
-    }
-  }
-
-  @Override
-  public void stop() {}
-
-  private CurrentSchemaVersion getSchemaVersion(ReviewDb db) {
-    try {
-      return db.schemaVersion().get(new CurrentSchemaVersion.Key());
-    } catch (OrmException e) {
-      return null;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_168.java b/java/com/google/gerrit/server/schema/SchemaCreator.java
similarity index 70%
rename from java/com/google/gerrit/server/schema/Schema_168.java
rename to java/com/google/gerrit/server/schema/SchemaCreator.java
index fff4049..78a8422 100644
--- a/java/com/google/gerrit/server/schema/Schema_168.java
+++ b/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -14,13 +14,11 @@
 
 package com.google.gerrit.server.schema;
 
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+import com.google.gwtorm.server.OrmException;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
 
-/** Drop group tables. */
-public class Schema_168 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_168(Provider<Schema_167> prior) {
-    super(prior);
-  }
+/** Populates initial repository data. */
+public interface SchemaCreator {
+  void create() throws OrmException, IOException, ConfigInvalidException;
 }
diff --git a/java/com/google/gerrit/server/schema/ReviewDbSchemaCreator.java b/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java
similarity index 79%
rename from java/com/google/gerrit/server/schema/ReviewDbSchemaCreator.java
rename to java/com/google/gerrit/server/schema/SchemaCreatorImpl.java
index 3a42f07..0d184a3 100644
--- a/java/com/google/gerrit/server/schema/ReviewDbSchemaCreator.java
+++ b/java/com/google/gerrit/server/schema/SchemaCreatorImpl.java
@@ -20,17 +20,11 @@
 import com.google.gerrit.git.RefUpdateUtil;
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.Sequences;
 import com.google.gerrit.server.account.GroupUUID;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.GerritServerId;
-import com.google.gerrit.server.config.SitePath;
-import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
@@ -42,110 +36,60 @@
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
 import com.google.gerrit.server.index.group.GroupIndex;
 import com.google.gerrit.server.index.group.GroupIndexCollection;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gwtorm.jdbc.JdbcExecutor;
-import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Collections;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 
-/** Creates the current database schema and populates initial code rows. */
-public class ReviewDbSchemaCreator {
-  @SitePath private final Path site_path;
-
+// TODO(dborowitz): The current NoteDb implementation mirrors the old ReviewDb code: this class is
+// called to create the site early on in NoteDbSchemaUpdater#update. This logic is a little
+// confusing and could stand to be reworked. Another smell is that this is an interface only for
+// testing purposes.
+public class SchemaCreatorImpl implements SchemaCreator {
   private final GitRepositoryManager repoManager;
   private final AllProjectsCreator allProjectsCreator;
   private final AllUsersCreator allUsersCreator;
   private final AllUsersName allUsersName;
   private final PersonIdent serverUser;
-  private final DataSourceType dataSourceType;
   private final GroupIndexCollection indexCollection;
   private final String serverId;
 
   private final Config config;
   private final MetricMaker metricMaker;
-  private final NotesMigration migration;
   private final AllProjectsName allProjectsName;
 
   @Inject
-  public ReviewDbSchemaCreator(
-      SitePaths site,
+  public SchemaCreatorImpl(
       GitRepositoryManager repoManager,
       AllProjectsCreator ap,
       AllUsersCreator auc,
       AllUsersName allUsersName,
       @GerritPersonIdent PersonIdent au,
-      DataSourceType dst,
-      GroupIndexCollection ic,
-      @GerritServerId String serverId,
-      @GerritServerConfig Config config,
-      MetricMaker metricMaker,
-      NotesMigration migration,
-      AllProjectsName apName) {
-    this(
-        site.site_path,
-        repoManager,
-        ap,
-        auc,
-        allUsersName,
-        au,
-        dst,
-        ic,
-        serverId,
-        config,
-        metricMaker,
-        migration,
-        apName);
-  }
-
-  public ReviewDbSchemaCreator(
-      @SitePath Path site,
-      GitRepositoryManager repoManager,
-      AllProjectsCreator ap,
-      AllUsersCreator auc,
-      AllUsersName allUsersName,
-      @GerritPersonIdent PersonIdent au,
-      DataSourceType dst,
       GroupIndexCollection ic,
       String serverId,
       Config config,
       MetricMaker metricMaker,
-      NotesMigration migration,
       AllProjectsName apName) {
-    site_path = site;
     this.repoManager = repoManager;
     allProjectsCreator = ap;
     allUsersCreator = auc;
     this.allUsersName = allUsersName;
     serverUser = au;
-    dataSourceType = dst;
     indexCollection = ic;
     this.serverId = serverId;
 
     this.config = config;
     this.allProjectsName = apName;
-    this.migration = migration;
     this.metricMaker = metricMaker;
   }
 
-  public void create(ReviewDb db) throws OrmException, IOException, ConfigInvalidException {
-    final JdbcSchema jdbc = (JdbcSchema) db;
-    try (JdbcExecutor e = new JdbcExecutor(jdbc)) {
-      jdbc.updateSchema(e);
-    }
-
-    final CurrentSchemaVersion sVer = CurrentSchemaVersion.create();
-    sVer.versionNbr = ReviewDbSchemaVersion.getBinaryVersion();
-    db.schemaVersion().insert(Collections.singleton(sVer));
-
+  @Override
+  public void create() throws OrmException, IOException, ConfigInvalidException {
     GroupReference admins = createGroupReference("Administrators");
     GroupReference batchUsers = createGroupReference("Non-Interactive Users");
 
@@ -157,8 +101,6 @@
     Sequences seqs =
         new Sequences(
             config,
-            () -> db,
-            migration,
             repoManager,
             GitReferenceUpdated.DISABLED,
             allProjectsName,
@@ -168,8 +110,6 @@
       createAdminsGroup(seqs, allUsersRepo, admins);
       createBatchUsersGroup(seqs, allUsersRepo, batchUsers, admins.getUUID());
     }
-
-    dataSourceType.getIndexScript().run(db);
   }
 
   private void createAdminsGroup(
diff --git a/java/com/google/gerrit/server/schema/Schema_100.java b/java/com/google/gerrit/server/schema/Schema_100.java
deleted file mode 100644
index b5105a9..0000000
--- a/java/com/google/gerrit/server/schema/Schema_100.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_100 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_100(Provider<Schema_99> prior) {
-    super(prior);
-  }
-
-  // No database migration; merges are rechecked on reindex.
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_101.java b/java/com/google/gerrit/server/schema/Schema_101.java
deleted file mode 100644
index 753d992..0000000
--- a/java/com/google/gerrit/server/schema/Schema_101.java
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.common.base.Joiner;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcExecutor;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.ColumnModel;
-import com.google.gwtorm.schema.RelationModel;
-import com.google.gwtorm.schema.java.JavaSchemaModel;
-import com.google.gwtorm.schema.sql.DialectPostgreSQL;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TreeMap;
-
-public class Schema_101 extends ReviewDbSchemaVersion {
-
-  private static class PrimaryKey {
-    String oldNameInDb;
-    List<String> cols;
-  }
-
-  private Connection conn;
-  private SqlDialect dialect;
-
-  @Inject
-  Schema_101(Provider<Schema_100> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    conn = ((JdbcSchema) db).getConnection();
-    dialect = ((JdbcSchema) db).getDialect();
-    Map<String, PrimaryKey> corrections = findPKUpdates();
-    if (corrections.isEmpty()) {
-      return;
-    }
-
-    ui.message("Wrong Primary Key Column Order Detected");
-    ui.message("The following tables are affected:");
-    ui.message(Joiner.on(", ").join(corrections.keySet()));
-    ui.message("fixing primary keys...");
-    try (JdbcExecutor executor = new JdbcExecutor(conn)) {
-      for (Map.Entry<String, PrimaryKey> c : corrections.entrySet()) {
-        ui.message(String.format("  table: %s ... ", c.getKey()));
-        recreatePK(executor, c.getKey(), c.getValue(), ui);
-        ui.message("done");
-      }
-      ui.message("done");
-    }
-  }
-
-  private Map<String, PrimaryKey> findPKUpdates() throws OrmException, SQLException {
-    Map<String, PrimaryKey> corrections = new TreeMap<>();
-    DatabaseMetaData meta = conn.getMetaData();
-    JavaSchemaModel jsm = new JavaSchemaModel(ReviewDb.class);
-    for (RelationModel rm : jsm.getRelations()) {
-      String tableName = rm.getRelationName();
-      List<String> expectedPKCols = relationPK(rm);
-      PrimaryKey actualPK = dbTablePK(meta, tableName);
-      if (!expectedPKCols.equals(actualPK.cols)) {
-        actualPK.cols = expectedPKCols;
-        corrections.put(tableName, actualPK);
-      }
-    }
-    return corrections;
-  }
-
-  private List<String> relationPK(RelationModel rm) {
-    Collection<ColumnModel> cols = rm.getPrimaryKeyColumns();
-    List<String> pk = new ArrayList<>(cols.size());
-    for (ColumnModel cm : cols) {
-      pk.add(cm.getColumnName().toLowerCase(Locale.US));
-    }
-    return pk;
-  }
-
-  private PrimaryKey dbTablePK(DatabaseMetaData meta, String tableName) throws SQLException {
-    if (meta.storesUpperCaseIdentifiers()) {
-      tableName = tableName.toUpperCase();
-    } else if (meta.storesLowerCaseIdentifiers()) {
-      tableName = tableName.toLowerCase();
-    }
-
-    try (ResultSet cols = meta.getPrimaryKeys(null, null, tableName)) {
-      PrimaryKey pk = new PrimaryKey();
-      Map<Short, String> seqToName = new TreeMap<>();
-      while (cols.next()) {
-        seqToName.put(cols.getShort("KEY_SEQ"), cols.getString("COLUMN_NAME"));
-        if (pk.oldNameInDb == null) {
-          pk.oldNameInDb = cols.getString("PK_NAME");
-        }
-      }
-
-      pk.cols = new ArrayList<>(seqToName.size());
-      for (String name : seqToName.values()) {
-        pk.cols.add(name.toLowerCase(Locale.US));
-      }
-      return pk;
-    }
-  }
-
-  private void recreatePK(StatementExecutor executor, String tableName, PrimaryKey pk, UpdateUI ui)
-      throws OrmException {
-    if (pk.oldNameInDb == null) {
-      ui.message(String.format("warning: primary key for table %s didn't exist ... ", tableName));
-    } else {
-      if (dialect instanceof DialectPostgreSQL) {
-        // postgresql doesn't support the ALTER TABLE foo DROP PRIMARY KEY form
-        executor.execute("ALTER TABLE " + tableName + " DROP CONSTRAINT " + pk.oldNameInDb);
-      } else {
-        executor.execute("ALTER TABLE " + tableName + " DROP PRIMARY KEY");
-      }
-    }
-    executor.execute(
-        "ALTER TABLE " + tableName + " ADD PRIMARY KEY(" + Joiner.on(",").join(pk.cols) + ")");
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_102.java b/java/com/google/gerrit/server/schema/Schema_102.java
deleted file mode 100644
index 2bd52c2..0000000
--- a/java/com/google/gerrit/server/schema/Schema_102.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.DialectPostgreSQL;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-public class Schema_102 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_102(Provider<Schema_101> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    JdbcSchema schema = (JdbcSchema) db;
-    SqlDialect dialect = schema.getDialect();
-    try (StatementExecutor e = newExecutor(db)) {
-      // Drop left over indexes that were missed to be removed in schema 84.
-      // See "Delete SQL index support" commit for more details:
-      // d4ae3a16d5e1464574bd04f429a63eb9c02b3b43
-      Pattern pattern =
-          Pattern.compile("^changes_(allOpen|allClosed|byBranchClosed)$", Pattern.CASE_INSENSITIVE);
-      String table = "changes";
-      Set<String> listIndexes = dialect.listIndexes(schema.getConnection(), table);
-      for (String index : listIndexes) {
-        if (pattern.matcher(index).matches()) {
-          dialect.dropIndex(e, table, index);
-        }
-      }
-
-      dialect.dropIndex(e, table, "changes_byProjectOpen");
-      if (dialect instanceof DialectPostgreSQL) {
-        e.execute(
-            "CREATE INDEX changes_byProjectOpen"
-                + " ON "
-                + table
-                + " (dest_project_name, last_updated_on)"
-                + " WHERE open = 'Y'");
-      } else {
-        e.execute(
-            "CREATE INDEX changes_byProjectOpen"
-                + " ON "
-                + table
-                + " (open, dest_project_name, last_updated_on)");
-      }
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_103.java b/java/com/google/gerrit/server/schema/Schema_103.java
deleted file mode 100644
index 1906b66..0000000
--- a/java/com/google/gerrit/server/schema/Schema_103.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_103 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_103(Provider<Schema_102> prior) {
-    super(prior);
-  }
-
-  // Adds originalSubject column
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_104.java b/java/com/google/gerrit/server/schema/Schema_104.java
deleted file mode 100644
index 9b4d67c..0000000
--- a/java/com/google/gerrit/server/schema/Schema_104.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_104 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_104(Provider<Schema_103> prior) {
-    super(prior);
-  }
-
-  // Remove old change screen
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_105.java b/java/com/google/gerrit/server/schema/Schema_105.java
deleted file mode 100644
index 56e19ef..0000000
--- a/java/com/google/gerrit/server/schema/Schema_105.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-public class Schema_105 extends ReviewDbSchemaVersion {
-  private static final String TABLE = "changes";
-
-  @Inject
-  Schema_105(Provider<Schema_104> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException, OrmException {
-    JdbcSchema schema = (JdbcSchema) db;
-    SqlDialect dialect = schema.getDialect();
-
-    Map<String, OrmException> errors = new HashMap<>();
-    try (StatementExecutor e = newExecutor(db)) {
-      for (String index : listChangesIndexes(schema)) {
-        ui.message("Dropping index " + index + " on table " + TABLE);
-        try {
-          dialect.dropIndex(e, TABLE, index);
-        } catch (OrmException err) {
-          errors.put(index, err);
-        }
-      }
-    }
-
-    for (String index : listChangesIndexes(schema)) {
-      String msg = "Failed to drop index " + index;
-      OrmException err = errors.get(index);
-      if (err != null) {
-        msg += ": " + err.getMessage();
-      }
-      ui.message(msg);
-    }
-  }
-
-  private Set<String> listChangesIndexes(JdbcSchema schema) throws SQLException {
-    // List of all changes indexes ever created or dropped, found with the
-    // following command:
-    //   find g* -name \*.sql | xargs git log -i -p -S' index changes_' | grep -io ' index
-    // changes_\w*' | cut -d' ' -f3 | tr A-Z a-z | sort -u
-    // Used rather than listIndexes as we're not sure whether it might include
-    // primary key indexes.
-    Set<String> allChanges =
-        ImmutableSet.of(
-            "changes_allclosed",
-            "changes_allopen",
-            "changes_bybranchclosed",
-            "changes_byownerclosed",
-            "changes_byowneropen",
-            "changes_byproject",
-            "changes_byprojectopen",
-            "changes_key",
-            "changes_submitted");
-    return Sets.intersection(
-        schema.getDialect().listIndexes(schema.getConnection(), TABLE), allChanges);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_106.java b/java/com/google/gerrit/server/schema/Schema_106.java
deleted file mode 100644
index a3f99fc..0000000
--- a/java/com/google/gerrit/server/schema/Schema_106.java
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.SortedSet;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_106 extends ReviewDbSchemaVersion {
-  // we can use multiple threads per CPU as we can expect that threads will be
-  // waiting for IO
-  private static final int THREADS_PER_CPU = 4;
-  private final GitRepositoryManager repoManager;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_106(
-      Provider<Schema_105> prior,
-      GitRepositoryManager repoManager,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    if (!(repoManager instanceof LocalDiskRepositoryManager)) {
-      return;
-    }
-
-    ui.message("listing all repositories ...");
-    SortedSet<Project.NameKey> repoList = repoManager.list();
-    ui.message("done");
-
-    ui.message(String.format("creating reflog files for %s branches ...", RefNames.REFS_CONFIG));
-
-    ExecutorService executorPool = createExecutor(ui, repoList.size());
-    List<Future<Void>> futures = new ArrayList<>();
-
-    for (Project.NameKey project : repoList) {
-      Callable<Void> callable = new ReflogCreator(project);
-      futures.add(executorPool.submit(callable));
-    }
-
-    executorPool.shutdown();
-    try {
-      for (Future<Void> future : futures) {
-        try {
-          future.get();
-        } catch (ExecutionException e) {
-          ui.message(e.getCause().getMessage());
-        }
-      }
-      ui.message("done");
-    } catch (InterruptedException ex) {
-      String msg =
-          String.format(
-              "Migration step 106 was interrupted. "
-                  + "Reflog created in %d of %d repositories only.",
-              countDone(futures), repoList.size());
-      ui.message(msg);
-    }
-  }
-
-  private static int countDone(List<Future<Void>> futures) {
-    int count = 0;
-    for (Future<Void> future : futures) {
-      if (future.isDone()) {
-        count++;
-      }
-    }
-
-    return count;
-  }
-
-  private ExecutorService createExecutor(UpdateUI ui, int repoCount) {
-    int procs = Runtime.getRuntime().availableProcessors();
-    int threads = Math.min(procs * THREADS_PER_CPU, repoCount);
-    ui.message(String.format("... using %d threads ...", threads));
-    return Executors.newFixedThreadPool(threads);
-  }
-
-  private class ReflogCreator implements Callable<Void> {
-    private final Project.NameKey project;
-
-    ReflogCreator(Project.NameKey project) {
-      this.project = project;
-    }
-
-    @Override
-    public Void call() throws IOException {
-      try (Repository repo = repoManager.openRepository(project)) {
-        File metaConfigLog = new File(repo.getDirectory(), "logs/" + RefNames.REFS_CONFIG);
-        if (metaConfigLog.exists()) {
-          return null;
-        }
-
-        if (!metaConfigLog.getParentFile().mkdirs() || !metaConfigLog.createNewFile()) {
-          throw new IOException();
-        }
-
-        ObjectId metaConfigId = repo.resolve(RefNames.REFS_CONFIG);
-        if (metaConfigId != null) {
-          try (PrintWriter writer = new PrintWriter(metaConfigLog, UTF_8.name())) {
-            writer.print(ObjectId.zeroId().name());
-            writer.print(" ");
-            writer.print(metaConfigId.name());
-            writer.print(" ");
-            writer.print(serverUser.toExternalString());
-            writer.print("\t");
-            writer.print("create reflog");
-            writer.println();
-          }
-        }
-        return null;
-      } catch (IOException e) {
-        throw new IOException(
-            String.format(
-                "ERROR: Failed to create reflog file for the %s branch in repository %s",
-                RefNames.REFS_CONFIG, project.get()));
-      }
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_107.java b/java/com/google/gerrit/server/schema/Schema_107.java
deleted file mode 100644
index bef8c65..0000000
--- a/java/com/google/gerrit/server/schema/Schema_107.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-public class Schema_107 extends ReviewDbSchemaVersion {
-
-  @Inject
-  Schema_107(Provider<Schema_106> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement()) {
-      stmt.executeUpdate("UPDATE accounts set mute_common_path_prefixes = 'Y'");
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_108.java b/java/com/google/gerrit/server/schema/Schema_108.java
deleted file mode 100644
index b2ab042..0000000
--- a/java/com/google/gerrit/server/schema/Schema_108.java
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.SetMultimap;
-import com.google.common.collect.Sets;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.GroupCollector;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevSort;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-public class Schema_108 extends ReviewDbSchemaVersion {
-  private final GitRepositoryManager repoManager;
-
-  @Inject
-  Schema_108(Provider<Schema_107> prior, GitRepositoryManager repoManager) {
-    super(prior);
-    this.repoManager = repoManager;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    ui.message("Listing all changes ...");
-    SetMultimap<Project.NameKey, Change.Id> openByProject = getOpenChangesByProject(db, ui);
-    ui.message("done");
-
-    ui.message("Updating groups for open changes ...");
-    int i = 0;
-    for (Map.Entry<Project.NameKey, Collection<Change.Id>> e : openByProject.asMap().entrySet()) {
-      try (Repository repo = repoManager.openRepository(e.getKey());
-          RevWalk rw = new RevWalk(repo)) {
-        updateProjectGroups(db, repo, rw, (Set<Change.Id>) e.getValue(), ui);
-      } catch (IOException | NoSuchChangeException err) {
-        throw new OrmException(err);
-      }
-      if (++i % 100 == 0) {
-        ui.message("  done " + i + " projects ...");
-      }
-    }
-    ui.message("done");
-  }
-
-  private void updateProjectGroups(
-      ReviewDb db, Repository repo, RevWalk rw, Set<Change.Id> changes, UpdateUI ui)
-      throws OrmException, IOException {
-    // Match sorting in ReceiveCommits.
-    rw.reset();
-    rw.sort(RevSort.TOPO);
-    rw.sort(RevSort.REVERSE, true);
-
-    RefDatabase refdb = repo.getRefDatabase();
-    for (Ref ref : refdb.getRefsByPrefix(Constants.R_HEADS)) {
-      RevCommit c = maybeParseCommit(rw, ref.getObjectId(), ui);
-      if (c != null) {
-        rw.markUninteresting(c);
-      }
-    }
-
-    ListMultimap<ObjectId, Ref> changeRefsBySha =
-        MultimapBuilder.hashKeys().arrayListValues().build();
-    ListMultimap<ObjectId, PatchSet.Id> patchSetsBySha =
-        MultimapBuilder.hashKeys().arrayListValues().build();
-    for (Ref ref : refdb.getRefsByPrefix(RefNames.REFS_CHANGES)) {
-      ObjectId id = ref.getObjectId();
-      if (ref.getObjectId() == null) {
-        continue;
-      }
-      id = id.copy();
-      changeRefsBySha.put(id, ref);
-      PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
-      if (psId != null && changes.contains(psId.getParentKey())) {
-        patchSetsBySha.put(id, psId);
-        RevCommit c = maybeParseCommit(rw, id, ui);
-        if (c != null) {
-          rw.markStart(c);
-        }
-      }
-    }
-
-    GroupCollector collector = GroupCollector.createForSchemaUpgradeOnly(changeRefsBySha, db);
-    RevCommit c;
-    while ((c = rw.next()) != null) {
-      collector.visit(c);
-    }
-
-    updateGroups(db, collector, patchSetsBySha);
-  }
-
-  private static void updateGroups(
-      ReviewDb db, GroupCollector collector, ListMultimap<ObjectId, PatchSet.Id> patchSetsBySha)
-      throws OrmException {
-    Map<PatchSet.Id, PatchSet> patchSets =
-        db.patchSets().toMap(db.patchSets().get(patchSetsBySha.values()));
-    for (Map.Entry<ObjectId, Collection<String>> e : collector.getGroups().asMap().entrySet()) {
-      for (PatchSet.Id psId : patchSetsBySha.get(e.getKey())) {
-        PatchSet ps = patchSets.get(psId);
-        if (ps != null) {
-          ps.setGroups(ImmutableList.copyOf(e.getValue()));
-        }
-      }
-    }
-
-    db.patchSets().update(patchSets.values());
-  }
-
-  private SetMultimap<Project.NameKey, Change.Id> getOpenChangesByProject(ReviewDb db, UpdateUI ui)
-      throws OrmException {
-    SortedSet<Project.NameKey> projects = repoManager.list();
-    SortedSet<Project.NameKey> nonExistentProjects = Sets.newTreeSet();
-    SetMultimap<Project.NameKey, Change.Id> openByProject =
-        MultimapBuilder.hashKeys().hashSetValues().build();
-    for (Change c : db.changes().all()) {
-      Status status = c.getStatus();
-      if (status != null && status.isClosed()) {
-        continue;
-      }
-
-      Project.NameKey projectKey = c.getProject();
-      if (!projects.contains(projectKey)) {
-        nonExistentProjects.add(projectKey);
-      } else {
-        // The old "submitted" state is not supported anymore
-        // (thus status is null) but it was an opened state and needs
-        // to be migrated as such
-        openByProject.put(projectKey, c.getId());
-      }
-    }
-
-    if (!nonExistentProjects.isEmpty()) {
-      ui.message("Detected open changes referring to the following non-existent projects:");
-      ui.message(Joiner.on(", ").join(nonExistentProjects));
-      ui.message(
-          "It is highly recommended to remove\n"
-              + "the obsolete open changes, comments and patch-sets from your DB.\n");
-    }
-    return openByProject;
-  }
-
-  private static RevCommit maybeParseCommit(RevWalk rw, ObjectId id, UpdateUI ui)
-      throws IOException {
-    if (id != null) {
-      try {
-        RevObject obj = rw.parseAny(id);
-        return (obj instanceof RevCommit) ? (RevCommit) obj : null;
-      } catch (MissingObjectException moe) {
-        ui.message("Missing object: " + id.getName() + "\n");
-      }
-    }
-    return null;
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_109.java b/java/com/google/gerrit/server/schema/Schema_109.java
deleted file mode 100644
index f582741..0000000
--- a/java/com/google/gerrit/server/schema/Schema_109.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_109 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_109(Provider<Schema_108> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    try (StatementExecutor e = newExecutor(db)) {
-      e.execute("UPDATE changes SET status = 'n', created_on = created_on WHERE status = 's'");
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_110.java b/java/com/google/gerrit/server/schema/Schema_110.java
deleted file mode 100644
index 6da10f6..0000000
--- a/java/com/google/gerrit/server/schema/Schema_110.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_110 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_110(Provider<Schema_109> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_111.java b/java/com/google/gerrit/server/schema/Schema_111.java
deleted file mode 100644
index 7ba48c2..0000000
--- a/java/com/google/gerrit/server/schema/Schema_111.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_111 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_111(Provider<Schema_110> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_112.java b/java/com/google/gerrit/server/schema/Schema_112.java
deleted file mode 100644
index 5a764d0..0000000
--- a/java/com/google/gerrit/server/schema/Schema_112.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_112 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_112(Provider<Schema_111> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_113.java b/java/com/google/gerrit/server/schema/Schema_113.java
deleted file mode 100644
index 2876636..0000000
--- a/java/com/google/gerrit/server/schema/Schema_113.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_113 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_113(Provider<Schema_112> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_114.java b/java/com/google/gerrit/server/schema/Schema_114.java
deleted file mode 100644
index 7bee00c..0000000
--- a/java/com/google/gerrit/server/schema/Schema_114.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_114 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_114(Provider<Schema_113> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_115.java b/java/com/google/gerrit/server/schema/Schema_115.java
deleted file mode 100644
index 28cfe22..0000000
--- a/java/com/google/gerrit/server/schema/Schema_115.java
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.config.ConfigUtil.storeSection;
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.base.Strings;
-import com.google.gerrit.extensions.client.DiffPreferencesInfo;
-import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
-import com.google.gerrit.extensions.client.Theme;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.UserConfigSections;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.patch.PatchListKey;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-public class Schema_115 extends ReviewDbSchemaVersion {
-  private final GitRepositoryManager mgr;
-  private final AllUsersName allUsersName;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_115(
-      Provider<Schema_114> prior,
-      GitRepositoryManager mgr,
-      AllUsersName allUsersName,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.mgr = mgr;
-    this.allUsersName = allUsersName;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    Map<Account.Id, DiffPreferencesInfo> imports = new HashMap<>();
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
-        ResultSet rs = stmt.executeQuery("SELECT * FROM account_diff_preferences")) {
-      Set<String> availableColumns = getColumns(rs);
-      while (rs.next()) {
-        Account.Id accountId = new Account.Id(rs.getInt("id"));
-        DiffPreferencesInfo prefs = new DiffPreferencesInfo();
-        if (availableColumns.contains("context")) {
-          prefs.context = (int) rs.getShort("context");
-        }
-        if (availableColumns.contains("expand_all_comments")) {
-          prefs.expandAllComments = toBoolean(rs.getString("expand_all_comments"));
-        }
-        if (availableColumns.contains("hide_line_numbers")) {
-          prefs.hideLineNumbers = toBoolean(rs.getString("hide_line_numbers"));
-        }
-        if (availableColumns.contains("hide_top_menu")) {
-          prefs.hideTopMenu = toBoolean(rs.getString("hide_top_menu"));
-        }
-        if (availableColumns.contains("ignore_whitespace")) {
-          // Enum with char as value
-          prefs.ignoreWhitespace = toWhitespace(rs.getString("ignore_whitespace"));
-        }
-        if (availableColumns.contains("intraline_difference")) {
-          prefs.intralineDifference = toBoolean(rs.getString("intraline_difference"));
-        }
-        if (availableColumns.contains("line_length")) {
-          prefs.lineLength = rs.getInt("line_length");
-        }
-        if (availableColumns.contains("manual_review")) {
-          prefs.manualReview = toBoolean(rs.getString("manual_review"));
-        }
-        if (availableColumns.contains("render_entire_file")) {
-          prefs.renderEntireFile = toBoolean(rs.getString("render_entire_file"));
-        }
-        if (availableColumns.contains("retain_header")) {
-          prefs.retainHeader = toBoolean(rs.getString("retain_header"));
-        }
-        if (availableColumns.contains("show_line_endings")) {
-          prefs.showLineEndings = toBoolean(rs.getString("show_line_endings"));
-        }
-        if (availableColumns.contains("show_tabs")) {
-          prefs.showTabs = toBoolean(rs.getString("show_tabs"));
-        }
-        if (availableColumns.contains("show_whitespace_errors")) {
-          prefs.showWhitespaceErrors = toBoolean(rs.getString("show_whitespace_errors"));
-        }
-        if (availableColumns.contains("skip_deleted")) {
-          prefs.skipDeleted = toBoolean(rs.getString("skip_deleted"));
-        }
-        if (availableColumns.contains("skip_uncommented")) {
-          prefs.skipUncommented = toBoolean(rs.getString("skip_uncommented"));
-        }
-        if (availableColumns.contains("syntax_highlighting")) {
-          prefs.syntaxHighlighting = toBoolean(rs.getString("syntax_highlighting"));
-        }
-        if (availableColumns.contains("tab_size")) {
-          prefs.tabSize = rs.getInt("tab_size");
-        }
-        if (availableColumns.contains("theme")) {
-          // Enum with name as values; can be null
-          prefs.theme = toTheme(rs.getString("theme"));
-        }
-        if (availableColumns.contains("hide_empty_pane")) {
-          prefs.hideEmptyPane = toBoolean(rs.getString("hide_empty_pane"));
-        }
-        if (availableColumns.contains("auto_hide_diff_table_header")) {
-          prefs.autoHideDiffTableHeader = toBoolean(rs.getString("auto_hide_diff_table_header"));
-        }
-        imports.put(accountId, prefs);
-      }
-    }
-
-    if (imports.isEmpty()) {
-      return;
-    }
-
-    try (Repository git = mgr.openRepository(allUsersName);
-        RevWalk rw = new RevWalk(git)) {
-      BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
-      for (Map.Entry<Account.Id, DiffPreferencesInfo> e : imports.entrySet()) {
-        try (MetaDataUpdate md =
-            new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git, bru)) {
-          md.getCommitBuilder().setAuthor(serverUser);
-          md.getCommitBuilder().setCommitter(serverUser);
-          VersionedAccountPreferences p = VersionedAccountPreferences.forUser(e.getKey());
-          p.load(md);
-          storeSection(
-              p.getConfig(),
-              UserConfigSections.DIFF,
-              null,
-              e.getValue(),
-              DiffPreferencesInfo.defaults());
-          p.commit(md);
-        }
-      }
-
-      bru.execute(rw, NullProgressMonitor.INSTANCE);
-    } catch (ConfigInvalidException | IOException ex) {
-      throw new OrmException(ex);
-    }
-  }
-
-  private Set<String> getColumns(ResultSet rs) throws SQLException {
-    ResultSetMetaData metaData = rs.getMetaData();
-    int columnCount = metaData.getColumnCount();
-    Set<String> columns = new HashSet<>(columnCount);
-    for (int i = 1; i <= columnCount; i++) {
-      columns.add(metaData.getColumnLabel(i).toLowerCase());
-    }
-    return columns;
-  }
-
-  private static Theme toTheme(String v) {
-    if (v == null) {
-      return Theme.DEFAULT;
-    }
-    return Theme.valueOf(v);
-  }
-
-  private static Whitespace toWhitespace(String v) {
-    requireNonNull(v);
-    if (v.isEmpty()) {
-      return Whitespace.IGNORE_NONE;
-    }
-    Whitespace r = PatchListKey.WHITESPACE_TYPES.inverse().get(v.charAt(0));
-    if (r == null) {
-      throw new IllegalArgumentException("Cannot find Whitespace type for: " + v);
-    }
-    return r;
-  }
-
-  private static boolean toBoolean(String v) {
-    checkState(!Strings.isNullOrEmpty(v));
-    return v.equals("Y");
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_116.java b/java/com/google/gerrit/server/schema/Schema_116.java
deleted file mode 100644
index 5174d92..0000000
--- a/java/com/google/gerrit/server/schema/Schema_116.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_116 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_116(Provider<Schema_115> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_117.java b/java/com/google/gerrit/server/schema/Schema_117.java
deleted file mode 100644
index d848bec..0000000
--- a/java/com/google/gerrit/server/schema/Schema_117.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-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.SQLException;
-import java.sql.Statement;
-import java.util.Set;
-
-public class Schema_117 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_117(Provider<Schema_116> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void preUpdateSchema(ReviewDb db) throws OrmException, SQLException {
-    JdbcSchema schema = (JdbcSchema) db;
-    Connection connection = schema.getConnection();
-    String tableName = "patch_sets";
-    String oldColumnName = "push_certficate";
-    String newColumnName = "push_certificate";
-    Set<String> columns = schema.getDialect().listColumns(connection, tableName);
-    if (columns.contains(oldColumnName)) {
-      renameColumn(db, tableName, oldColumnName, newColumnName);
-    }
-    try (Statement stmt = schema.getConnection().createStatement()) {
-      stmt.execute("ALTER TABLE " + tableName + " MODIFY " + newColumnName + " clob");
-    } catch (SQLException e) {
-      // Ignore.  Type may have already been modified manually.
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_118.java b/java/com/google/gerrit/server/schema/Schema_118.java
deleted file mode 100644
index e55afda..0000000
--- a/java/com/google/gerrit/server/schema/Schema_118.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_118 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_118(Provider<Schema_117> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_119.java b/java/com/google/gerrit/server/schema/Schema_119.java
deleted file mode 100644
index 9423d81..0000000
--- a/java/com/google/gerrit/server/schema/Schema_119.java
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright (C) 2015 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.schema;
-
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.reviewdb.client.CoreDownloadSchemes.ANON_GIT;
-import static com.google.gerrit.reviewdb.client.CoreDownloadSchemes.ANON_HTTP;
-import static com.google.gerrit.reviewdb.client.CoreDownloadSchemes.HTTP;
-import static com.google.gerrit.reviewdb.client.CoreDownloadSchemes.REPO_DOWNLOAD;
-import static com.google.gerrit.reviewdb.client.CoreDownloadSchemes.SSH;
-import static com.google.gerrit.server.config.ConfigUtil.storeSection;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.ReviewCategoryStrategy;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.UserConfigSections;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-public class Schema_119 extends ReviewDbSchemaVersion {
-  private static final ImmutableMap<String, String> LEGACY_DISPLAYNAME_MAP =
-      ImmutableMap.<String, String>of(
-          "ANON_GIT", ANON_GIT,
-          "ANON_HTTP", ANON_HTTP,
-          "HTTP", HTTP,
-          "SSH", SSH,
-          "REPO_DOWNLOAD", REPO_DOWNLOAD);
-
-  private final GitRepositoryManager mgr;
-  private final AllUsersName allUsersName;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_119(
-      Provider<Schema_118> prior,
-      GitRepositoryManager mgr,
-      AllUsersName allUsersName,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.mgr = mgr;
-    this.allUsersName = allUsersName;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    JdbcSchema schema = (JdbcSchema) db;
-    Connection connection = schema.getConnection();
-    String tableName = "accounts";
-    String emailStrategy = "email_strategy";
-    Set<String> columns = schema.getDialect().listColumns(connection, tableName);
-    Map<Account.Id, GeneralPreferencesInfo> imports = new HashMap<>();
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
-        ResultSet rs =
-            stmt.executeQuery(
-                "select "
-                    + "account_id, "
-                    + "maximum_page_size, "
-                    + "show_site_header, "
-                    + "use_flash_clipboard, "
-                    + "download_url, "
-                    + "download_command, "
-                    + (columns.contains(emailStrategy)
-                        ? emailStrategy + ", "
-                        : "copy_self_on_email, ")
-                    + "date_format, "
-                    + "time_format, "
-                    + "relative_date_in_change_table, "
-                    + "diff_view, "
-                    + "size_bar_in_change_table, "
-                    + "legacycid_in_change_table, "
-                    + "review_category_strategy, "
-                    + "mute_common_path_prefixes "
-                    + "from "
-                    + tableName)) {
-      while (rs.next()) {
-        GeneralPreferencesInfo p = new GeneralPreferencesInfo();
-        Account.Id accountId = new Account.Id(rs.getInt(1));
-        p.changesPerPage = (int) rs.getShort(2);
-        p.showSiteHeader = toBoolean(rs.getString(3));
-        p.useFlashClipboard = toBoolean(rs.getString(4));
-        p.downloadScheme = convertToModernNames(rs.getString(5));
-        p.downloadCommand = toDownloadCommand(rs.getString(6));
-        p.emailStrategy = toEmailStrategy(rs.getString(7), columns.contains(emailStrategy));
-        p.dateFormat = toDateFormat(rs.getString(8));
-        p.timeFormat = toTimeFormat(rs.getString(9));
-        p.relativeDateInChangeTable = toBoolean(rs.getString(10));
-        p.diffView = toDiffView(rs.getString(11));
-        p.sizeBarInChangeTable = toBoolean(rs.getString(12));
-        p.legacycidInChangeTable = toBoolean(rs.getString(13));
-        p.reviewCategoryStrategy = toReviewCategoryStrategy(rs.getString(14));
-        p.muteCommonPathPrefixes = toBoolean(rs.getString(15));
-        p.defaultBaseForMerges = GeneralPreferencesInfo.defaults().defaultBaseForMerges;
-        imports.put(accountId, p);
-      }
-    }
-
-    if (imports.isEmpty()) {
-      return;
-    }
-
-    try (Repository git = mgr.openRepository(allUsersName);
-        RevWalk rw = new RevWalk(git)) {
-      BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
-      for (Map.Entry<Account.Id, GeneralPreferencesInfo> e : imports.entrySet()) {
-        try (MetaDataUpdate md =
-            new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git, bru)) {
-          md.getCommitBuilder().setAuthor(serverUser);
-          md.getCommitBuilder().setCommitter(serverUser);
-          VersionedAccountPreferences p = VersionedAccountPreferences.forUser(e.getKey());
-          p.load(md);
-          storeSection(
-              p.getConfig(),
-              UserConfigSections.GENERAL,
-              null,
-              e.getValue(),
-              GeneralPreferencesInfo.defaults());
-          p.commit(md);
-        }
-      }
-
-      bru.execute(rw, NullProgressMonitor.INSTANCE);
-    } catch (ConfigInvalidException | IOException ex) {
-      throw new OrmException(ex);
-    }
-  }
-
-  private String convertToModernNames(String s) {
-    return !Strings.isNullOrEmpty(s) && LEGACY_DISPLAYNAME_MAP.containsKey(s)
-        ? LEGACY_DISPLAYNAME_MAP.get(s)
-        : s;
-  }
-
-  private static DownloadCommand toDownloadCommand(String v) {
-    if (v == null) {
-      return DownloadCommand.CHECKOUT;
-    }
-    return DownloadCommand.valueOf(v);
-  }
-
-  private static DateFormat toDateFormat(String v) {
-    if (v == null) {
-      return DateFormat.STD;
-    }
-    return DateFormat.valueOf(v);
-  }
-
-  private static TimeFormat toTimeFormat(String v) {
-    if (v == null) {
-      return TimeFormat.HHMM_12;
-    }
-    return TimeFormat.valueOf(v);
-  }
-
-  private static DiffView toDiffView(String v) {
-    if (v == null) {
-      return DiffView.SIDE_BY_SIDE;
-    }
-    return DiffView.valueOf(v);
-  }
-
-  private static EmailStrategy toEmailStrategy(String v, boolean emailStrategyColumnExists)
-      throws OrmException {
-    if (v == null) {
-      return EmailStrategy.ENABLED;
-    }
-    if (emailStrategyColumnExists) {
-      return EmailStrategy.valueOf(v);
-    }
-    if (v.equals("N")) {
-      // EMAIL_STRATEGY='ENABLED' WHERE (COPY_SELF_ON_EMAIL='N')
-      return EmailStrategy.ENABLED;
-    } else if (v.equals("Y")) {
-      // EMAIL_STRATEGY='CC_ON_OWN_COMMENTS' WHERE (COPY_SELF_ON_EMAIL='Y')
-      return EmailStrategy.CC_ON_OWN_COMMENTS;
-    } else {
-      throw new OrmException("invalid value in accounts.copy_self_on_email: " + v);
-    }
-  }
-
-  private static ReviewCategoryStrategy toReviewCategoryStrategy(String v) {
-    if (v == null) {
-      return ReviewCategoryStrategy.NONE;
-    }
-    return ReviewCategoryStrategy.valueOf(v);
-  }
-
-  private static boolean toBoolean(String v) {
-    checkState(!Strings.isNullOrEmpty(v));
-    return v.equals("Y");
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_120.java b/java/com/google/gerrit/server/schema/Schema_120.java
deleted file mode 100644
index 9a1c15b..0000000
--- a/java/com/google/gerrit/server/schema/Schema_120.java
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.gerrit.common.data.SubscribeSection;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.RefSpec;
-
-public class Schema_120 extends ReviewDbSchemaVersion {
-
-  private final GitRepositoryManager mgr;
-  private final ProjectConfig.Factory projectConfigFactory;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_120(
-      Provider<Schema_119> prior,
-      GitRepositoryManager mgr,
-      ProjectConfig.Factory projectConfigFactory,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.mgr = mgr;
-    this.projectConfigFactory = projectConfigFactory;
-    this.serverUser = serverUser;
-  }
-
-  private void allowSubmoduleSubscription(Branch.NameKey subbranch, Branch.NameKey superBranch)
-      throws OrmException {
-    try (Repository git = mgr.openRepository(subbranch.getParentKey());
-        RevWalk rw = new RevWalk(git)) {
-      BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
-      try (MetaDataUpdate md =
-          new MetaDataUpdate(GitReferenceUpdated.DISABLED, subbranch.getParentKey(), git, bru)) {
-        md.getCommitBuilder().setAuthor(serverUser);
-        md.getCommitBuilder().setCommitter(serverUser);
-        md.setMessage("Added superproject subscription during upgrade");
-        ProjectConfig pc = projectConfigFactory.read(md);
-
-        SubscribeSection s = null;
-        for (SubscribeSection s1 : pc.getSubscribeSections(subbranch)) {
-          if (s1.getProject().equals(superBranch.getParentKey())) {
-            s = s1;
-          }
-        }
-        if (s == null) {
-          s = new SubscribeSection(superBranch.getParentKey());
-          pc.addSubscribeSection(s);
-        }
-        RefSpec newRefSpec = new RefSpec(subbranch.get() + ":" + superBranch.get());
-
-        if (!s.getMatchingRefSpecs().contains(newRefSpec)) {
-          // For the migration we use only exact RefSpecs, we're not trying to
-          // generalize it.
-          s.addMatchingRefSpec(newRefSpec);
-        }
-
-        pc.commit(md);
-      }
-      bru.execute(rw, NullProgressMonitor.INSTANCE);
-    } catch (ConfigInvalidException | IOException e) {
-      throw new OrmException(e);
-    }
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    ui.message("Generating Superproject subscriptions table to submodule ACLs");
-
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
-        ResultSet rs =
-            stmt.executeQuery(
-                "SELECT "
-                    + "super_project_project_name, "
-                    + "super_project_branch_name, "
-                    + "submodule_project_name, "
-                    + "submodule_branch_name "
-                    + "FROM submodule_subscriptions")) {
-      while (rs.next()) {
-        Project.NameKey superproject = new Project.NameKey(rs.getString(1));
-        Branch.NameKey superbranch = new Branch.NameKey(superproject, rs.getString(2));
-
-        Project.NameKey submodule = new Project.NameKey(rs.getString(3));
-        Branch.NameKey subbranch = new Branch.NameKey(submodule, rs.getString(4));
-
-        allowSubmoduleSubscription(subbranch, superbranch);
-      }
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_121.java b/java/com/google/gerrit/server/schema/Schema_121.java
deleted file mode 100644
index bb994ef..0000000
--- a/java/com/google/gerrit/server/schema/Schema_121.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_121 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_121(Provider<Schema_120> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_122.java b/java/com/google/gerrit/server/schema/Schema_122.java
deleted file mode 100644
index cce1419..0000000
--- a/java/com/google/gerrit/server/schema/Schema_122.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_122 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_122(Provider<Schema_121> prior) {
-    super(prior);
-  }
-
-  // Adds tag column
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_123.java b/java/com/google/gerrit/server/schema/Schema_123.java
deleted file mode 100644
index cd84188..0000000
--- a/java/com/google/gerrit/server/schema/Schema_123.java
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.StarredChangesUtil;
-import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.Map;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.TextProgressMonitor;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-
-public class Schema_123 extends ReviewDbSchemaVersion {
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-
-  @Inject
-  Schema_123(
-      Provider<Schema_122> prior, GitRepositoryManager repoManager, AllUsersName allUsersName) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    ListMultimap<Account.Id, Change.Id> imports =
-        MultimapBuilder.hashKeys().arrayListValues().build();
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
-        ResultSet rs = stmt.executeQuery("SELECT account_id, change_id FROM starred_changes")) {
-      while (rs.next()) {
-        Account.Id accountId = new Account.Id(rs.getInt(1));
-        Change.Id changeId = new Change.Id(rs.getInt(2));
-        imports.put(accountId, changeId);
-      }
-    }
-
-    if (imports.isEmpty()) {
-      return;
-    }
-
-    try (Repository git = repoManager.openRepository(allUsersName);
-        RevWalk rw = new RevWalk(git)) {
-      BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
-      ObjectId id = StarredChangesUtil.writeLabels(git, StarredChangesUtil.DEFAULT_LABELS);
-      for (Map.Entry<Account.Id, Change.Id> e : imports.entries()) {
-        bru.addCommand(
-            new ReceiveCommand(
-                ObjectId.zeroId(), id, RefNames.refsStarredChanges(e.getValue(), e.getKey())));
-      }
-      bru.execute(rw, new TextProgressMonitor());
-    } catch (IOException | IllegalLabelException ex) {
-      throw new OrmException(ex);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_124.java b/java/com/google/gerrit/server/schema/Schema_124.java
deleted file mode 100644
index 7e2fa42..0000000
--- a/java/com/google/gerrit/server/schema/Schema_124.java
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import static java.util.Comparator.comparing;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Ordering;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.AccountSshKey;
-import com.google.gerrit.server.account.VersionedAuthorizedKeys;
-import com.google.gerrit.server.account.VersionedAuthorizedKeys.SimpleSshKeyCreator;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-public class Schema_124 extends ReviewDbSchemaVersion {
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_124(
-      Provider<Schema_123> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    ListMultimap<Account.Id, AccountSshKey> imports =
-        MultimapBuilder.hashKeys().arrayListValues().build();
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
-        ResultSet rs =
-            stmt.executeQuery(
-                "SELECT "
-                    + "account_id, "
-                    + "seq, "
-                    + "ssh_public_key, "
-                    + "valid "
-                    + "FROM account_ssh_keys")) {
-      while (rs.next()) {
-        Account.Id accountId = new Account.Id(rs.getInt(1));
-        int seq = rs.getInt(2);
-        String sshPublicKey = rs.getString(3);
-        boolean valid = toBoolean(rs.getString(4));
-        AccountSshKey key = AccountSshKey.create(accountId, seq, sshPublicKey, valid);
-        imports.put(accountId, key);
-      }
-    }
-
-    if (imports.isEmpty()) {
-      return;
-    }
-
-    try (Repository git = repoManager.openRepository(allUsersName);
-        RevWalk rw = new RevWalk(git)) {
-      BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
-
-      for (Map.Entry<Account.Id, Collection<AccountSshKey>> e : imports.asMap().entrySet()) {
-        try (MetaDataUpdate md =
-            new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git, bru)) {
-          md.getCommitBuilder().setAuthor(serverUser);
-          md.getCommitBuilder().setCommitter(serverUser);
-
-          VersionedAuthorizedKeys authorizedKeys =
-              new VersionedAuthorizedKeys(new SimpleSshKeyCreator(), e.getKey());
-          authorizedKeys.load(md);
-          authorizedKeys.setKeys(fixInvalidSequenceNumbers(e.getValue()));
-          authorizedKeys.commit(md);
-        }
-      }
-
-      bru.execute(rw, NullProgressMonitor.INSTANCE);
-    } catch (ConfigInvalidException | IOException ex) {
-      throw new OrmException(ex);
-    }
-  }
-
-  private Collection<AccountSshKey> fixInvalidSequenceNumbers(Collection<AccountSshKey> keys) {
-    Ordering<AccountSshKey> o = Ordering.from(comparing(AccountSshKey::seq));
-    List<AccountSshKey> fixedKeys = new ArrayList<>(keys);
-    AccountSshKey minKey = o.min(keys);
-    while (minKey.seq() <= 0) {
-      AccountSshKey fixedKey =
-          AccountSshKey.create(
-              minKey.accountId(), Math.max(o.max(keys).seq() + 1, 1), minKey.sshPublicKey());
-      Collections.replaceAll(fixedKeys, minKey, fixedKey);
-      minKey = o.min(fixedKeys);
-    }
-    return fixedKeys;
-  }
-
-  private static boolean toBoolean(String v) {
-    return !Strings.isNullOrEmpty(v) && v.equalsIgnoreCase("Y");
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_125.java b/java/com/google/gerrit/server/schema/Schema_125.java
deleted file mode 100644
index cc0d9e7..0000000
--- a/java/com/google/gerrit/server/schema/Schema_125.java
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.schema.AclUtil.grant;
-
-import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.data.LabelType;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.RefPattern;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_125 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG =
-      "Assign default permissions on user branches\n"
-          + "\n"
-          + "By default each user should be able to read and update the own user\n"
-          + "branch. Also the user should be able to approve and submit changes for\n"
-          + "the own user branch. Assign default permissions for this and remove the\n"
-          + "old exclusive read protection from the user branches.\n";
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final AllProjectsName allProjectsName;
-  private final SystemGroupBackend systemGroupBackend;
-  private final ProjectConfig.Factory projectConfigFactory;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_125(
-      Provider<Schema_124> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      AllProjectsName allProjectsName,
-      SystemGroupBackend systemGroupBackend,
-      ProjectConfig.Factory projectConfigFactory,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.allProjectsName = allProjectsName;
-    this.systemGroupBackend = systemGroupBackend;
-    this.projectConfigFactory = projectConfigFactory;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    try (Repository git = repoManager.openRepository(allUsersName);
-        MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git)) {
-      ProjectConfig config = projectConfigFactory.read(md);
-
-      config
-          .getAccessSection(RefNames.REFS_USERS + "*", true)
-          .remove(new Permission(Permission.READ));
-      GroupReference registered = systemGroupBackend.getGroup(REGISTERED_USERS);
-      AccessSection users =
-          config.getAccessSection(
-              RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}", true);
-      grant(config, users, Permission.READ, true, registered);
-      grant(config, users, Permission.PUSH, true, registered);
-      grant(config, users, Permission.SUBMIT, true, registered);
-
-      for (LabelType lt : getLabelTypes(config)) {
-        if ("Code-Review".equals(lt.getName()) || "Verified".equals(lt.getName())) {
-          grant(config, users, lt, lt.getMin().getValue(), lt.getMax().getValue(), registered);
-        }
-      }
-
-      md.getCommitBuilder().setAuthor(serverUser);
-      md.getCommitBuilder().setCommitter(serverUser);
-      md.setMessage(COMMIT_MSG);
-      config.commit(md);
-    } catch (ConfigInvalidException | IOException ex) {
-      throw new OrmException(ex);
-    }
-  }
-
-  private Collection<LabelType> getLabelTypes(ProjectConfig config)
-      throws IOException, ConfigInvalidException {
-    Map<String, LabelType> labelTypes = new HashMap<>(config.getLabelSections());
-    Project.NameKey parent = config.getProject().getParent(allProjectsName);
-    while (parent != null) {
-      try (Repository git = repoManager.openRepository(parent);
-          MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, parent, git)) {
-        ProjectConfig parentConfig = projectConfigFactory.read(md);
-        for (LabelType lt : parentConfig.getLabelSections().values()) {
-          if (!labelTypes.containsKey(lt.getName())) {
-            labelTypes.put(lt.getName(), lt);
-          }
-        }
-        parent = parentConfig.getProject().getParent(allProjectsName);
-      }
-    }
-    return labelTypes.values();
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_126.java b/java/com/google/gerrit/server/schema/Schema_126.java
deleted file mode 100644
index f3004ba..0000000
--- a/java/com/google/gerrit/server/schema/Schema_126.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.schema.AclUtil.grant;
-
-import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.RefPattern;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_126 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG = "Fix default permissions on user branches";
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final SystemGroupBackend systemGroupBackend;
-  private final ProjectConfig.Factory projectConfigFactory;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_126(
-      Provider<Schema_125> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      SystemGroupBackend systemGroupBackend,
-      ProjectConfig.Factory projectConfigFactory,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.systemGroupBackend = systemGroupBackend;
-    this.projectConfigFactory = projectConfigFactory;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    try (Repository git = repoManager.openRepository(allUsersName);
-        MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git)) {
-      ProjectConfig config = projectConfigFactory.read(md);
-
-      String refsUsersShardedId = RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}";
-      config.remove(config.getAccessSection(refsUsersShardedId));
-
-      GroupReference registered = systemGroupBackend.getGroup(REGISTERED_USERS);
-      AccessSection users = config.getAccessSection(refsUsersShardedId, true);
-      grant(config, users, Permission.READ, false, true, registered);
-      grant(config, users, Permission.PUSH, false, true, registered);
-      grant(config, users, Permission.SUBMIT, false, true, registered);
-
-      md.getCommitBuilder().setAuthor(serverUser);
-      md.getCommitBuilder().setCommitter(serverUser);
-      md.setMessage(COMMIT_MSG);
-      config.commit(md);
-    } catch (ConfigInvalidException | IOException ex) {
-      throw new OrmException(ex);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_127.java b/java/com/google/gerrit/server/schema/Schema_127.java
deleted file mode 100644
index 8e4175c..0000000
--- a/java/com/google/gerrit/server/schema/Schema_127.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.config.ThreadSettingsConfig;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import org.eclipse.jgit.lib.Config;
-
-public class Schema_127 extends ReviewDbSchemaVersion {
-  private static final int MAX_BATCH_SIZE = 1000;
-
-  private final SitePaths sitePaths;
-  private final Config cfg;
-  private final ThreadSettingsConfig threadSettingsConfig;
-
-  @Inject
-  Schema_127(
-      Provider<Schema_126> prior,
-      SitePaths sitePaths,
-      @GerritServerConfig Config cfg,
-      ThreadSettingsConfig threadSettingsConfig) {
-    super(prior);
-    this.sitePaths = sitePaths;
-    this.cfg = cfg;
-    this.threadSettingsConfig = threadSettingsConfig;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    JdbcAccountPatchReviewStore jdbcAccountPatchReviewStore =
-        JdbcAccountPatchReviewStore.createAccountPatchReviewStore(
-            cfg, sitePaths, threadSettingsConfig);
-    jdbcAccountPatchReviewStore.dropTableIfExists();
-    jdbcAccountPatchReviewStore.createTableIfNotExists();
-    try (Connection con = jdbcAccountPatchReviewStore.getConnection();
-        PreparedStatement stmt =
-            con.prepareStatement(
-                "INSERT INTO account_patch_reviews "
-                    + "(account_id, change_id, patch_set_id, file_name) VALUES "
-                    + "(?, ?, ?, ?)")) {
-      int batchCount = 0;
-
-      try (Statement s = newStatement(db);
-          ResultSet rs = s.executeQuery("SELECT * from account_patch_reviews")) {
-        while (rs.next()) {
-          stmt.setInt(1, rs.getInt("account_id"));
-          stmt.setInt(2, rs.getInt("change_id"));
-          stmt.setInt(3, rs.getInt("patch_set_id"));
-          stmt.setString(4, rs.getString("file_name"));
-          stmt.addBatch();
-          batchCount++;
-          if (batchCount >= MAX_BATCH_SIZE) {
-            stmt.executeBatch();
-            batchCount = 0;
-          }
-        }
-      }
-      if (batchCount > 0) {
-        stmt.executeBatch();
-      }
-    } catch (SQLException e) {
-      throw jdbcAccountPatchReviewStore.convertError("insert", e);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_128.java b/java/com/google/gerrit/server/schema/Schema_128.java
deleted file mode 100644
index cd2718b..0000000
--- a/java/com/google/gerrit/server/schema/Schema_128.java
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.schema.AclUtil.grant;
-
-import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_128 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG = "Add addPatchSet permission to all projects";
-
-  private final GitRepositoryManager repoManager;
-  private final AllProjectsName allProjectsName;
-  private final SystemGroupBackend systemGroupBackend;
-  private final ProjectConfig.Factory projectConfigFactory;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_128(
-      Provider<Schema_127> prior,
-      GitRepositoryManager repoManager,
-      AllProjectsName allProjectsName,
-      SystemGroupBackend systemGroupBackend,
-      ProjectConfig.Factory projectConfigFactory,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allProjectsName = allProjectsName;
-    this.systemGroupBackend = systemGroupBackend;
-    this.projectConfigFactory = projectConfigFactory;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    try (Repository git = repoManager.openRepository(allProjectsName);
-        MetaDataUpdate md =
-            new MetaDataUpdate(GitReferenceUpdated.DISABLED, allProjectsName, git)) {
-      ProjectConfig config = projectConfigFactory.read(md);
-
-      GroupReference registered = systemGroupBackend.getGroup(REGISTERED_USERS);
-      AccessSection refsFor = config.getAccessSection("refs/for/*", true);
-      grant(config, refsFor, Permission.ADD_PATCH_SET, false, false, registered);
-
-      md.getCommitBuilder().setAuthor(serverUser);
-      md.getCommitBuilder().setCommitter(serverUser);
-      md.setMessage(COMMIT_MSG);
-      config.commit(md);
-    } catch (ConfigInvalidException | IOException ex) {
-      throw new OrmException(ex);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_129.java b/java/com/google/gerrit/server/schema/Schema_129.java
deleted file mode 100644
index f7cd8c3..0000000
--- a/java/com/google/gerrit/server/schema/Schema_129.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-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.SQLException;
-import java.sql.Statement;
-
-public class Schema_129 extends ReviewDbSchemaVersion {
-
-  @Inject
-  Schema_129(Provider<Schema_128> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void preUpdateSchema(ReviewDb db) throws OrmException {
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement()) {
-      stmt.execute("ALTER TABLE patch_sets MODIFY groups clob");
-    } catch (SQLException e) {
-      // Ignore.  Type may have already been modified manually.
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_130.java b/java/com/google/gerrit/server/schema/Schema_130.java
deleted file mode 100644
index e550121..0000000
--- a/java/com/google/gerrit/server/schema/Schema_130.java
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import static java.util.stream.Collectors.joining;
-
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_130 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG =
-      "Remove force option from 'Push Annotated Tag' permission\n"
-          + "\n"
-          + "The force option on 'Push Annotated Tag' had no effect and is no longer\n"
-          + "supported.";
-
-  private final GitRepositoryManager repoManager;
-  private final PersonIdent serverUser;
-  private final ProjectConfigSchemaUpdate.Factory projectConfigSchemaUpdateFactory;
-
-  @Inject
-  Schema_130(
-      Provider<Schema_129> prior,
-      GitRepositoryManager repoManager,
-      @GerritPersonIdent PersonIdent serverUser,
-      ProjectConfigSchemaUpdate.Factory projectConfigSchemaUpdateFactory) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.serverUser = serverUser;
-    this.projectConfigSchemaUpdateFactory = projectConfigSchemaUpdateFactory;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    SortedSet<Project.NameKey> repoList = repoManager.list();
-    SortedSet<Project.NameKey> repoUpgraded = new TreeSet<>();
-    ui.message("\tMigrating " + repoList.size() + " repositories ...");
-    for (Project.NameKey projectName : repoList) {
-      try (Repository git = repoManager.openRepository(projectName);
-          MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, projectName, git)) {
-        ProjectConfigSchemaUpdate cfg = projectConfigSchemaUpdateFactory.read(md);
-        cfg.removeForceFromPermission("pushTag");
-        if (cfg.isUpdated()) {
-          repoUpgraded.add(projectName);
-        }
-        cfg.save(serverUser, COMMIT_MSG);
-      } catch (ConfigInvalidException | IOException ex) {
-        throw new OrmException("Cannot migrate project " + projectName, ex);
-      }
-    }
-    ui.message("\tMigration completed:  " + repoUpgraded.size() + " repositories updated:");
-    ui.message("\t" + repoUpgraded.stream().map(Project.NameKey::get).collect(joining(" ")));
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_131.java b/java/com/google/gerrit/server/schema/Schema_131.java
deleted file mode 100644
index b37ae4b..0000000
--- a/java/com/google/gerrit/server/schema/Schema_131.java
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import static java.util.stream.Collectors.joining;
-
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_131 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG =
-      "Rename 'Push Annotated/Signed Tag' permission to 'Create Annotated/Signed Tag'";
-
-  private final GitRepositoryManager repoManager;
-  private final ProjectConfig.Factory projectConfigFactory;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_131(
-      Provider<Schema_130> prior,
-      GitRepositoryManager repoManager,
-      ProjectConfig.Factory projectConfigFactory,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.projectConfigFactory = projectConfigFactory;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    SortedSet<Project.NameKey> repoList = repoManager.list();
-    SortedSet<Project.NameKey> repoUpgraded = new TreeSet<>();
-    ui.message("\tMigrating " + repoList.size() + " repositories ...");
-    for (Project.NameKey projectName : repoList) {
-      try (Repository git = repoManager.openRepository(projectName);
-          MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, projectName, git)) {
-        ProjectConfig config = projectConfigFactory.read(md);
-        if (config.hasLegacyPermissions()) {
-          md.getCommitBuilder().setAuthor(serverUser);
-          md.getCommitBuilder().setCommitter(serverUser);
-          md.setMessage(COMMIT_MSG);
-          config.commit(md);
-          repoUpgraded.add(projectName);
-        }
-      } catch (ConfigInvalidException | IOException ex) {
-        throw new OrmException("Cannot migrate project " + projectName, ex);
-      }
-    }
-    ui.message("\tMigration completed:  " + repoUpgraded.size() + " repositories updated:");
-    ui.message("\t" + repoUpgraded.stream().map(Project.NameKey::get).collect(joining(" ")));
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_132.java b/java/com/google/gerrit/server/schema/Schema_132.java
deleted file mode 100644
index 72ec590..0000000
--- a/java/com/google/gerrit/server/schema/Schema_132.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_132 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_132(Provider<Schema_131> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_133.java b/java/com/google/gerrit/server/schema/Schema_133.java
deleted file mode 100644
index 29cd6e9..0000000
--- a/java/com/google/gerrit/server/schema/Schema_133.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_133 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_133(Provider<Schema_132> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_134.java b/java/com/google/gerrit/server/schema/Schema_134.java
deleted file mode 100644
index 298ac03..0000000
--- a/java/com/google/gerrit/server/schema/Schema_134.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_134 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_134(Provider<Schema_133> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_135.java b/java/com/google/gerrit/server/schema/Schema_135.java
deleted file mode 100644
index 03ef84a..0000000
--- a/java/com/google/gerrit/server/schema/Schema_135.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import static com.google.gerrit.server.group.SystemGroupBackend.PROJECT_OWNERS;
-import static java.util.stream.Collectors.toSet;
-
-import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.util.Set;
-import java.util.stream.Stream;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_135 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG =
-      "Allow admins and project owners to create refs/meta/config";
-
-  private final GitRepositoryManager repoManager;
-  private final AllProjectsName allProjectsName;
-  private final SystemGroupBackend systemGroupBackend;
-  private final ProjectConfig.Factory projectConfigFactory;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_135(
-      Provider<Schema_134> prior,
-      GitRepositoryManager repoManager,
-      AllProjectsName allProjectsName,
-      SystemGroupBackend systemGroupBackend,
-      ProjectConfig.Factory projectConfigFactory,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allProjectsName = allProjectsName;
-    this.systemGroupBackend = systemGroupBackend;
-    this.projectConfigFactory = projectConfigFactory;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    try (Repository git = repoManager.openRepository(allProjectsName);
-        MetaDataUpdate md =
-            new MetaDataUpdate(GitReferenceUpdated.DISABLED, allProjectsName, git)) {
-      ProjectConfig config = projectConfigFactory.read(md);
-
-      AccessSection meta = config.getAccessSection(RefNames.REFS_CONFIG, true);
-      Permission createRefsMetaConfigPermission = meta.getPermission(Permission.CREATE, true);
-
-      Set<GroupReference> groups =
-          Stream.concat(
-                  config
-                      .getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true)
-                      .getPermission(GlobalCapability.ADMINISTRATE_SERVER, true)
-                      .getRules()
-                      .stream()
-                      .map(PermissionRule::getGroup),
-                  Stream.of(systemGroupBackend.getGroup(PROJECT_OWNERS)))
-              .filter(g -> createRefsMetaConfigPermission.getRule(g) == null)
-              .collect(toSet());
-
-      for (GroupReference group : groups) {
-        createRefsMetaConfigPermission.add(new PermissionRule(config.resolve(group)));
-      }
-
-      md.getCommitBuilder().setAuthor(serverUser);
-      md.getCommitBuilder().setCommitter(serverUser);
-      md.setMessage(COMMIT_MSG);
-      config.commit(md);
-    } catch (ConfigInvalidException | IOException ex) {
-      throw new OrmException(ex);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_136.java b/java/com/google/gerrit/server/schema/Schema_136.java
deleted file mode 100644
index 11eda7b..0000000
--- a/java/com/google/gerrit/server/schema/Schema_136.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_136 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_136(Provider<Schema_135> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_137.java b/java/com/google/gerrit/server/schema/Schema_137.java
deleted file mode 100644
index a6ea83b..0000000
--- a/java/com/google/gerrit/server/schema/Schema_137.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/* change the type of SystemConfig#sitePath to CLOB */
-public class Schema_137 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_137(Provider<Schema_136> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_138.java b/java/com/google/gerrit/server/schema/Schema_138.java
deleted file mode 100644
index 61d55ec..0000000
--- a/java/com/google/gerrit/server/schema/Schema_138.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/* Add resolved field to PatchLineComment */
-public class Schema_138 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_138(Provider<Schema_137> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_139.java b/java/com/google/gerrit/server/schema/Schema_139.java
deleted file mode 100644
index a234be5..0000000
--- a/java/com/google/gerrit/server/schema/Schema_139.java
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.auto.value.AutoValue;
-import com.google.common.base.Strings;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.AccountConfig;
-import com.google.gerrit.server.account.InternalAccountUpdate;
-import com.google.gerrit.server.account.ProjectWatches.NotifyType;
-import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-public class Schema_139 extends ReviewDbSchemaVersion {
-  private static final String MSG = "Migrate project watches to git";
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_139(
-      Provider<Schema_138> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    ListMultimap<Account.Id, ProjectWatch> imports =
-        MultimapBuilder.hashKeys().arrayListValues().build();
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
-        ResultSet rs =
-            stmt.executeQuery(
-                "SELECT "
-                    + "account_id, "
-                    + "project_name, "
-                    + "filter, "
-                    + "notify_abandoned_changes, "
-                    + "notify_all_comments, "
-                    + "notify_new_changes, "
-                    + "notify_new_patch_sets, "
-                    + "notify_submitted_changes "
-                    + "FROM account_project_watches")) {
-      while (rs.next()) {
-        Account.Id accountId = new Account.Id(rs.getInt(1));
-        ProjectWatch.Builder b =
-            ProjectWatch.builder()
-                .project(new Project.NameKey(rs.getString(2)))
-                .filter(rs.getString(3))
-                .notifyAbandonedChanges(toBoolean(rs.getString(4)))
-                .notifyAllComments(toBoolean(rs.getString(5)))
-                .notifyNewChanges(toBoolean(rs.getString(6)))
-                .notifyNewPatchSets(toBoolean(rs.getString(7)))
-                .notifySubmittedChanges(toBoolean(rs.getString(8)));
-        imports.put(accountId, b.build());
-      }
-    }
-
-    if (imports.isEmpty()) {
-      return;
-    }
-
-    try (Repository git = repoManager.openRepository(allUsersName);
-        RevWalk rw = new RevWalk(git)) {
-      BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
-      bru.setRefLogIdent(serverUser);
-      bru.setRefLogMessage(MSG, false);
-
-      for (Map.Entry<Account.Id, Collection<ProjectWatch>> e : imports.asMap().entrySet()) {
-        Map<ProjectWatchKey, Set<NotifyType>> projectWatches = new HashMap<>();
-        for (ProjectWatch projectWatch : e.getValue()) {
-          ProjectWatchKey key =
-              ProjectWatchKey.create(projectWatch.project(), projectWatch.filter());
-          if (projectWatches.containsKey(key)) {
-            throw new OrmDuplicateKeyException(
-                "Duplicate key for watched project: " + key.toString());
-          }
-          Set<NotifyType> notifyValues = EnumSet.noneOf(NotifyType.class);
-          if (projectWatch.notifyAbandonedChanges()) {
-            notifyValues.add(NotifyType.ABANDONED_CHANGES);
-          }
-          if (projectWatch.notifyAllComments()) {
-            notifyValues.add(NotifyType.ALL_COMMENTS);
-          }
-          if (projectWatch.notifyNewChanges()) {
-            notifyValues.add(NotifyType.NEW_CHANGES);
-          }
-          if (projectWatch.notifyNewPatchSets()) {
-            notifyValues.add(NotifyType.NEW_PATCHSETS);
-          }
-          if (projectWatch.notifySubmittedChanges()) {
-            notifyValues.add(NotifyType.SUBMITTED_CHANGES);
-          }
-          projectWatches.put(key, notifyValues);
-        }
-
-        try (MetaDataUpdate md =
-            new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git, bru)) {
-          md.getCommitBuilder().setAuthor(serverUser);
-          md.getCommitBuilder().setCommitter(serverUser);
-          md.setMessage(MSG);
-
-          AccountConfig accountConfig = new AccountConfig(e.getKey(), allUsersName, git);
-          accountConfig.load(md);
-          accountConfig.setAccountUpdate(
-              InternalAccountUpdate.builder()
-                  .deleteProjectWatches(accountConfig.getProjectWatches().keySet())
-                  .updateProjectWatches(projectWatches)
-                  .build());
-          accountConfig.commit(md);
-        }
-      }
-      bru.execute(rw, NullProgressMonitor.INSTANCE);
-    } catch (IOException | ConfigInvalidException ex) {
-      throw new OrmException(ex);
-    }
-  }
-
-  @AutoValue
-  abstract static class ProjectWatch {
-    abstract Project.NameKey project();
-
-    abstract @Nullable String filter();
-
-    abstract boolean notifyAbandonedChanges();
-
-    abstract boolean notifyAllComments();
-
-    abstract boolean notifyNewChanges();
-
-    abstract boolean notifyNewPatchSets();
-
-    abstract boolean notifySubmittedChanges();
-
-    static Builder builder() {
-      return new AutoValue_Schema_139_ProjectWatch.Builder();
-    }
-
-    @AutoValue.Builder
-    abstract static class Builder {
-      abstract Builder project(Project.NameKey project);
-
-      abstract Builder filter(@Nullable String filter);
-
-      abstract Builder notifyAbandonedChanges(boolean notifyAbandonedChanges);
-
-      abstract Builder notifyAllComments(boolean notifyAllComments);
-
-      abstract Builder notifyNewChanges(boolean notifyNewChanges);
-
-      abstract Builder notifyNewPatchSets(boolean notifyNewPatchSets);
-
-      abstract Builder notifySubmittedChanges(boolean notifySubmittedChanges);
-
-      abstract ProjectWatch build();
-    }
-  }
-
-  private static boolean toBoolean(String v) {
-    checkState(!Strings.isNullOrEmpty(v));
-    return v.equals("Y");
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_140.java b/java/com/google/gerrit/server/schema/Schema_140.java
deleted file mode 100644
index f59368c..0000000
--- a/java/com/google/gerrit/server/schema/Schema_140.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/** Remove ChangeMessage sequence. */
-public class Schema_140 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_140(Provider<Schema_139> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_141.java b/java/com/google/gerrit/server/schema/Schema_141.java
deleted file mode 100644
index ce1c910..0000000
--- a/java/com/google/gerrit/server/schema/Schema_141.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/** Add status field to account. */
-public class Schema_141 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_141(Provider<Schema_140> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_142.java b/java/com/google/gerrit/server/schema/Schema_142.java
deleted file mode 100644
index 9e06c52..0000000
--- a/java/com/google/gerrit/server/schema/Schema_142.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
-
-import com.google.common.base.Strings;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.account.HashedPassword;
-import com.google.gerrit.server.account.externalids.ExternalId;
-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.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-public class Schema_142 extends ReviewDbSchemaVersion {
-  private static final int MAX_BATCH_SIZE = 1000;
-
-  @Inject
-  Schema_142(Provider<Schema_141> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try (PreparedStatement updateStmt =
-        ((JdbcSchema) db)
-            .getConnection()
-            .prepareStatement(
-                "UPDATE account_external_ids " + "SET password = ? " + "WHERE external_id = ?")) {
-      int batchCount = 0;
-
-      try (Statement stmt = newStatement(db);
-          ResultSet rs =
-              stmt.executeQuery("SELECT external_id, password FROM account_external_ids")) {
-        while (rs.next()) {
-          String externalId = rs.getString("external_id");
-          String password = rs.getString("password");
-          if (!ExternalId.Key.parse(externalId).isScheme(SCHEME_USERNAME)
-              || Strings.isNullOrEmpty(password)) {
-            continue;
-          }
-
-          HashedPassword hashed = HashedPassword.fromPassword(password);
-          updateStmt.setString(1, hashed.encode());
-          updateStmt.setString(2, externalId);
-          updateStmt.addBatch();
-          batchCount++;
-          if (batchCount >= MAX_BATCH_SIZE) {
-            updateStmt.executeBatch();
-            batchCount = 0;
-          }
-        }
-      }
-
-      if (batchCount > 0) {
-        updateStmt.executeBatch();
-      }
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_143.java b/java/com/google/gerrit/server/schema/Schema_143.java
deleted file mode 100644
index 10d108f..0000000
--- a/java/com/google/gerrit/server/schema/Schema_143.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/** Add isPrivate field to change. */
-public class Schema_143 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_143(Provider<Schema_142> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_144.java b/java/com/google/gerrit/server/schema/Schema_144.java
deleted file mode 100644
index 0aad504..0000000
--- a/java/com/google/gerrit/server/schema/Schema_144.java
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (C) 2016 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.schema;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIdNotes;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.HashSet;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_144 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG = "Import external IDs from ReviewDb";
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final PersonIdent serverIdent;
-
-  @Inject
-  Schema_144(
-      Provider<Schema_143> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      @GerritPersonIdent PersonIdent serverIdent) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.serverIdent = serverIdent;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    Set<ExternalId> toAdd = new HashSet<>();
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
-        ResultSet rs =
-            stmt.executeQuery(
-                "SELECT "
-                    + "account_id, "
-                    + "email_address, "
-                    + "password, "
-                    + "external_id "
-                    + "FROM account_external_ids")) {
-      while (rs.next()) {
-        Account.Id accountId = new Account.Id(rs.getInt(1));
-        String email = rs.getString(2);
-        String password = rs.getString(3);
-        String externalId = rs.getString(4);
-
-        toAdd.add(ExternalId.create(ExternalId.Key.parse(externalId), accountId, email, password));
-      }
-    }
-
-    try {
-      try (Repository repo = repoManager.openRepository(allUsersName)) {
-        ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsersName, repo);
-        extIdNotes.upsert(toAdd);
-        try (MetaDataUpdate metaDataUpdate =
-            new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, repo)) {
-          metaDataUpdate.getCommitBuilder().setAuthor(serverIdent);
-          metaDataUpdate.getCommitBuilder().setCommitter(serverIdent);
-          metaDataUpdate.getCommitBuilder().setMessage(COMMIT_MSG);
-          extIdNotes.commit(metaDataUpdate);
-        }
-      }
-    } catch (IOException | ConfigInvalidException e) {
-      throw new OrmException("Failed to migrate external IDs to NoteDb", e);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_145.java b/java/com/google/gerrit/server/schema/Schema_145.java
deleted file mode 100644
index 1207632..0000000
--- a/java/com/google/gerrit/server/schema/Schema_145.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-
-/** Create account_external_ids_byEmail index. */
-public class Schema_145 extends ReviewDbSchemaVersion {
-
-  @Inject
-  Schema_145(Provider<Schema_144> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    JdbcSchema schema = (JdbcSchema) db;
-    SqlDialect dialect = schema.getDialect();
-    try (StatementExecutor e = newExecutor(db)) {
-      try {
-        dialect.dropIndex(e, "account_external_ids", "account_external_ids_byEmail");
-      } catch (OrmException ex) {
-        // Ignore.  The index did not exist.
-      }
-      e.execute(
-          "CREATE INDEX account_external_ids_byEmail"
-              + " ON account_external_ids"
-              + " (email_address)");
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_146.java b/java/com/google/gerrit/server/schema/Schema_146.java
deleted file mode 100644
index 2930408..0000000
--- a/java/com/google/gerrit/server/schema/Schema_146.java
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.util.HashMap;
-import java.util.Map;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevSort;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-/**
- * Make sure that for every account a user branch exists that has an initial empty commit with the
- * registration date as commit time.
- *
- * <p>For accounts that don't have a user branch yet the user branch is created with an initial
- * empty commit that has the registration date as commit time.
- *
- * <p>For accounts that already have a user branch the user branch is rewritten and an initial empty
- * commit with the registration date as commit time is inserted (if such a commit doesn't exist
- * yet).
- */
-public class Schema_146 extends ReviewDbSchemaVersion {
-  private static final String CREATE_ACCOUNT_MSG = "Create Account";
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final PersonIdent serverIdent;
-
-  @Inject
-  Schema_146(
-      Provider<Schema_145> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      @GerritPersonIdent PersonIdent serverIdent) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.serverIdent = serverIdent;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try (Repository repo = repoManager.openRepository(allUsersName);
-        RevWalk rw = new RevWalk(repo);
-        ObjectInserter oi = repo.newObjectInserter()) {
-      ObjectId emptyTree = emptyTree(oi);
-
-      for (Map.Entry<Account.Id, Timestamp> e : scanAccounts(db).entrySet()) {
-        String refName = RefNames.refsUsers(e.getKey());
-        Ref ref = repo.exactRef(refName);
-        if (ref != null) {
-          rewriteUserBranch(repo, rw, oi, emptyTree, ref, e.getValue());
-        } else {
-          createUserBranch(repo, oi, emptyTree, e.getKey(), e.getValue());
-        }
-      }
-    } catch (IOException e) {
-      throw new OrmException("Failed to rewrite user branches.", e);
-    }
-  }
-
-  private void rewriteUserBranch(
-      Repository repo,
-      RevWalk rw,
-      ObjectInserter oi,
-      ObjectId emptyTree,
-      Ref ref,
-      Timestamp registeredOn)
-      throws IOException {
-    ObjectId current = createInitialEmptyCommit(oi, emptyTree, registeredOn);
-
-    rw.reset();
-    rw.sort(RevSort.TOPO);
-    rw.sort(RevSort.REVERSE, true);
-    rw.markStart(rw.parseCommit(ref.getObjectId()));
-
-    RevCommit c;
-    while ((c = rw.next()) != null) {
-      if (isInitialEmptyCommit(emptyTree, c)) {
-        return;
-      }
-
-      CommitBuilder cb = new CommitBuilder();
-      cb.setParentId(current);
-      cb.setTreeId(c.getTree());
-      cb.setAuthor(c.getAuthorIdent());
-      cb.setCommitter(c.getCommitterIdent());
-      cb.setMessage(c.getFullMessage());
-      cb.setEncoding(c.getEncoding());
-      current = oi.insert(cb);
-    }
-
-    oi.flush();
-
-    RefUpdate ru = repo.updateRef(ref.getName());
-    ru.setExpectedOldObjectId(ref.getObjectId());
-    ru.setNewObjectId(current);
-    ru.setForceUpdate(true);
-    ru.setRefLogIdent(serverIdent);
-    ru.setRefLogMessage(getClass().getSimpleName(), true);
-    Result result = ru.update();
-    if (result != Result.FORCED) {
-      throw new IOException(
-          String.format("Failed to update ref %s: %s", ref.getName(), result.name()));
-    }
-  }
-
-  public void createUserBranch(
-      Repository repo,
-      ObjectInserter oi,
-      ObjectId emptyTree,
-      Account.Id accountId,
-      Timestamp registeredOn)
-      throws IOException {
-    ObjectId id = createInitialEmptyCommit(oi, emptyTree, registeredOn);
-
-    String refName = RefNames.refsUsers(accountId);
-    RefUpdate ru = repo.updateRef(refName);
-    ru.setExpectedOldObjectId(ObjectId.zeroId());
-    ru.setNewObjectId(id);
-    ru.setRefLogIdent(serverIdent);
-    ru.setRefLogMessage(CREATE_ACCOUNT_MSG, false);
-    Result result = ru.update();
-    if (result != Result.NEW) {
-      throw new IOException(String.format("Failed to update ref %s: %s", refName, result.name()));
-    }
-  }
-
-  private ObjectId createInitialEmptyCommit(
-      ObjectInserter oi, ObjectId emptyTree, Timestamp registrationDate) throws IOException {
-    PersonIdent ident = new PersonIdent(serverIdent, registrationDate);
-
-    CommitBuilder cb = new CommitBuilder();
-    cb.setTreeId(emptyTree);
-    cb.setCommitter(ident);
-    cb.setAuthor(ident);
-    cb.setMessage(CREATE_ACCOUNT_MSG);
-    return oi.insert(cb);
-  }
-
-  private boolean isInitialEmptyCommit(ObjectId emptyTree, RevCommit c) {
-    return c.getParentCount() == 0
-        && c.getTree().equals(emptyTree)
-        && c.getShortMessage().equals(CREATE_ACCOUNT_MSG);
-  }
-
-  private static ObjectId emptyTree(ObjectInserter oi) throws IOException {
-    return oi.insert(Constants.OBJ_TREE, new byte[] {});
-  }
-
-  private Map<Account.Id, Timestamp> scanAccounts(ReviewDb db) throws SQLException {
-    try (Statement stmt = newStatement(db);
-        ResultSet rs = stmt.executeQuery("SELECT account_id, registered_on FROM accounts")) {
-      HashMap<Account.Id, Timestamp> m = new HashMap<>();
-      while (rs.next()) {
-        m.put(new Account.Id(rs.getInt(1)), rs.getTimestamp(2));
-      }
-      return m;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_147.java b/java/com/google/gerrit/server/schema/Schema_147.java
deleted file mode 100644
index f317e9a..0000000
--- a/java/com/google/gerrit/server/schema/Schema_147.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static java.util.stream.Collectors.toSet;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.eclipse.jgit.lib.Repository;
-
-/** Delete user branches for which no account exists. */
-public class Schema_147 extends ReviewDbSchemaVersion {
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-
-  @Inject
-  Schema_147(
-      Provider<Schema_146> prior, GitRepositoryManager repoManager, AllUsersName allUsersName) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try (Repository repo = repoManager.openRepository(allUsersName)) {
-      Set<Account.Id> accountIdsFromReviewDb = scanAccounts(db);
-      Set<Account.Id> accountIdsFromUserBranches =
-          repo.getRefDatabase()
-              .getRefsByPrefix(RefNames.REFS_USERS)
-              .stream()
-              .map(r -> Account.Id.fromRef(r.getName()))
-              .filter(Objects::nonNull)
-              .collect(toSet());
-      accountIdsFromUserBranches.removeAll(accountIdsFromReviewDb);
-      for (Account.Id accountId : accountIdsFromUserBranches) {
-        deleteUserBranch(repo, accountId);
-      }
-    } catch (IOException e) {
-      throw new OrmException("Failed to delete user branches for non-existing accounts.", e);
-    }
-  }
-
-  private Set<Account.Id> scanAccounts(ReviewDb db) throws SQLException {
-    try (Statement stmt = newStatement(db);
-        ResultSet rs = stmt.executeQuery("SELECT account_id FROM accounts")) {
-      Set<Account.Id> ids = new HashSet<>();
-      while (rs.next()) {
-        ids.add(new Account.Id(rs.getInt(1)));
-      }
-      return ids;
-    }
-  }
-
-  private void deleteUserBranch(Repository allUsersRepo, Account.Id accountId) throws IOException {
-    String refName = RefNames.refsUsers(accountId);
-    Ref ref = allUsersRepo.exactRef(refName);
-    if (ref == null) {
-      return;
-    }
-
-    RefUpdate ru = allUsersRepo.updateRef(refName);
-    ru.setExpectedOldObjectId(ref.getObjectId());
-    ru.setNewObjectId(ObjectId.zeroId());
-    ru.setForceUpdate(true);
-    Result result = ru.delete();
-    if (result != Result.FORCED) {
-      throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_148.java b/java/com/google/gerrit/server/schema/Schema_148.java
deleted file mode 100644
index 949dd5a..0000000
--- a/java/com/google/gerrit/server/schema/Schema_148.java
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.common.primitives.Ints;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIdNotes;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.SQLException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_148 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG = "Make account IDs of external IDs human-readable";
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_148(
-      Provider<Schema_147> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try (Repository repo = repoManager.openRepository(allUsersName)) {
-      ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsersName, repo);
-      for (ExternalId extId : extIdNotes.all()) {
-        if (needsUpdate(extId)) {
-          extIdNotes.upsert(extId);
-        }
-      }
-
-      try (MetaDataUpdate metaDataUpdate =
-          new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, repo)) {
-        metaDataUpdate.getCommitBuilder().setAuthor(serverUser);
-        metaDataUpdate.getCommitBuilder().setCommitter(serverUser);
-        metaDataUpdate.getCommitBuilder().setMessage(COMMIT_MSG);
-        extIdNotes.commit(metaDataUpdate);
-      }
-    } catch (IOException | ConfigInvalidException e) {
-      throw new OrmException("Failed to update external IDs", e);
-    }
-  }
-
-  private static boolean needsUpdate(ExternalId extId) {
-    Config cfg = new Config();
-    cfg.setInt("externalId", extId.key().get(), "accountId", extId.accountId().get());
-    return Ints.tryParse(cfg.getString("externalId", extId.key().get(), "accountId")) == null;
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_149.java b/java/com/google/gerrit/server/schema/Schema_149.java
deleted file mode 100644
index 156091b..0000000
--- a/java/com/google/gerrit/server/schema/Schema_149.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/** Add workInProgress field to change. */
-public class Schema_149 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_149(Provider<Schema_148> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_150.java b/java/com/google/gerrit/server/schema/Schema_150.java
deleted file mode 100644
index 71736f3..0000000
--- a/java/com/google/gerrit/server/schema/Schema_150.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/** Drop ACCOUNT_EXTERNAL_IDS table. */
-public class Schema_150 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_150(Provider<Schema_149> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_151.java b/java/com/google/gerrit/server/schema/Schema_151.java
deleted file mode 100644
index 0e8700f..0000000
--- a/java/com/google/gerrit/server/schema/Schema_151.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-/** A schema which adds the 'created on' field to groups. */
-public class Schema_151 extends ReviewDbSchemaVersion {
-  @Inject
-  protected Schema_151(Provider<Schema_150> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try (PreparedStatement groupUpdate =
-            prepareStatement(db, "UPDATE account_groups SET created_on = ? WHERE group_id = ?");
-        PreparedStatement addedOnRetrieval =
-            prepareStatement(
-                db,
-                "SELECT added_on FROM account_group_members_audit WHERE group_id = ?"
-                    + " ORDER BY added_on ASC")) {
-      List<AccountGroup.Id> accountGroups = getAllGroupIds(db);
-      for (AccountGroup.Id groupId : accountGroups) {
-        Optional<Timestamp> firstTimeMentioned = getFirstTimeMentioned(addedOnRetrieval, groupId);
-        Timestamp createdOn = firstTimeMentioned.orElseGet(AccountGroup::auditCreationInstantTs);
-
-        groupUpdate.setTimestamp(1, createdOn);
-        groupUpdate.setInt(2, groupId.get());
-        groupUpdate.executeUpdate();
-      }
-    }
-  }
-
-  private static Optional<Timestamp> getFirstTimeMentioned(
-      PreparedStatement addedOnRetrieval, AccountGroup.Id groupId) throws SQLException {
-    addedOnRetrieval.setInt(1, groupId.get());
-    try (ResultSet resultSet = addedOnRetrieval.executeQuery()) {
-      if (resultSet.next()) {
-        return Optional.of(resultSet.getTimestamp(1));
-      }
-    }
-    return Optional.empty();
-  }
-
-  private static List<AccountGroup.Id> getAllGroupIds(ReviewDb db) throws SQLException {
-    try (Statement stmt = newStatement(db);
-        ResultSet rs = stmt.executeQuery("SELECT group_id FROM account_groups")) {
-      List<AccountGroup.Id> groupIds = new ArrayList<>();
-      while (rs.next()) {
-        groupIds.add(new AccountGroup.Id(rs.getInt(1)));
-      }
-      return groupIds;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_152.java b/java/com/google/gerrit/server/schema/Schema_152.java
deleted file mode 100644
index b605e90..0000000
--- a/java/com/google/gerrit/server/schema/Schema_152.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-
-/** Drop unused indexes from accounts table. */
-public class Schema_152 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_152(Provider<Schema_151> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    JdbcSchema schema = (JdbcSchema) db;
-    SqlDialect dialect = schema.getDialect();
-    try (StatementExecutor e = newExecutor(db)) {
-      dialect.dropIndex(e, "accounts", "accounts_byFullName");
-    } catch (OrmException ex) {
-      // Ignore. The index did not exist.
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_153.java b/java/com/google/gerrit/server/schema/Schema_153.java
deleted file mode 100644
index 7d1f63e..0000000
--- a/java/com/google/gerrit/server/schema/Schema_153.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/** Add reviewStarted field to change. */
-public class Schema_153 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_153(Provider<Schema_152> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    try (StatementExecutor e = newExecutor(db)) {
-      // Initialize review_started to a sensible default value according to
-      // whether change is currently WIP. No migration is needed in NoteDb,
-      // where the value of review_started is always derived from the history
-      // of assignments to work_in_progress.
-      e.execute(
-          "UPDATE changes SET review_started = 'Y', created_on = created_on WHERE work_in_progress = 'N'");
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_154.java b/java/com/google/gerrit/server/schema/Schema_154.java
deleted file mode 100644
index 8c97010..0000000
--- a/java/com/google/gerrit/server/schema/Schema_154.java
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static java.util.stream.Collectors.toMap;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.AccountConfig;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.TextProgressMonitor;
-
-/** Migrate accounts to NoteDb. */
-public class Schema_154 extends ReviewDbSchemaVersion {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private static final String TABLE = "accounts";
-  private static final ImmutableMap<String, AccountSetter> ACCOUNT_FIELDS_MAP =
-      ImmutableMap.<String, AccountSetter>builder()
-          .put("full_name", (a, rs, field) -> a.setFullName(rs.getString(field)))
-          .put("preferred_email", (a, rs, field) -> a.setPreferredEmail(rs.getString(field)))
-          .put("status", (a, rs, field) -> a.setStatus(rs.getString(field)))
-          .put("inactive", (a, rs, field) -> a.setActive(rs.getString(field).equals("N")))
-          .build();
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final Provider<PersonIdent> serverIdent;
-
-  @Inject
-  Schema_154(
-      Provider<Schema_153> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      @GerritPersonIdent Provider<PersonIdent> serverIdent) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.serverIdent = serverIdent;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try {
-      try (Repository repo = repoManager.openRepository(allUsersName)) {
-        ProgressMonitor pm = new TextProgressMonitor();
-        pm.beginTask("Collecting accounts", ProgressMonitor.UNKNOWN);
-        Set<Account> accounts = scanAccounts(db, pm);
-        pm.endTask();
-        pm.beginTask("Migrating accounts to NoteDb", accounts.size());
-        for (Account account : accounts) {
-          updateAccountInNoteDb(repo, account);
-          pm.update(1);
-        }
-        pm.endTask();
-      }
-    } catch (IOException | ConfigInvalidException e) {
-      throw new OrmException("Migrating accounts to NoteDb failed", e);
-    }
-  }
-
-  private Set<Account> scanAccounts(ReviewDb db, ProgressMonitor pm) throws SQLException {
-    Map<String, AccountSetter> fields = getFields(db);
-    if (fields.isEmpty()) {
-      logger.atWarning().log("Only account_id and registered_on fields are migrated for accounts");
-    }
-
-    List<String> queryFields = new ArrayList<>();
-    queryFields.add("account_id");
-    queryFields.add("registered_on");
-    queryFields.addAll(fields.keySet());
-    String query = "SELECT " + String.join(", ", queryFields) + String.format(" FROM %s", TABLE);
-    try (Statement stmt = newStatement(db);
-        ResultSet rs = stmt.executeQuery(query)) {
-      Set<Account> s = new HashSet<>();
-      while (rs.next()) {
-        Account a = new Account(new Account.Id(rs.getInt(1)), rs.getTimestamp(2));
-        for (Map.Entry<String, AccountSetter> field : fields.entrySet()) {
-          field.getValue().set(a, rs, field.getKey());
-        }
-        s.add(a);
-        pm.update(1);
-      }
-      return s;
-    }
-  }
-
-  private Map<String, AccountSetter> getFields(ReviewDb db) throws SQLException {
-    JdbcSchema schema = (JdbcSchema) db;
-    Connection connection = schema.getConnection();
-    Set<String> columns = schema.getDialect().listColumns(connection, TABLE);
-    return ACCOUNT_FIELDS_MAP
-        .entrySet()
-        .stream()
-        .filter(e -> columns.contains(e.getKey()))
-        .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
-  }
-
-  private void updateAccountInNoteDb(Repository allUsersRepo, Account account)
-      throws IOException, ConfigInvalidException {
-    MetaDataUpdate md =
-        new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, allUsersRepo);
-    PersonIdent ident = serverIdent.get();
-    md.getCommitBuilder().setAuthor(ident);
-    md.getCommitBuilder().setCommitter(ident);
-    new AccountConfig(account.getId(), allUsersName, allUsersRepo)
-        .load()
-        .setAccount(account)
-        .commit(md);
-  }
-
-  @FunctionalInterface
-  private interface AccountSetter {
-    void set(Account a, ResultSet rs, String field) throws SQLException;
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_155.java b/java/com/google/gerrit/server/schema/Schema_155.java
deleted file mode 100644
index 812d7a6..0000000
--- a/java/com/google/gerrit/server/schema/Schema_155.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.RepoSequence;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-
-/** Create account sequence in NoteDb */
-public class Schema_155 extends ReviewDbSchemaVersion {
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-
-  @Inject
-  Schema_155(
-      Provider<Schema_154> prior, GitRepositoryManager repoManager, AllUsersName allUsersName) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    @SuppressWarnings("deprecation")
-    RepoSequence.Seed accountSeed = db::nextAccountId;
-    RepoSequence accountSeq =
-        new RepoSequence(
-            repoManager,
-            GitReferenceUpdated.DISABLED,
-            allUsersName,
-            Sequences.NAME_ACCOUNTS,
-            accountSeed,
-            1);
-
-    // consume one account ID to ensure that the account sequence is initialized in NoteDb
-    accountSeq.next();
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_156.java b/java/com/google/gerrit/server/schema/Schema_156.java
deleted file mode 100644
index 237161c..0000000
--- a/java/com/google/gerrit/server/schema/Schema_156.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/** Add revertOf field to change. */
-public class Schema_156 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_156(Provider<Schema_155> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_157.java b/java/com/google/gerrit/server/schema/Schema_157.java
deleted file mode 100644
index 20a8c4b..0000000
--- a/java/com/google/gerrit/server/schema/Schema_157.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-
-/** Drop unused indexes from accounts table. */
-public class Schema_157 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_157(Provider<Schema_156> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    JdbcSchema schema = (JdbcSchema) db;
-    SqlDialect dialect = schema.getDialect();
-    try (StatementExecutor e = newExecutor(db)) {
-      dialect.dropIndex(e, "accounts", "accounts_byPreferredEmail");
-    } catch (OrmException ex) {
-      // Ignore. The index did not exist.
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_158.java b/java/com/google/gerrit/server/schema/Schema_158.java
deleted file mode 100644
index 3b7d9e8..0000000
--- a/java/com/google/gerrit/server/schema/Schema_158.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/** Drop ACCOUNTS table. */
-public class Schema_158 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_158(Provider<Schema_157> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_159.java b/java/com/google/gerrit/server/schema/Schema_159.java
deleted file mode 100644
index ddb5765..0000000
--- a/java/com/google/gerrit/server/schema/Schema_159.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-/** Migrate draft changes to private or wip changes. */
-public class Schema_159 extends ReviewDbSchemaVersion {
-
-  private enum DraftWorkflowMigrationStrategy {
-    PRIVATE,
-    WORK_IN_PROGRESS
-  }
-
-  @Inject
-  Schema_159(Provider<Schema_158> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    DraftWorkflowMigrationStrategy strategy = DraftWorkflowMigrationStrategy.WORK_IN_PROGRESS;
-    if (ui.yesno(false, "Migrate draft changes to private changes (default is work-in-progress)")) {
-      strategy = DraftWorkflowMigrationStrategy.PRIVATE;
-    }
-    ui.message(
-        String.format("Replace draft changes with %s changes ...", strategy.name().toLowerCase()));
-    try (StatementExecutor e = newExecutor(db)) {
-      String column =
-          strategy == DraftWorkflowMigrationStrategy.PRIVATE ? "is_private" : "work_in_progress";
-      // Mark changes private/WIP and NEW if either:
-      // * they have status DRAFT
-      // * they have status NEW and have any draft patch sets
-      e.execute(
-          String.format(
-              "UPDATE changes "
-                  + "SET %s = 'Y', "
-                  + "    status = 'n', "
-                  + "    created_on = created_on "
-                  + "WHERE status = 'd' "
-                  + "  OR (status = 'n' "
-                  + "      AND EXISTS "
-                  + "        (SELECT * "
-                  + "         FROM patch_sets "
-                  + "         WHERE patch_sets.change_id = changes.change_id "
-                  + "           AND patch_sets.draft = 'Y')) ",
-              column));
-    }
-    ui.message("done");
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_160.java b/java/com/google/gerrit/server/schema/Schema_160.java
deleted file mode 100644
index 10f5c9d..0000000
--- a/java/com/google/gerrit/server/schema/Schema_160.java
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.gerrit.server.git.UserConfigSections.KEY_URL;
-import static com.google.gerrit.server.git.UserConfigSections.MY;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.Accounts;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.TextProgressMonitor;
-
-/**
- * Remove "My Drafts" menu items for all users and server-wide default preferences.
- *
- * <p>Since draft changes no longer exist, these menu items are obsolete.
- *
- * <p>Only matches menu items (with any name) where the URL exactly matches one of the following,
- * with or without leading {@code #}:
- *
- * <ul>
- *   <li>/q/is:draft
- *   <li>/q/owner:self+is:draft
- * </ul>
- *
- * In particular, this includes the <a
- * href="https://gerrit.googlesource.com/gerrit/+/v2.14.4/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneralPreferencesLoader.java#144">default
- * from version 2.14 and earlier</a>.
- *
- * <p>Other menus containing {@code is:draft} in other positions are not affected; this is still a
- * valid predicate that matches no changes.
- */
-public class Schema_160 extends ReviewDbSchemaVersion {
-  @VisibleForTesting static final ImmutableList<String> DEFAULT_DRAFT_ITEMS;
-
-  static {
-    String ownerSelfIsDraft = "/q/owner:self+is:draft";
-    String isDraft = "/q/is:draft";
-    DEFAULT_DRAFT_ITEMS =
-        ImmutableList.of(ownerSelfIsDraft, '#' + ownerSelfIsDraft, isDraft, '#' + isDraft);
-  }
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final Provider<PersonIdent> serverIdent;
-
-  @Inject
-  Schema_160(
-      Provider<Schema_159> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      @GerritPersonIdent Provider<PersonIdent> serverIdent) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.serverIdent = serverIdent;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    try {
-      try (Repository repo = repoManager.openRepository(allUsersName)) {
-        ProgressMonitor pm = new TextProgressMonitor();
-        pm.beginTask("Removing \"My Drafts\" menu items", ProgressMonitor.UNKNOWN);
-        for (Account.Id id : (Iterable<Account.Id>) Accounts.readUserRefs(repo)::iterator) {
-          removeMyDrafts(repo, RefNames.refsUsers(id), pm);
-        }
-        removeMyDrafts(repo, RefNames.REFS_USERS_DEFAULT, pm);
-        pm.endTask();
-      }
-    } catch (IOException | ConfigInvalidException e) {
-      throw new OrmException("Removing \"My Drafts\" menu items failed", e);
-    }
-  }
-
-  private void removeMyDrafts(Repository repo, String ref, ProgressMonitor pm)
-      throws IOException, ConfigInvalidException {
-    MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, repo);
-    PersonIdent ident = serverIdent.get();
-    md.getCommitBuilder().setAuthor(ident);
-    md.getCommitBuilder().setCommitter(ident);
-    Prefs prefs = new Prefs(ref);
-    prefs.load(allUsersName, repo);
-    prefs.removeMyDrafts();
-    prefs.commit(md);
-    if (prefs.dirty()) {
-      pm.update(1);
-    }
-  }
-
-  private static class Prefs extends VersionedAccountPreferences {
-    private boolean dirty;
-
-    Prefs(String ref) {
-      super(ref);
-    }
-
-    @Override
-    protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
-      if (!dirty) {
-        return false;
-      }
-      commit.setMessage("Remove \"My Drafts\" menu items");
-      return super.onSave(commit);
-    }
-
-    void removeMyDrafts() {
-      Config cfg = getConfig();
-      for (String item : cfg.getSubsections(MY)) {
-        String value = cfg.getString(MY, item, KEY_URL);
-        if (DEFAULT_DRAFT_ITEMS.contains(value)) {
-          cfg.unsetSection(MY, item);
-          dirty = true;
-        }
-      }
-    }
-
-    boolean dirty() {
-      return dirty;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_161.java b/java/com/google/gerrit/server/schema/Schema_161.java
deleted file mode 100644
index 4627abe..0000000
--- a/java/com/google/gerrit/server/schema/Schema_161.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static java.util.stream.Collectors.toList;
-
-import com.google.common.primitives.Ints;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.StarredChangesUtil;
-import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
-import com.google.gerrit.server.StarredChangesUtil.StarRef;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.TextProgressMonitor;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-
-public class Schema_161 extends ReviewDbSchemaVersion {
-  private static final String MUTE_LABEL = "mute";
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-
-  @Inject
-  Schema_161(
-      Provider<Schema_160> prior, GitRepositoryManager repoManager, AllUsersName allUsersName) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    try (Repository git = repoManager.openRepository(allUsersName);
-        RevWalk rw = new RevWalk(git)) {
-      BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
-      bru.setAllowNonFastForwards(true);
-
-      for (Ref ref : git.getRefDatabase().getRefsByPrefix(RefNames.REFS_STARRED_CHANGES)) {
-        StarRef starRef = StarredChangesUtil.readLabels(git, ref.getName());
-
-        Set<Integer> mutedPatchSets =
-            StarredChangesUtil.getStarredPatchSets(starRef.labels(), MUTE_LABEL);
-        if (mutedPatchSets.isEmpty()) {
-          continue;
-        }
-
-        Set<Integer> reviewedPatchSets =
-            StarredChangesUtil.getStarredPatchSets(
-                starRef.labels(), StarredChangesUtil.REVIEWED_LABEL);
-        Set<Integer> unreviewedPatchSets =
-            StarredChangesUtil.getStarredPatchSets(
-                starRef.labels(), StarredChangesUtil.UNREVIEWED_LABEL);
-
-        List<String> newLabels =
-            starRef
-                .labels()
-                .stream()
-                .map(
-                    l -> {
-                      if (l.startsWith(MUTE_LABEL)) {
-                        Integer mutedPatchSet = Ints.tryParse(l.substring(MUTE_LABEL.length() + 1));
-                        if (mutedPatchSet == null) {
-                          // unexpected format of mute label, must be a label that was manually
-                          // set, just leave it alone
-                          return l;
-                        }
-                        if (!reviewedPatchSets.contains(mutedPatchSet)
-                            && !unreviewedPatchSets.contains(mutedPatchSet)) {
-                          // convert mute label to reviewed label
-                          return StarredChangesUtil.REVIEWED_LABEL + "/" + mutedPatchSet;
-                        }
-                        // else patch set is muted but has either reviewed or unreviewed label
-                        // -> just drop the mute label
-                        return null;
-                      }
-                      return l;
-                    })
-                .filter(Objects::nonNull)
-                .collect(toList());
-
-        ObjectId id = StarredChangesUtil.writeLabels(git, newLabels);
-        bru.addCommand(new ReceiveCommand(ref.getTarget().getObjectId(), id, ref.getName()));
-      }
-      bru.execute(rw, new TextProgressMonitor());
-    } catch (IOException | IllegalLabelException ex) {
-      throw new OrmException(ex);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_162.java b/java/com/google/gerrit/server/schema/Schema_162.java
deleted file mode 100644
index 3d3a192..0000000
--- a/java/com/google/gerrit/server/schema/Schema_162.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-public class Schema_162 extends ReviewDbSchemaVersion {
-  private final GitRepositoryManager repoManager;
-  private final AllProjectsName allProjectsName;
-  private final AllUsersName allUsersName;
-  private final ProjectConfig.Factory projectConfigFactory;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_162(
-      Provider<Schema_161> prior,
-      GitRepositoryManager repoManager,
-      AllProjectsName allProjectsName,
-      AllUsersName allUsersName,
-      ProjectConfig.Factory projectConfigFactory,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allProjectsName = allProjectsName;
-    this.allUsersName = allUsersName;
-    this.projectConfigFactory = projectConfigFactory;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    try (Repository git = repoManager.openRepository(allUsersName);
-        MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git)) {
-      ProjectConfig cfg = projectConfigFactory.read(md);
-      if (allProjectsName.equals(cfg.getProject().getParent(allProjectsName))) {
-        return;
-      }
-      cfg.getProject().setParentName(allProjectsName);
-      md.getCommitBuilder().setAuthor(serverUser);
-      md.getCommitBuilder().setCommitter(serverUser);
-      md.setMessage(
-          String.format("Make %s inherit from %s", allUsersName.get(), allProjectsName.get()));
-      cfg.commit(md);
-    } catch (ConfigInvalidException | IOException ex) {
-      throw new OrmException(ex);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_163.java b/java/com/google/gerrit/server/schema/Schema_163.java
deleted file mode 100644
index 9eb5d5e..0000000
--- a/java/com/google/gerrit/server/schema/Schema_163.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.RepoSequence;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-
-/** Create group sequence in NoteDb */
-public class Schema_163 extends ReviewDbSchemaVersion {
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-
-  @Inject
-  Schema_163(
-      Provider<Schema_162> prior, GitRepositoryManager repoManager, AllUsersName allUsersName) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    @SuppressWarnings("deprecation")
-    RepoSequence.Seed groupSeed = db::nextAccountGroupId;
-    RepoSequence groupSeq =
-        new RepoSequence(
-            repoManager,
-            GitReferenceUpdated.DISABLED,
-            allUsersName,
-            Sequences.NAME_GROUPS,
-            groupSeed,
-            1);
-
-    // consume one account ID to ensure that the group sequence is initialized in NoteDb
-    groupSeq.next();
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_164.java b/java/com/google/gerrit/server/schema/Schema_164.java
deleted file mode 100644
index b6c3118..0000000
--- a/java/com/google/gerrit/server/schema/Schema_164.java
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static com.google.gerrit.server.schema.AclUtil.grant;
-
-import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.SQLException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-/** Grant read on group branches */
-public class Schema_164 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG = "Grant read permissions on group branches";
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final SystemGroupBackend systemGroupBackend;
-  private final ProjectConfig.Factory projectConfigFactory;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_164(
-      Provider<Schema_163> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      SystemGroupBackend systemGroupBackend,
-      ProjectConfig.Factory projectConfigFactory,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.systemGroupBackend = systemGroupBackend;
-    this.projectConfigFactory = projectConfigFactory;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try (Repository git = repoManager.openRepository(allUsersName);
-        MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git)) {
-      md.getCommitBuilder().setAuthor(serverUser);
-      md.getCommitBuilder().setCommitter(serverUser);
-      md.setMessage(COMMIT_MSG);
-
-      ProjectConfig config = projectConfigFactory.read(md);
-      AccessSection groups = config.getAccessSection(RefNames.REFS_GROUPS + "*", true);
-      grant(
-          config,
-          groups,
-          Permission.READ,
-          false,
-          true,
-          systemGroupBackend.getGroup(REGISTERED_USERS));
-      config.commit(md);
-    } catch (IOException | ConfigInvalidException e) {
-      throw new OrmException("Failed to grant read permissions on group branches", e);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_165.java b/java/com/google/gerrit/server/schema/Schema_165.java
deleted file mode 100644
index d80c770..0000000
--- a/java/com/google/gerrit/server/schema/Schema_165.java
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-
-import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.common.data.PermissionRule.Action;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.project.RefPattern;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.SQLException;
-import java.util.Optional;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-/** Make default Label-Code-Review permission on user branches exclusive. */
-public class Schema_165 extends ReviewDbSchemaVersion {
-  private static final String COMMIT_MSG =
-      "Make default Label-Code-Review permission on user branches exclusive";
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final SystemGroupBackend systemGroupBackend;
-  private final ProjectConfig.Factory projectConfigFactory;
-  private final PersonIdent serverUser;
-
-  @Inject
-  Schema_165(
-      Provider<Schema_164> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      SystemGroupBackend systemGroupBackend,
-      ProjectConfig.Factory projectConfigFactory,
-      @GerritPersonIdent PersonIdent serverUser) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.systemGroupBackend = systemGroupBackend;
-    this.projectConfigFactory = projectConfigFactory;
-    this.serverUser = serverUser;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try (Repository git = repoManager.openRepository(allUsersName);
-        MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git)) {
-      ProjectConfig config = projectConfigFactory.read(md);
-      Optional<Permission> permission = findDefaultPermission(config);
-      if (!permission.isPresent()) {
-        // the default permission was not found, hence it cannot be fixed
-        return;
-      }
-
-      permission.get().setExclusiveGroup(true);
-
-      md.getCommitBuilder().setAuthor(serverUser);
-      md.getCommitBuilder().setCommitter(serverUser);
-      md.setMessage(COMMIT_MSG);
-      config.commit(md);
-    } catch (IOException | ConfigInvalidException e) {
-      throw new OrmException(
-          "Failed to make default Label-Code-Review permission on user branches exclusive", e);
-    }
-  }
-
-  /**
-   * Searches for the default "Label-Code-Review" permission on the user branch and returns it if it
-   * was found. If it was not found (e.g. because it was removed or modified) {@link
-   * Optional#empty()} is returned.
-   */
-  private Optional<Permission> findDefaultPermission(ProjectConfig config) {
-    AccessSection users =
-        config.getAccessSection(
-            RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}", false);
-    if (users == null) {
-      // default permission was removed
-      return Optional.empty();
-    }
-
-    Permission permission = users.getPermission(Permission.LABEL + "Code-Review", false);
-    return isDefaultPermissionUntouched(permission) ? Optional.of(permission) : Optional.empty();
-  }
-
-  /**
-   * Checks whether the given permission matches the default "Label-Code-Review" permission on the
-   * user branch that was initially setup by {@link AllUsersCreator}.
-   */
-  private boolean isDefaultPermissionUntouched(Permission permission) {
-    if (permission == null) {
-      // default permission was removed
-      return false;
-    } else if (permission.getExclusiveGroup()) {
-      // default permission was modified
-      return false;
-    }
-
-    if (permission.getRules().size() != 1) {
-      // default permission was modified
-      return false;
-    }
-
-    PermissionRule rule = permission.getRule(systemGroupBackend.getGroup(REGISTERED_USERS));
-    if (rule == null) {
-      // default permission was removed
-      return false;
-    }
-
-    if (rule.getAction() != Action.ALLOW
-        || rule.getForce()
-        || rule.getMin() != -2
-        || rule.getMax() != 2) {
-      // default permission was modified
-      return false;
-    }
-
-    return true;
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_166.java b/java/com/google/gerrit/server/schema/Schema_166.java
deleted file mode 100644
index 901df56..0000000
--- a/java/com/google/gerrit/server/schema/Schema_166.java
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (C) 2018 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.schema;
-
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.SQLException;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-
-/** Set HEAD for All-Users to refs/meta/config. */
-public class Schema_166 extends ReviewDbSchemaVersion {
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-
-  @Inject
-  Schema_166(
-      Provider<Schema_165> prior, GitRepositoryManager repoManager, AllUsersName allUsersName) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try (Repository git = repoManager.openRepository(allUsersName)) {
-      RefUpdate u = git.updateRef(Constants.HEAD);
-      u.link(RefNames.REFS_CONFIG);
-    } catch (IOException e) {
-      throw new OrmException(String.format("Failed to update HEAD for %s", allUsersName.get()), e);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_167.java b/java/com/google/gerrit/server/schema/Schema_167.java
deleted file mode 100644
index 44d89f9..0000000
--- a/java/com/google/gerrit/server/schema/Schema_167.java
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright (C) 2018 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.schema;
-
-import static com.google.gerrit.server.notedb.NoteDbTable.GROUPS;
-import static com.google.gerrit.server.notedb.NotesMigration.DISABLE_REVIEW_DB;
-import static com.google.gerrit.server.notedb.NotesMigration.SECTION_NOTE_DB;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.AccountConfig;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.GerritServerIdProvider;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.group.db.AuditLogFormatter;
-import com.google.gerrit.server.group.db.GroupNameNotes;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-/** Migrate groups from ReviewDb to NoteDb. */
-public class Schema_167 extends ReviewDbSchemaVersion {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private final GitRepositoryManager repoManager;
-  private final AllUsersName allUsersName;
-  private final Config gerritConfig;
-  private final SitePaths sitePaths;
-  private final PersonIdent serverIdent;
-  private final SystemGroupBackend systemGroupBackend;
-
-  @Inject
-  protected Schema_167(
-      Provider<Schema_166> prior,
-      GitRepositoryManager repoManager,
-      AllUsersName allUsersName,
-      @GerritServerConfig Config gerritConfig,
-      SitePaths sitePaths,
-      @GerritPersonIdent PersonIdent serverIdent,
-      SystemGroupBackend systemGroupBackend) {
-    super(prior);
-    this.repoManager = repoManager;
-    this.allUsersName = allUsersName;
-    this.gerritConfig = gerritConfig;
-    this.sitePaths = sitePaths;
-    this.serverIdent = serverIdent;
-    this.systemGroupBackend = systemGroupBackend;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    if (gerritConfig.getBoolean(SECTION_NOTE_DB, GROUPS.key(), DISABLE_REVIEW_DB, false)) {
-      // Groups in ReviewDb have already been disabled, nothing to do.
-      return;
-    }
-
-    try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
-      List<GroupReference> allGroupReferences = readGroupReferencesFromReviewDb(db);
-
-      BatchRefUpdate batchRefUpdate = allUsersRepo.getRefDatabase().newBatchUpdate();
-      writeAllGroupNamesToNoteDb(allUsersRepo, allGroupReferences, batchRefUpdate);
-
-      GroupRebuilder groupRebuilder = createGroupRebuilder(db, allUsersRepo);
-      for (GroupReference groupReference : allGroupReferences) {
-        migrateOneGroupToNoteDb(
-            db, allUsersRepo, groupRebuilder, groupReference.getUUID(), batchRefUpdate);
-      }
-
-      RefUpdateUtil.executeChecked(batchRefUpdate, allUsersRepo);
-    } catch (IOException | ConfigInvalidException e) {
-      throw new OrmException(
-          String.format("Failed to migrate groups to NoteDb for %s", allUsersName.get()), e);
-    }
-  }
-
-  private List<GroupReference> readGroupReferencesFromReviewDb(ReviewDb db) throws SQLException {
-    try (Statement stmt = ReviewDbWrapper.unwrapJbdcSchema(db).getConnection().createStatement();
-        ResultSet rs = stmt.executeQuery("SELECT group_uuid, name FROM account_groups")) {
-      List<GroupReference> allGroupReferences = new ArrayList<>();
-      while (rs.next()) {
-        AccountGroup.UUID groupUuid = new AccountGroup.UUID(rs.getString(1));
-        String groupName = rs.getString(2);
-        allGroupReferences.add(new GroupReference(groupUuid, groupName));
-      }
-      return allGroupReferences;
-    }
-  }
-
-  private void writeAllGroupNamesToNoteDb(
-      Repository allUsersRepo,
-      List<GroupReference> allGroupReferences,
-      BatchRefUpdate batchRefUpdate)
-      throws IOException {
-    try (ObjectInserter inserter = allUsersRepo.newObjectInserter()) {
-      GroupNameNotes.updateAllGroups(
-          allUsersRepo, inserter, batchRefUpdate, allGroupReferences, serverIdent);
-      inserter.flush();
-    }
-  }
-
-  private GroupRebuilder createGroupRebuilder(ReviewDb db, Repository allUsersRepo)
-      throws IOException, ConfigInvalidException {
-    AuditLogFormatter auditLogFormatter =
-        createAuditLogFormatter(db, allUsersRepo, gerritConfig, sitePaths);
-    return new GroupRebuilder(serverIdent, allUsersName, auditLogFormatter);
-  }
-
-  private AuditLogFormatter createAuditLogFormatter(
-      ReviewDb db, Repository allUsersRepo, Config gerritConfig, SitePaths sitePaths)
-      throws IOException, ConfigInvalidException {
-    String serverId = new GerritServerIdProvider(gerritConfig, sitePaths).get();
-    SimpleInMemoryAccountCache accountCache =
-        new SimpleInMemoryAccountCache(allUsersName, allUsersRepo);
-    SimpleInMemoryGroupCache groupCache = new SimpleInMemoryGroupCache(db);
-    return AuditLogFormatter.create(
-        accountCache::get,
-        uuid -> {
-          if (systemGroupBackend.handles(uuid)) {
-            return Optional.ofNullable(systemGroupBackend.get(uuid));
-          }
-          return groupCache.get(uuid);
-        },
-        serverId);
-  }
-
-  private static void migrateOneGroupToNoteDb(
-      ReviewDb db,
-      Repository allUsersRepo,
-      GroupRebuilder rebuilder,
-      AccountGroup.UUID uuid,
-      BatchRefUpdate batchRefUpdate)
-      throws ConfigInvalidException, IOException, OrmException {
-    GroupBundle reviewDbBundle = GroupBundle.Factory.fromReviewDb(db, uuid);
-    RefUpdateUtil.deleteChecked(allUsersRepo, RefNames.refsGroups(uuid));
-    rebuilder.rebuild(allUsersRepo, reviewDbBundle, batchRefUpdate);
-  }
-
-  // The regular account cache isn't available during init. -> Use a simple replacement which tries
-  // to load every account only once from disk.
-  private static class SimpleInMemoryAccountCache {
-    private final AllUsersName allUsersName;
-    private final Repository allUsersRepo;
-    private Map<Account.Id, Optional<Account>> accounts = new HashMap<>();
-
-    public SimpleInMemoryAccountCache(AllUsersName allUsersName, Repository allUsersRepo) {
-      this.allUsersName = allUsersName;
-      this.allUsersRepo = allUsersRepo;
-    }
-
-    public Optional<Account> get(Account.Id accountId) {
-      accounts.computeIfAbsent(accountId, this::load);
-      return accounts.get(accountId);
-    }
-
-    private Optional<Account> load(Account.Id accountId) {
-      try {
-        AccountConfig accountConfig =
-            new AccountConfig(accountId, allUsersName, allUsersRepo).load();
-        return accountConfig.getLoadedAccount();
-      } catch (IOException | ConfigInvalidException ignored) {
-        logger.atWarning().withCause(ignored).log(
-            "Failed to load account %s."
-                + " Cannot get account name for group audit log commit messages.",
-            accountId.get());
-        return Optional.empty();
-      }
-    }
-  }
-
-  // The regular GroupBackends (especially external GroupBackends) and our internal group cache
-  // aren't available during init. -> Use a simple replacement which tries to look up only internal
-  // groups and which loads every internal group only once from disc. (There's no way we can look up
-  // external groups during init. As we need those groups only for cosmetic aspects in
-  // AuditLogFormatter, it's safe to exclude them.)
-  private static class SimpleInMemoryGroupCache {
-    private final ReviewDb db;
-    private Map<AccountGroup.UUID, Optional<GroupDescription.Basic>> groups = new HashMap<>();
-
-    public SimpleInMemoryGroupCache(ReviewDb db) {
-      this.db = db;
-    }
-
-    public Optional<GroupDescription.Basic> get(AccountGroup.UUID groupUuid) {
-      groups.computeIfAbsent(groupUuid, this::load);
-      return groups.get(groupUuid);
-    }
-
-    private Optional<GroupDescription.Basic> load(AccountGroup.UUID groupUuid) {
-      if (!AccountGroup.isInternalGroup(groupUuid)) {
-        return Optional.empty();
-      }
-
-      List<GroupDescription.Basic> groupDescriptions = getGroupDescriptions(groupUuid);
-      if (groupDescriptions.size() == 1) {
-        return Optional.of(Iterables.getOnlyElement(groupDescriptions));
-      }
-      return Optional.empty();
-    }
-
-    private List<GroupDescription.Basic> getGroupDescriptions(AccountGroup.UUID groupUuid) {
-      try (Statement stmt = ReviewDbWrapper.unwrapJbdcSchema(db).getConnection().createStatement();
-          ResultSet rs =
-              stmt.executeQuery(
-                  "SELECT name FROM account_groups where group_uuid = '" + groupUuid + "'")) {
-        List<GroupDescription.Basic> groupDescriptions = new ArrayList<>();
-        while (rs.next()) {
-          String groupName = rs.getString(1);
-          groupDescriptions.add(toGroupDescription(groupUuid, groupName));
-        }
-        return groupDescriptions;
-      } catch (SQLException ignored) {
-        logger.atWarning().withCause(ignored).log(
-            "Failed to load group %s."
-                + " Cannot get group name for group audit log commit messages.",
-            groupUuid.get());
-        return ImmutableList.of();
-      }
-    }
-
-    private static GroupDescription.Basic toGroupDescription(
-        AccountGroup.UUID groupUuid, String groupName) {
-      return new GroupDescription.Basic() {
-        @Override
-        public AccountGroup.UUID getGroupUUID() {
-          return groupUuid;
-        }
-
-        @Override
-        public String getName() {
-          return groupName;
-        }
-
-        @Nullable
-        @Override
-        public String getEmailAddress() {
-          return null;
-        }
-
-        @Nullable
-        @Override
-        public String getUrl() {
-          return null;
-        }
-      };
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_169.java b/java/com/google/gerrit/server/schema/Schema_169.java
deleted file mode 100644
index 11601e4..0000000
--- a/java/com/google/gerrit/server/schema/Schema_169.java
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.CommentJsonMigrator;
-import com.google.gerrit.server.notedb.CommentJsonMigrator.ProjectMigrationResult;
-import com.google.gerrit.server.notedb.MutableNotesMigration;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.util.SortedSet;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.TextProgressMonitor;
-
-/** Migrate NoteDb inline comments to JSON format. */
-public class Schema_169 extends ReviewDbSchemaVersion {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-  private final CommentJsonMigrator migrator;
-  private final GitRepositoryManager repoManager;
-  private final NotesMigration notesMigration;
-
-  @Inject
-  Schema_169(
-      Provider<Schema_168> prior,
-      CommentJsonMigrator migrator,
-      GitRepositoryManager repoManager,
-      @GerritServerConfig Config config) {
-    super(prior);
-    this.migrator = migrator;
-    this.repoManager = repoManager;
-    this.notesMigration = MutableNotesMigration.fromConfig(config);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
-    migrateData(ui);
-  }
-
-  @VisibleForTesting
-  protected void migrateData(UpdateUI ui) throws OrmException {
-    //  If the migration hasn't started, no need to look for non-JSON
-    if (!notesMigration.commitChangeWrites()) {
-      return;
-    }
-
-    boolean ok = true;
-    ProgressMonitor pm = new TextProgressMonitor();
-    SortedSet<Project.NameKey> projects = repoManager.list();
-    pm.beginTask("Migrating projects", projects.size());
-    int skipped = 0;
-    for (Project.NameKey project : projects) {
-      try (Repository repo = repoManager.openRepository(project)) {
-        ProjectMigrationResult progress = migrator.migrateProject(project, repo, false);
-        skipped += progress.skipped;
-      } catch (IOException e) {
-        ok = false;
-        logger.atWarning().log("Error migrating project " + project, e);
-      }
-      pm.update(1);
-    }
-
-    pm.endTask();
-    ui.message(
-        "Skipped " + skipped + " project" + (skipped == 1 ? "" : "s") + " with no legacy comments");
-
-    if (!ok) {
-      throw new OrmException("Migration failed");
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_170.java b/java/com/google/gerrit/server/schema/Schema_170.java
deleted file mode 100644
index 6a86494..0000000
--- a/java/com/google/gerrit/server/schema/Schema_170.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2018 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_170 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_170(Provider<Schema_169> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_168.java b/java/com/google/gerrit/server/schema/Schema_180.java
similarity index 70%
copy from java/com/google/gerrit/server/schema/Schema_168.java
copy to java/com/google/gerrit/server/schema/Schema_180.java
index fff4049..4d16022 100644
--- a/java/com/google/gerrit/server/schema/Schema_168.java
+++ b/java/com/google/gerrit/server/schema/Schema_180.java
@@ -14,13 +14,14 @@
 
 package com.google.gerrit.server.schema;
 
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+public class Schema_180 implements NoteDbSchemaVersion {
+  @SuppressWarnings("unused")
+  Schema_180(Arguments args) {
+    // Do nothing.
+  }
 
-/** Drop group tables. */
-public class Schema_168 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_168(Provider<Schema_167> prior) {
-    super(prior);
+  @Override
+  public void upgrade(UpdateUI ui) {
+    // Do nothing; only used to populate the version ref, which is done by the caller.
   }
 }
diff --git a/java/com/google/gerrit/server/schema/Schema_83.java b/java/com/google/gerrit/server/schema/Schema_83.java
deleted file mode 100644
index 95b7e6f..0000000
--- a/java/com/google/gerrit/server/schema/Schema_83.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-
-public class Schema_83 extends ReviewDbSchemaVersion {
-
-  @Inject
-  Schema_83() {
-    super(
-        new Provider<ReviewDbSchemaVersion>() {
-          @Override
-          public ReviewDbSchemaVersion get() {
-            throw new ProvisionException("Upgrade first to 2.8 or 2.9");
-          }
-        });
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_84.java b/java/com/google/gerrit/server/schema/Schema_84.java
deleted file mode 100644
index 415b2e3..0000000
--- a/java/com/google/gerrit/server/schema/Schema_84.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_84 extends ReviewDbSchemaVersion {
-
-  @Inject
-  Schema_84(Provider<Schema_83> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_85.java b/java/com/google/gerrit/server/schema/Schema_85.java
deleted file mode 100644
index ee8fbdb..0000000
--- a/java/com/google/gerrit/server/schema/Schema_85.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_85 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_85(Provider<Schema_84> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_86.java b/java/com/google/gerrit/server/schema/Schema_86.java
deleted file mode 100644
index e468345..0000000
--- a/java/com/google/gerrit/server/schema/Schema_86.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_86 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_86(Provider<Schema_85> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_87.java b/java/com/google/gerrit/server/schema/Schema_87.java
deleted file mode 100644
index 79884ba..0000000
--- a/java/com/google/gerrit/server/schema/Schema_87.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.HashSet;
-import java.util.Optional;
-import java.util.Set;
-
-public class Schema_87 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_87(Provider<Schema_86> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try (PreparedStatement uuidRetrieval =
-            prepareStatement(db, "SELECT group_uuid FROM account_groups WHERE group_id = ?");
-        PreparedStatement groupDeletion =
-            prepareStatement(db, "DELETE FROM account_groups WHERE group_id = ?");
-        PreparedStatement groupNameDeletion =
-            prepareStatement(db, "DELETE FROM account_group_names WHERE group_id = ?")) {
-      for (AccountGroup.Id id : scanSystemGroups(db)) {
-        Optional<AccountGroup.UUID> groupUuid = getUuid(uuidRetrieval, id);
-        if (groupUuid.filter(SystemGroupBackend::isSystemGroup).isPresent()) {
-          groupDeletion.setInt(1, id.get());
-          groupDeletion.executeUpdate();
-
-          groupNameDeletion.setInt(1, id.get());
-          groupNameDeletion.executeUpdate();
-        }
-      }
-    }
-  }
-
-  private static Optional<AccountGroup.UUID> getUuid(
-      PreparedStatement uuidRetrieval, AccountGroup.Id id) throws SQLException {
-    uuidRetrieval.setInt(1, id.get());
-    try (ResultSet uuidResults = uuidRetrieval.executeQuery()) {
-      if (uuidResults.next()) {
-        Optional.of(new AccountGroup.UUID(uuidResults.getString(1)));
-      }
-    }
-    return Optional.empty();
-  }
-
-  private static Set<AccountGroup.Id> scanSystemGroups(ReviewDb db) throws SQLException {
-    try (Statement stmt = newStatement(db);
-        ResultSet rs =
-            stmt.executeQuery("SELECT group_id FROM account_groups WHERE group_type = 'SYSTEM'")) {
-      Set<AccountGroup.Id> ids = new HashSet<>();
-      while (rs.next()) {
-        ids.add(new AccountGroup.Id(rs.getInt(1)));
-      }
-      return ids;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_88.java b/java/com/google/gerrit/server/schema/Schema_88.java
deleted file mode 100644
index d5e9994..0000000
--- a/java/com/google/gerrit/server/schema/Schema_88.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_88 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_88(Provider<Schema_87> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_89.java b/java/com/google/gerrit/server/schema/Schema_89.java
deleted file mode 100644
index 3d352da..0000000
--- a/java/com/google/gerrit/server/schema/Schema_89.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.StatementExecutor;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-
-public class Schema_89 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_89(Provider<Schema_88> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    SqlDialect dialect = ((JdbcSchema) db).getDialect();
-    try (StatementExecutor e = newExecutor(db)) {
-      dialect.dropIndex(e, "patch_set_approvals", "patch_set_approvals_openByUser");
-      dialect.dropIndex(e, "patch_set_approvals", "patch_set_approvals_closedByU");
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_90.java b/java/com/google/gerrit/server/schema/Schema_90.java
deleted file mode 100644
index 3831f33..0000000
--- a/java/com/google/gerrit/server/schema/Schema_90.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-public class Schema_90 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_90(Provider<Schema_89> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
-    try (Statement stmt = newStatement(db)) {
-      stmt.executeUpdate("UPDATE accounts set size_bar_in_change_table = 'Y'");
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_91.java b/java/com/google/gerrit/server/schema/Schema_91.java
deleted file mode 100644
index 6dd2d58..0000000
--- a/java/com/google/gerrit/server/schema/Schema_91.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2013 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_91 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_91(Provider<Schema_90> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_92.java b/java/com/google/gerrit/server/schema/Schema_92.java
deleted file mode 100644
index 9af33c0..0000000
--- a/java/com/google/gerrit/server/schema/Schema_92.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_92 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_92(Provider<Schema_91> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_93.java b/java/com/google/gerrit/server/schema/Schema_93.java
deleted file mode 100644
index e9a6691..0000000
--- a/java/com/google/gerrit/server/schema/Schema_93.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_93 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_93(Provider<Schema_92> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_94.java b/java/com/google/gerrit/server/schema/Schema_94.java
deleted file mode 100644
index 1551650..0000000
--- a/java/com/google/gerrit/server/schema/Schema_94.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-public class Schema_94 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_94(Provider<Schema_93> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
-    try (Statement stmt = newStatement(db)) {
-      stmt.execute("CREATE INDEX patch_sets_byRevision ON patch_sets (revision)");
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_95.java b/java/com/google/gerrit/server/schema/Schema_95.java
deleted file mode 100644
index 19dfa97..0000000
--- a/java/com/google/gerrit/server/schema/Schema_95.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.sql.SQLException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-
-public class Schema_95 extends ReviewDbSchemaVersion {
-  private final AllUsersCreator allUsersCreator;
-
-  @Inject
-  Schema_95(Provider<Schema_94> prior, AllUsersCreator allUsersCreator) {
-    super(prior);
-    this.allUsersCreator = allUsersCreator;
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
-    try {
-      allUsersCreator.create();
-    } catch (IOException | ConfigInvalidException e) {
-      throw new OrmException(e);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_96.java b/java/com/google/gerrit/server/schema/Schema_96.java
deleted file mode 100644
index 2eb9a87..0000000
--- a/java/com/google/gerrit/server/schema/Schema_96.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_96 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_96(Provider<Schema_95> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_97.java b/java/com/google/gerrit/server/schema/Schema_97.java
deleted file mode 100644
index 98f548e..0000000
--- a/java/com/google/gerrit/server/schema/Schema_97.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_97 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_97(Provider<Schema_96> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_98.java b/java/com/google/gerrit/server/schema/Schema_98.java
deleted file mode 100644
index 8a7498f..0000000
--- a/java/com/google/gerrit/server/schema/Schema_98.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-public class Schema_98 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_98(Provider<Schema_97> prior) {
-    super(prior);
-  }
-
-  @Override
-  protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
-    ui.message("Migrate user preference showUserInReview to reviewCategoryStrategy");
-    try (Statement stmt = newStatement(db)) {
-      stmt.executeUpdate(
-          "UPDATE accounts SET "
-              + "REVIEW_CATEGORY_STRATEGY='NAME' "
-              + "WHERE (SHOW_USER_IN_REVIEW='Y')");
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_99.java b/java/com/google/gerrit/server/schema/Schema_99.java
deleted file mode 100644
index ca3a959..0000000
--- a/java/com/google/gerrit/server/schema/Schema_99.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2014 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.schema;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-public class Schema_99 extends ReviewDbSchemaVersion {
-  @Inject
-  Schema_99(Provider<Schema_98> prior) {
-    super(prior);
-  }
-}
diff --git a/java/com/google/gerrit/server/schema/ScriptRunner.java b/java/com/google/gerrit/server/schema/ScriptRunner.java
deleted file mode 100644
index f4cba98..0000000
--- a/java/com/google/gerrit/server/schema/ScriptRunner.java
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (C) 2009 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.schema;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.base.CharMatcher;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.schema.sql.SqlDialect;
-import com.google.gwtorm.server.OrmException;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.List;
-
-/** Parses an SQL script from a resource file and later runs it. */
-class ScriptRunner {
-  private final String name;
-  private final List<String> commands;
-
-  static final ScriptRunner NOOP =
-      new ScriptRunner(null, null) {
-        @Override
-        void run(ReviewDb db) {}
-      };
-
-  ScriptRunner(String scriptName, InputStream script) {
-    this.name = scriptName;
-    try {
-      this.commands = script != null ? parse(script) : null;
-    } catch (IOException e) {
-      throw new IllegalStateException("Cannot parse " + name, e);
-    }
-  }
-
-  void run(ReviewDb db) throws OrmException {
-    try {
-      final JdbcSchema schema = (JdbcSchema) db;
-      final Connection c = schema.getConnection();
-      final SqlDialect dialect = schema.getDialect();
-      try (Statement stmt = c.createStatement()) {
-        for (String sql : commands) {
-          try {
-            if (!dialect.isStatementDelimiterSupported()) {
-              sql = CharMatcher.is(';').trimTrailingFrom(sql);
-            }
-            stmt.execute(sql);
-          } catch (SQLException e) {
-            throw new OrmException("Error in " + name + ":\n" + sql, e);
-          }
-        }
-      }
-    } catch (SQLException e) {
-      throw new OrmException("Cannot run statements for " + name, e);
-    }
-  }
-
-  private List<String> parse(InputStream in) throws IOException {
-    try (BufferedReader br = new BufferedReader(new InputStreamReader(in, UTF_8))) {
-      String delimiter = ";";
-      List<String> commands = new ArrayList<>();
-      StringBuilder buffer = new StringBuilder();
-      String line;
-      while ((line = br.readLine()) != null) {
-        if (line.isEmpty()) {
-          continue;
-        }
-        if (line.startsWith("--")) {
-          continue;
-        }
-
-        if (buffer.length() == 0 && line.toLowerCase().startsWith("delimiter ")) {
-          delimiter = line.substring("delimiter ".length()).trim();
-          continue;
-        }
-
-        if (buffer.length() > 0) {
-          buffer.append('\n');
-        }
-        buffer.append(line);
-
-        if (isDone(delimiter, line, buffer)) {
-          String cmd = buffer.toString();
-          commands.add(cmd);
-          buffer = new StringBuilder();
-        }
-      }
-      if (buffer.length() > 0) {
-        commands.add(buffer.toString());
-      }
-      return commands;
-    }
-  }
-
-  private boolean isDone(String delimiter, String line, StringBuilder buffer) {
-    if (";".equals(delimiter)) {
-      return buffer.charAt(buffer.length() - 1) == ';';
-
-    } else if (line.equals(delimiter)) {
-      buffer.setLength(buffer.length() - delimiter.length());
-      return true;
-
-    } else {
-      return false;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/submit/CherryPick.java b/java/com/google/gerrit/server/submit/CherryPick.java
index 182c22a..844b3d4 100644
--- a/java/com/google/gerrit/server/submit/CherryPick.java
+++ b/java/com/google/gerrit/server/submit/CherryPick.java
@@ -157,10 +157,9 @@
               String.format(
                   "no new commit produced by CherryPick of %s, expected to fail fast",
                   toMerge.change().getId()));
-      PatchSet prevPs = args.psUtil.current(ctx.getDb(), ctx.getNotes());
+      PatchSet prevPs = args.psUtil.current(ctx.getNotes());
       PatchSet newPs =
           args.psUtil.insert(
-              ctx.getDb(),
               ctx.getRevWalk(),
               ctx.getUpdate(psId),
               psId,
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 8e018a5..81e2661 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -911,7 +911,7 @@
               cd.getId(),
               new BatchUpdateOp() {
                 @Override
-                public boolean updateChange(ChangeContext ctx) throws OrmException {
+                public boolean updateChange(ChangeContext ctx) {
                   Change change = ctx.getChange();
                   if (!change.getStatus().isOpen()) {
                     return false;
@@ -926,8 +926,7 @@
                           change.getLastUpdatedOn(),
                           ChangeMessagesUtil.TAG_MERGED,
                           "Project was deleted.");
-                  cmUtil.addChangeMessage(
-                      ctx.getDb(), ctx.getUpdate(change.currentPatchSetId()), msg);
+                  cmUtil.addChangeMessage(ctx.getUpdate(change.currentPatchSetId()), msg);
 
                   return true;
                 }
diff --git a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
index cf3a44e..144b5b1 100644
--- a/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
+++ b/java/com/google/gerrit/server/submit/RebaseSubmitStrategy.java
@@ -170,7 +170,7 @@
         ctx.addRefUpdate(ObjectId.zeroId(), newCommit, newPatchSetId.toRefName());
       } else {
         // Stale read of patch set is ok; see comments in RebaseChangeOp.
-        PatchSet origPs = args.psUtil.get(ctx.getDb(), toMerge.getNotes(), toMerge.getPatchsetId());
+        PatchSet origPs = args.psUtil.get(toMerge.getNotes(), toMerge.getPatchsetId());
         rebaseOp =
             args.rebaseFactory
                 .create(toMerge.notes(), origPs, args.mergeTip.getCurrentTip())
@@ -227,10 +227,9 @@
         newPs = rebaseOp.getPatchSet();
       } else {
         // CherryPick
-        PatchSet prevPs = args.psUtil.current(ctx.getDb(), ctx.getNotes());
+        PatchSet prevPs = args.psUtil.current(ctx.getNotes());
         newPs =
             args.psUtil.insert(
-                ctx.getDb(),
                 ctx.getRevWalk(),
                 ctx.getUpdate(newPatchSetId),
                 newPatchSetId,
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index 3be4c31..9cd30db 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -32,7 +32,6 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeMessagesUtil;
@@ -251,7 +250,7 @@
         // during the submit strategy.
         mergedPatchSet =
             requireNonNull(
-                args.psUtil.get(ctx.getDb(), ctx.getNotes(), oldPsId),
+                args.psUtil.get(ctx.getNotes(), oldPsId),
                 () -> String.format("missing old patch set %s", oldPsId));
       } else {
         PatchSet.Id n = newPatchSet.getId();
@@ -301,12 +300,12 @@
       throws IOException, OrmException {
     PatchSet.Id psId = alreadyMergedCommit.getPatchsetId();
     logger.atFine().log("Fixing up already-merged patch set %s", psId);
-    PatchSet prevPs = args.psUtil.current(ctx.getDb(), ctx.getNotes());
+    PatchSet prevPs = args.psUtil.current(ctx.getNotes());
     ctx.getRevWalk().parseBody(alreadyMergedCommit);
     ctx.getChange()
         .setCurrentPatchSet(
             psId, alreadyMergedCommit.getShortMessage(), ctx.getChange().getOriginalSubject());
-    PatchSet existing = args.psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
+    PatchSet existing = args.psUtil.get(ctx.getNotes(), psId);
     if (existing != null) {
       logger.atFine().log("Patch set row exists, only updating change");
       return existing;
@@ -317,14 +316,7 @@
     List<String> groups =
         prevPs != null ? prevPs.getGroups() : GroupCollector.getDefaultGroups(alreadyMergedCommit);
     return args.psUtil.insert(
-        ctx.getDb(),
-        ctx.getRevWalk(),
-        ctx.getUpdate(psId),
-        psId,
-        alreadyMergedCommit,
-        groups,
-        null,
-        null);
+        ctx.getRevWalk(), ctx.getUpdate(psId), psId, alreadyMergedCommit, groups, null, null);
   }
 
   private void setApproval(ChangeContext ctx, IdentifiedUser user)
@@ -483,9 +475,8 @@
         psId, ctx.getUser(), ctx.getWhen(), body, ChangeMessagesUtil.TAG_MERGED);
   }
 
-  private void setMerged(ChangeContext ctx, ChangeMessage msg) throws OrmException {
+  private void setMerged(ChangeContext ctx, ChangeMessage msg) {
     Change c = ctx.getChange();
-    ReviewDb db = ctx.getDb();
     logger.atFine().log("Setting change %s merged", c.getId());
     c.setStatus(Change.Status.MERGED);
     c.setSubmissionId(args.submissionId.toStringForStorage());
@@ -494,7 +485,7 @@
     // which is not the user from the update context. addMergedMessage was able
     // to do this in the past.
     if (msg != null) {
-      args.cmUtil.addChangeMessage(db, ctx.getUpdate(msg.getPatchSetId()), msg);
+      args.cmUtil.addChangeMessage(ctx.getUpdate(msg.getPatchSetId()), msg);
     }
   }
 
diff --git a/java/com/google/gerrit/server/update/BatchUpdate.java b/java/com/google/gerrit/server/update/BatchUpdate.java
index b3472d2..a768888 100644
--- a/java/com/google/gerrit/server/update/BatchUpdate.java
+++ b/java/com/google/gerrit/server/update/BatchUpdate.java
@@ -39,7 +39,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.validators.OnSubmitValidators;
 import com.google.gerrit.server.logging.RequestId;
-import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.NoSuchProjectException;
@@ -93,7 +92,6 @@
     return new FactoryModule() {
       @Override
       public void configure() {
-        factory(ReviewDbBatchUpdate.AssistedFactory.class);
         factory(NoteDbBatchUpdate.AssistedFactory.class);
       }
     };
@@ -101,27 +99,17 @@
 
   @Singleton
   public static class Factory {
-    private final NotesMigration migration;
-    private final ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory;
     private final NoteDbBatchUpdate.AssistedFactory noteDbBatchUpdateFactory;
 
     // TODO(dborowitz): Make this non-injectable to force all callers to use RetryHelper.
     @Inject
-    Factory(
-        NotesMigration migration,
-        ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory,
-        NoteDbBatchUpdate.AssistedFactory noteDbBatchUpdateFactory) {
-      this.migration = migration;
-      this.reviewDbBatchUpdateFactory = reviewDbBatchUpdateFactory;
+    Factory(NoteDbBatchUpdate.AssistedFactory noteDbBatchUpdateFactory) {
       this.noteDbBatchUpdateFactory = noteDbBatchUpdateFactory;
     }
 
     public BatchUpdate create(
         ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when) {
-      if (migration.disableChangeReviewDb()) {
-        return noteDbBatchUpdateFactory.create(db, project, user, when);
-      }
-      return reviewDbBatchUpdateFactory.create(db, project, user, when);
+      return noteDbBatchUpdateFactory.create(db, project, user, when);
     }
 
     @SuppressWarnings({"rawtypes", "unchecked"})
@@ -135,15 +123,9 @@
       // method above, which always returns instances of the type we expect. Just to be safe,
       // copy them into an ImmutableList so there is no chance the callee can pollute the input
       // collection.
-      if (migration.disableChangeReviewDb()) {
-        ImmutableList<NoteDbBatchUpdate> noteDbUpdates =
-            (ImmutableList) ImmutableList.copyOf(updates);
-        NoteDbBatchUpdate.execute(noteDbUpdates, listener, dryRun);
-      } else {
-        ImmutableList<ReviewDbBatchUpdate> reviewDbUpdates =
-            (ImmutableList) ImmutableList.copyOf(updates);
-        ReviewDbBatchUpdate.execute(reviewDbUpdates, listener, dryRun);
-      }
+      ImmutableList<NoteDbBatchUpdate> noteDbUpdates =
+          (ImmutableList) ImmutableList.copyOf(updates);
+      NoteDbBatchUpdate.execute(noteDbUpdates, listener, dryRun);
     }
 
     private static void checkDifferentProject(Collection<BatchUpdate> updates) {
@@ -174,25 +156,6 @@
     return o;
   }
 
-  static boolean getUpdateChangesInParallel(Collection<? extends BatchUpdate> updates) {
-    checkArgument(!updates.isEmpty());
-    Boolean p = null;
-    for (BatchUpdate u : updates) {
-      if (p == null) {
-        p = u.updateChangesInParallel;
-      } else if (u.updateChangesInParallel != p) {
-        throw new IllegalArgumentException("cannot mix parallel and non-parallel operations");
-      }
-    }
-    // Properly implementing this would involve hoisting the parallel loop up
-    // even further. As of this writing, the only user is ReceiveCommits,
-    // which only executes a single BatchUpdate at a time. So bail for now.
-    checkArgument(
-        !p || updates.size() <= 1,
-        "cannot execute ChangeOps in parallel with more than 1 BatchUpdate");
-    return p;
-  }
-
   static void wrapAndThrowException(Exception e) throws UpdateException, RestApiException {
     Throwables.throwIfUnchecked(e);
 
@@ -234,8 +197,6 @@
   protected PushCertificate pushCert;
   protected String refLogMessage;
 
-  private boolean updateChangesInParallel;
-
   protected BatchUpdate(
       GitRepositoryManager repoManager,
       PersonIdent serverIdent,
@@ -296,18 +257,6 @@
     return this;
   }
 
-  /**
-   * Execute {@link BatchUpdateOp#updateChange(ChangeContext)} in parallel for each change.
-   *
-   * <p>This improves performance of writing to multiple changes in separate ReviewDb transactions.
-   * When only NoteDb is used, updates to all changes are written in a single batch ref update, so
-   * parallelization is not used and this option is ignored.
-   */
-  public BatchUpdate updateChangesInParallel() {
-    this.updateChangesInParallel = true;
-    return this;
-  }
-
   protected void initRepository() throws IOException {
     if (repoView == null) {
       repoView = new RepoView(repoManager, project);
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index c8d338b..9bdf293 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -41,7 +41,6 @@
 import com.google.gerrit.metrics.Histogram1;
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.time.Duration;
@@ -141,7 +140,6 @@
     return options().build();
   }
 
-  private final NotesMigration migration;
   private final Metrics metrics;
   private final BatchUpdate.Factory updateFactory;
   private final Map<ActionType, Duration> defaultTimeouts;
@@ -152,24 +150,18 @@
   RetryHelper(
       @GerritServerConfig Config cfg,
       Metrics metrics,
-      NotesMigration migration,
-      ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory,
       NoteDbBatchUpdate.AssistedFactory noteDbBatchUpdateFactory) {
-    this(cfg, metrics, migration, reviewDbBatchUpdateFactory, noteDbBatchUpdateFactory, null);
+    this(cfg, metrics, noteDbBatchUpdateFactory, null);
   }
 
   @VisibleForTesting
   public RetryHelper(
       @GerritServerConfig Config cfg,
       Metrics metrics,
-      NotesMigration migration,
-      ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory,
       NoteDbBatchUpdate.AssistedFactory noteDbBatchUpdateFactory,
       @Nullable Consumer<RetryerBuilder<?>> overwriteDefaultRetryerStrategySetup) {
     this.metrics = metrics;
-    this.migration = migration;
-    this.updateFactory =
-        new BatchUpdate.Factory(migration, reviewDbBatchUpdateFactory, noteDbBatchUpdateFactory);
+    this.updateFactory = new BatchUpdate.Factory(noteDbBatchUpdateFactory);
 
     Duration defaultTimeout =
         Duration.ofMillis(
@@ -229,16 +221,6 @@
   public <T> T execute(ChangeAction<T> changeAction, Options opts)
       throws RestApiException, UpdateException {
     try {
-      if (!migration.disableChangeReviewDb()) {
-        // Either we aren't full-NoteDb, or the underlying ref storage doesn't support atomic
-        // transactions. Either way, retrying a partially-failed operation is not idempotent, so
-        // don't do it automatically. Let the end user decide whether they want to retry.
-        return executeWithTimeoutCount(
-            ActionType.CHANGE_UPDATE,
-            () -> changeAction.call(updateFactory),
-            RetryerBuilder.<T>newBuilder().build());
-      }
-
       return execute(
           ActionType.CHANGE_UPDATE,
           () -> changeAction.call(updateFactory),
diff --git a/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java b/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java
deleted file mode 100644
index b859895..0000000
--- a/java/com/google/gerrit/server/update/ReviewDbBatchUpdate.java
+++ /dev/null
@@ -1,842 +0,0 @@
-// Copyright (C) 2015 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.update;
-
-import static com.google.common.base.Preconditions.checkState;
-import static java.util.Comparator.comparing;
-import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
-import static java.util.stream.Collectors.toList;
-
-import com.google.common.base.Stopwatch;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.flogger.FluentLogger;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.git.LockFailureException;
-import com.google.gerrit.metrics.Description;
-import com.google.gerrit.metrics.Description.Units;
-import com.google.gerrit.metrics.Field;
-import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.ChangeUpdateExecutor;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.InsertedObject;
-import com.google.gerrit.server.index.change.ChangeIndexer;
-import com.google.gerrit.server.logging.TraceContext;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.NoteDbChangeState;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager.MismatchedStateException;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TimeZone;
-import java.util.TreeMap;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-
-/**
- * {@link BatchUpdate} implementation that supports mixed ReviewDb/NoteDb operations, depending on
- * the migration state specified in {@link NotesMigration}.
- *
- * <p>When performing change updates in a mixed ReviewDb/NoteDb environment with ReviewDb primary,
- * the order of operations is very subtle:
- *
- * <ol>
- *   <li>Stage NoteDb updates to get the new NoteDb state, but do not write to the repo.
- *   <li>Write the new state in the Change entity, and commit this to ReviewDb.
- *   <li>Update NoteDb, ignoring any write failures.
- * </ol>
- *
- * The implementation in this class is well-tested, and it is strongly recommended that you not
- * attempt to reimplement this logic. Use {@code BatchUpdate} if at all possible.
- */
-public class ReviewDbBatchUpdate extends BatchUpdate {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  public interface AssistedFactory {
-    ReviewDbBatchUpdate create(
-        ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when);
-  }
-
-  class ContextImpl implements Context {
-    @Override
-    public RepoView getRepoView() throws IOException {
-      return ReviewDbBatchUpdate.this.getRepoView();
-    }
-
-    @Override
-    public RevWalk getRevWalk() throws IOException {
-      return getRepoView().getRevWalk();
-    }
-
-    @Override
-    public Project.NameKey getProject() {
-      return project;
-    }
-
-    @Override
-    public Timestamp getWhen() {
-      return when;
-    }
-
-    @Override
-    public TimeZone getTimeZone() {
-      return tz;
-    }
-
-    @Override
-    public ReviewDb getDb() {
-      return db;
-    }
-
-    @Override
-    public CurrentUser getUser() {
-      return user;
-    }
-
-    @Override
-    public Order getOrder() {
-      return order;
-    }
-  }
-
-  private class RepoContextImpl extends ContextImpl implements RepoContext {
-    @Override
-    public ObjectInserter getInserter() throws IOException {
-      return getRepoView().getInserterWrapper();
-    }
-
-    @Override
-    public void addRefUpdate(ReceiveCommand cmd) throws IOException {
-      initRepository();
-      repoView.getCommands().add(cmd);
-    }
-  }
-
-  private class ChangeContextImpl extends ContextImpl implements ChangeContext {
-    private final ChangeNotes notes;
-    private final Map<PatchSet.Id, ChangeUpdate> updates;
-    private final ReviewDbWrapper dbWrapper;
-    private final Repository threadLocalRepo;
-    private final RevWalk threadLocalRevWalk;
-
-    private boolean deleted;
-    private boolean bumpLastUpdatedOn = true;
-
-    protected ChangeContextImpl(
-        ChangeNotes notes, ReviewDbWrapper dbWrapper, Repository repo, RevWalk rw) {
-      this.notes = requireNonNull(notes);
-      this.dbWrapper = dbWrapper;
-      this.threadLocalRepo = repo;
-      this.threadLocalRevWalk = rw;
-      updates = new TreeMap<>(comparing(PatchSet.Id::get));
-    }
-
-    @Override
-    public ReviewDb getDb() {
-      requireNonNull(dbWrapper);
-      return dbWrapper;
-    }
-
-    @Override
-    public RevWalk getRevWalk() {
-      return threadLocalRevWalk;
-    }
-
-    @Override
-    public ChangeUpdate getUpdate(PatchSet.Id psId) {
-      ChangeUpdate u = updates.get(psId);
-      if (u == null) {
-        u = changeUpdateFactory.create(notes, user, when);
-        if (newChanges.containsKey(notes.getChangeId())) {
-          u.setAllowWriteToNewRef(true);
-        }
-        u.setPatchSetId(psId);
-        updates.put(psId, u);
-      }
-      return u;
-    }
-
-    @Override
-    public ChangeNotes getNotes() {
-      return notes;
-    }
-
-    @Override
-    public void dontBumpLastUpdatedOn() {
-      bumpLastUpdatedOn = false;
-    }
-
-    @Override
-    public void deleteChange() {
-      deleted = true;
-    }
-  }
-
-  @Singleton
-  private static class Metrics {
-    final Timer1<Boolean> executeChangeOpsLatency;
-
-    @Inject
-    Metrics(MetricMaker metricMaker) {
-      executeChangeOpsLatency =
-          metricMaker.newTimer(
-              "batch_update/execute_change_ops",
-              new Description("BatchUpdate change update latency, excluding reindexing")
-                  .setCumulative()
-                  .setUnit(Units.MILLISECONDS),
-              Field.ofBoolean("success"));
-    }
-  }
-
-  static void execute(
-      ImmutableList<ReviewDbBatchUpdate> updates, BatchUpdateListener listener, boolean dryrun)
-      throws UpdateException, RestApiException {
-    if (updates.isEmpty()) {
-      return;
-    }
-    try {
-      Order order = getOrder(updates, listener);
-      boolean updateChangesInParallel = getUpdateChangesInParallel(updates);
-      switch (order) {
-        case REPO_BEFORE_DB:
-          for (ReviewDbBatchUpdate u : updates) {
-            u.executeUpdateRepo();
-          }
-          listener.afterUpdateRepos();
-          for (ReviewDbBatchUpdate u : updates) {
-            u.executeRefUpdates(dryrun);
-          }
-          listener.afterUpdateRefs();
-          for (ReviewDbBatchUpdate u : updates) {
-            u.reindexChanges(u.executeChangeOps(updateChangesInParallel, dryrun));
-          }
-          listener.afterUpdateChanges();
-          break;
-        case DB_BEFORE_REPO:
-          for (ReviewDbBatchUpdate u : updates) {
-            u.reindexChanges(u.executeChangeOps(updateChangesInParallel, dryrun));
-          }
-          for (ReviewDbBatchUpdate u : updates) {
-            u.executeUpdateRepo();
-          }
-          for (ReviewDbBatchUpdate u : updates) {
-            u.executeRefUpdates(dryrun);
-          }
-          break;
-        default:
-          throw new IllegalStateException("invalid execution order: " + order);
-      }
-
-      ChangeIndexer.allAsList(
-              updates.stream().flatMap(u -> u.indexFutures.stream()).collect(toList()))
-          .get();
-
-      // Fire ref update events only after all mutations are finished, since callers may assume a
-      // patch set ref being created means the change was created, or a branch advancing meaning
-      // some changes were closed.
-      updates
-          .stream()
-          .filter(u -> u.batchRefUpdate != null)
-          .forEach(
-              u -> u.gitRefUpdated.fire(u.project, u.batchRefUpdate, u.getAccount().orElse(null)));
-
-      if (!dryrun) {
-        for (ReviewDbBatchUpdate u : updates) {
-          u.executePostOps();
-        }
-      }
-    } catch (Exception e) {
-      wrapAndThrowException(e);
-    }
-  }
-
-  private final AllUsersName allUsers;
-  private final ChangeIndexer indexer;
-  private final ChangeNotes.Factory changeNotesFactory;
-  private final ChangeUpdate.Factory changeUpdateFactory;
-  private final GitReferenceUpdated gitRefUpdated;
-  private final ListeningExecutorService changeUpdateExector;
-  private final Metrics metrics;
-  private final NoteDbUpdateManager.Factory updateManagerFactory;
-  private final NotesMigration notesMigration;
-  private final ReviewDb db;
-  private final SchemaFactory<ReviewDb> schemaFactory;
-  private final long skewMs;
-
-  @SuppressWarnings("deprecation")
-  private final List<com.google.common.util.concurrent.CheckedFuture<?, IOException>> indexFutures =
-      new ArrayList<>();
-
-  @Inject
-  ReviewDbBatchUpdate(
-      @GerritServerConfig Config cfg,
-      AllUsersName allUsers,
-      ChangeIndexer indexer,
-      ChangeNotes.Factory changeNotesFactory,
-      @ChangeUpdateExecutor ListeningExecutorService changeUpdateExector,
-      ChangeUpdate.Factory changeUpdateFactory,
-      @GerritPersonIdent PersonIdent serverIdent,
-      GitReferenceUpdated gitRefUpdated,
-      GitRepositoryManager repoManager,
-      Metrics metrics,
-      NoteDbUpdateManager.Factory updateManagerFactory,
-      NotesMigration notesMigration,
-      SchemaFactory<ReviewDb> schemaFactory,
-      @Assisted ReviewDb db,
-      @Assisted Project.NameKey project,
-      @Assisted CurrentUser user,
-      @Assisted Timestamp when) {
-    super(repoManager, serverIdent, project, user, when);
-    this.allUsers = allUsers;
-    this.changeNotesFactory = changeNotesFactory;
-    this.changeUpdateExector = changeUpdateExector;
-    this.changeUpdateFactory = changeUpdateFactory;
-    this.gitRefUpdated = gitRefUpdated;
-    this.indexer = indexer;
-    this.metrics = metrics;
-    this.notesMigration = notesMigration;
-    this.schemaFactory = schemaFactory;
-    this.updateManagerFactory = updateManagerFactory;
-    this.db = db;
-    skewMs = NoteDbChangeState.getReadOnlySkew(cfg);
-  }
-
-  @Override
-  public void execute(BatchUpdateListener listener) throws UpdateException, RestApiException {
-    execute(ImmutableList.of(this), listener, false);
-  }
-
-  @Override
-  protected Context newContext() {
-    return new ContextImpl();
-  }
-
-  private void executeUpdateRepo() throws UpdateException, RestApiException {
-    try {
-      logDebug("Executing updateRepo on %d ops", ops.size());
-      RepoContextImpl ctx = new RepoContextImpl();
-      for (BatchUpdateOp op : ops.values()) {
-        op.updateRepo(ctx);
-      }
-
-      logDebug("Executing updateRepo on %d RepoOnlyOps", repoOnlyOps.size());
-      for (RepoOnlyOp op : repoOnlyOps) {
-        op.updateRepo(ctx);
-      }
-
-      if (onSubmitValidators != null && !getRefUpdates().isEmpty()) {
-        // Validation of refs has to take place here and not at the beginning of executeRefUpdates.
-        // Otherwise, failing validation in a second BatchUpdate object will happen *after* the
-        // first update's executeRefUpdates has finished, hence after first repo's refs have been
-        // updated, which is too late.
-        onSubmitValidators.validate(
-            project, ctx.getRevWalk().getObjectReader(), repoView.getCommands());
-      }
-
-      if (repoView != null) {
-        logDebug("Flushing inserter");
-        repoView.getInserter().flush();
-      } else {
-        logDebug("No objects to flush");
-      }
-    } catch (Exception e) {
-      Throwables.throwIfInstanceOf(e, RestApiException.class);
-      throw new UpdateException(e);
-    }
-  }
-
-  private void executeRefUpdates(boolean dryrun) throws IOException, RestApiException {
-    if (getRefUpdates().isEmpty()) {
-      logDebug("No ref updates to execute");
-      return;
-    }
-    // May not be opened if the caller added ref updates but no new objects.
-    // TODO(dborowitz): Really?
-    initRepository();
-    batchRefUpdate = repoView.getRepository().getRefDatabase().newBatchUpdate();
-    batchRefUpdate.setPushCertificate(pushCert);
-    batchRefUpdate.setRefLogMessage(refLogMessage, true);
-    batchRefUpdate.setAllowNonFastForwards(true);
-    repoView.getCommands().addTo(batchRefUpdate);
-    if (user.isIdentifiedUser()) {
-      batchRefUpdate.setRefLogIdent(user.asIdentifiedUser().newRefLogIdent(when, tz));
-    }
-    logDebug("Executing batch of %d ref updates", batchRefUpdate.getCommands().size());
-    if (dryrun) {
-      return;
-    }
-
-    // Force BatchRefUpdate to read newly referenced objects using a new RevWalk, rather than one
-    // that might have access to unflushed objects.
-    try (RevWalk updateRw = new RevWalk(repoView.getRepository())) {
-      batchRefUpdate.execute(updateRw, NullProgressMonitor.INSTANCE);
-    }
-    boolean ok = true;
-    for (ReceiveCommand cmd : batchRefUpdate.getCommands()) {
-      if (cmd.getResult() != ReceiveCommand.Result.OK) {
-        ok = false;
-        break;
-      }
-    }
-    if (!ok) {
-      throw new RestApiException("BatchRefUpdate failed: " + batchRefUpdate);
-    }
-  }
-
-  private List<ChangeTask> executeChangeOps(boolean parallel, boolean dryrun)
-      throws UpdateException, RestApiException {
-    List<ChangeTask> tasks;
-    boolean success = false;
-    Stopwatch sw = Stopwatch.createStarted();
-    try {
-      logDebug("Executing change ops (parallel? %s)", parallel);
-      ListeningExecutorService executor =
-          parallel ? changeUpdateExector : MoreExecutors.newDirectExecutorService();
-
-      tasks = new ArrayList<>(ops.keySet().size());
-      try {
-        if (notesMigration.commitChangeWrites() && repoView != null) {
-          // A NoteDb change may have been rebuilt since the repo was originally
-          // opened, so make sure we see that.
-          logDebug("Preemptively scanning for repo changes");
-          repoView.getRepository().scanForRepoChanges();
-        }
-        if (!ops.isEmpty() && notesMigration.failChangeWrites()) {
-          // Fail fast before attempting any writes if changes are read-only, as
-          // this is a programmer error.
-          logDebug("Failing early due to read-only Changes table");
-          throw new OrmException(NoteDbUpdateManager.CHANGES_READ_ONLY);
-        }
-        List<ListenableFuture<?>> futures = new ArrayList<>(ops.keySet().size());
-        for (Map.Entry<Change.Id, Collection<BatchUpdateOp>> e : ops.asMap().entrySet()) {
-          ChangeTask task =
-              new ChangeTask(e.getKey(), e.getValue(), Thread.currentThread(), dryrun);
-          tasks.add(task);
-          if (!parallel) {
-            logDebug("Direct execution of task for ops: %s", ops);
-          }
-          futures.add(executor.submit(task));
-        }
-        if (parallel) {
-          logDebug(
-              "Waiting on futures for %d ops spanning %d changes", ops.size(), ops.keySet().size());
-        }
-        Futures.allAsList(futures).get();
-
-        if (notesMigration.commitChangeWrites()) {
-          if (!dryrun) {
-            executeNoteDbUpdates(tasks);
-          }
-        }
-        success = true;
-      } catch (ExecutionException | InterruptedException e) {
-        Throwables.throwIfInstanceOf(e.getCause(), UpdateException.class);
-        Throwables.throwIfInstanceOf(e.getCause(), RestApiException.class);
-        throw new UpdateException(e);
-      } catch (OrmException | IOException e) {
-        throw new UpdateException(e);
-      }
-    } finally {
-      metrics.executeChangeOpsLatency.record(success, sw.elapsed(NANOSECONDS), NANOSECONDS);
-    }
-    return tasks;
-  }
-
-  private void reindexChanges(List<ChangeTask> tasks) {
-    // Reindex changes.
-    for (ChangeTask task : tasks) {
-      if (task.deleted) {
-        indexFutures.add(indexer.deleteAsync(task.id));
-      } else if (task.dirty) {
-        indexFutures.add(indexer.indexAsync(project, task.id));
-      }
-    }
-  }
-
-  private void executeNoteDbUpdates(List<ChangeTask> tasks)
-      throws ResourceConflictException, IOException {
-    // Aggregate together all NoteDb ref updates from the ops we executed,
-    // possibly in parallel. Each task had its own NoteDbUpdateManager instance
-    // with its own thread-local copy of the repo(s), but each of those was just
-    // used for staging updates and was never executed.
-    //
-    // Use a new BatchRefUpdate as the original batchRefUpdate field is intended
-    // for use only by the updateRepo phase.
-    //
-    // See the comments in NoteDbUpdateManager#execute() for why we execute the
-    // updates on the change repo first.
-    logDebug("Executing NoteDb updates for %d changes", tasks.size());
-    try {
-      initRepository();
-      BatchRefUpdate changeRefUpdate = repoView.getRepository().getRefDatabase().newBatchUpdate();
-      boolean hasAllUsersCommands = false;
-      try (ObjectInserter ins = repoView.getRepository().newObjectInserter()) {
-        int objs = 0;
-        for (ChangeTask task : tasks) {
-          if (task.noteDbResult == null) {
-            logDebug("No-op update to %s", task.id);
-            continue;
-          }
-          for (ReceiveCommand cmd : task.noteDbResult.changeCommands()) {
-            changeRefUpdate.addCommand(cmd);
-          }
-          for (InsertedObject obj : task.noteDbResult.changeObjects()) {
-            objs++;
-            ins.insert(obj.type(), obj.data().toByteArray());
-          }
-          hasAllUsersCommands |= !task.noteDbResult.allUsersCommands().isEmpty();
-        }
-        logDebug(
-            "Collected %d objects and %d ref updates to change repo",
-            objs, changeRefUpdate.getCommands().size());
-        executeNoteDbUpdate(getRevWalk(), ins, changeRefUpdate);
-      }
-
-      if (hasAllUsersCommands) {
-        try (Repository allUsersRepo = repoManager.openRepository(allUsers);
-            RevWalk allUsersRw = new RevWalk(allUsersRepo);
-            ObjectInserter allUsersIns = allUsersRepo.newObjectInserter()) {
-          int objs = 0;
-          BatchRefUpdate allUsersRefUpdate = allUsersRepo.getRefDatabase().newBatchUpdate();
-          for (ChangeTask task : tasks) {
-            for (ReceiveCommand cmd : task.noteDbResult.allUsersCommands()) {
-              allUsersRefUpdate.addCommand(cmd);
-            }
-            for (InsertedObject obj : task.noteDbResult.allUsersObjects()) {
-              allUsersIns.insert(obj.type(), obj.data().toByteArray());
-            }
-          }
-          logDebug(
-              "Collected %d objects and %d ref updates to All-Users",
-              objs, allUsersRefUpdate.getCommands().size());
-          executeNoteDbUpdate(allUsersRw, allUsersIns, allUsersRefUpdate);
-        }
-      } else {
-        logDebug("No All-Users updates");
-      }
-    } catch (IOException e) {
-      if (tasks.stream().allMatch(t -> t.storage == PrimaryStorage.REVIEW_DB)) {
-        // Ignore all errors trying to update NoteDb at this point. We've already written the
-        // NoteDbChangeStates to ReviewDb, which means if any state is out of date it will be
-        // rebuilt the next time it is needed.
-        //
-        // Always log even without RequestId.
-        logger.atFine().withCause(e).log("Ignoring NoteDb update error after ReviewDb write");
-
-        // Otherwise, we can't prove it's safe to ignore the error, either because some change had
-        // NOTE_DB primary, or a task failed before determining the primary storage.
-      } else if (e instanceof LockFailureException) {
-        // LOCK_FAILURE is a special case indicating there was a conflicting write to a meta ref,
-        // although it happened too late for us to produce anything but a generic error message.
-        throw new ResourceConflictException("Updating change failed due to conflicting write", e);
-      }
-      throw e;
-    }
-  }
-
-  private void executeNoteDbUpdate(RevWalk rw, ObjectInserter ins, BatchRefUpdate bru)
-      throws IOException {
-    if (bru.getCommands().isEmpty()) {
-      logDebug("No commands, skipping flush and ref update");
-      return;
-    }
-    ins.flush();
-    bru.setAllowNonFastForwards(true);
-    bru.execute(rw, NullProgressMonitor.INSTANCE);
-    for (ReceiveCommand cmd : bru.getCommands()) {
-      // TODO(dborowitz): LOCK_FAILURE for NoteDb primary should be retried.
-      if (cmd.getResult() != ReceiveCommand.Result.OK) {
-        throw new IOException("Update failed: " + bru);
-      }
-    }
-  }
-
-  private class ChangeTask implements Callable<Void> {
-    final Change.Id id;
-    private final Collection<BatchUpdateOp> changeOps;
-    private final Thread mainThread;
-    private final boolean dryrun;
-
-    PrimaryStorage storage;
-    NoteDbUpdateManager.StagedResult noteDbResult;
-    boolean dirty;
-    boolean deleted;
-
-    private ChangeTask(
-        Change.Id id, Collection<BatchUpdateOp> changeOps, Thread mainThread, boolean dryrun) {
-      this.id = id;
-      this.changeOps = changeOps;
-      this.mainThread = mainThread;
-      this.dryrun = dryrun;
-    }
-
-    @Override
-    public Void call() throws Exception {
-      try (TraceContext traceContext =
-          TraceContext.open()
-              .addTag("TASK_ID", id.toString() + "-" + Thread.currentThread().getId())) {
-        if (Thread.currentThread() == mainThread) {
-          initRepository();
-          Repository repo = repoView.getRepository();
-          try (RevWalk rw = new RevWalk(repo)) {
-            call(ReviewDbBatchUpdate.this.db, repo, rw);
-          }
-        } else {
-          // Possible optimization: allow Ops to declare whether they need to
-          // access the repo from updateChange, and don't open in this thread
-          // unless we need it. However, as of this writing the only operations
-          // that are executed in parallel are during ReceiveCommits, and they
-          // all need the repo open anyway. (The non-parallel case above does not
-          // reopen the repo.)
-          try (ReviewDb threadLocalDb = schemaFactory.open();
-              Repository repo = repoManager.openRepository(project);
-              RevWalk rw = new RevWalk(repo)) {
-            call(threadLocalDb, repo, rw);
-          }
-        }
-        return null;
-      }
-    }
-
-    private void call(ReviewDb db, Repository repo, RevWalk rw) throws Exception {
-      @SuppressWarnings("resource") // Not always opened.
-      NoteDbUpdateManager updateManager = null;
-      try {
-        db.changes().beginTransaction(id);
-        try {
-          ChangeContextImpl ctx = newChangeContext(db, repo, rw, id);
-          NoteDbChangeState oldState = NoteDbChangeState.parse(ctx.getChange());
-          NoteDbChangeState.checkNotReadOnly(oldState, skewMs);
-
-          storage = PrimaryStorage.of(oldState);
-          if (storage == PrimaryStorage.NOTE_DB && !notesMigration.readChanges()) {
-            throw new OrmException("must have NoteDb enabled to update change " + id);
-          }
-
-          // Call updateChange on each op.
-          logDebug("Calling updateChange on %s ops", changeOps.size());
-          for (BatchUpdateOp op : changeOps) {
-            dirty |= op.updateChange(ctx);
-          }
-          if (!dirty) {
-            logDebug("No ops reported dirty, short-circuiting");
-            return;
-          }
-          deleted = ctx.deleted;
-          if (deleted) {
-            logDebug("Change was deleted");
-          }
-
-          // Stage the NoteDb update and store its state in the Change.
-          if (notesMigration.commitChangeWrites()) {
-            updateManager = stageNoteDbUpdate(ctx, deleted);
-          }
-
-          if (storage == PrimaryStorage.REVIEW_DB) {
-            // If primary storage of this change is in ReviewDb, bump
-            // lastUpdatedOn or rowVersion and commit. Otherwise, don't waste
-            // time updating ReviewDb at all.
-            Iterable<Change> cs = changesToUpdate(ctx);
-            if (isNewChange(id)) {
-              // Insert rather than upsert in case of a race on change IDs.
-              logDebug("Inserting change");
-              db.changes().insert(cs);
-            } else if (deleted) {
-              logDebug("Deleting change");
-              db.changes().delete(cs);
-            } else {
-              logDebug("Updating change");
-              db.changes().update(cs);
-            }
-            if (!dryrun) {
-              db.commit();
-            }
-          } else {
-            logDebug("Skipping ReviewDb write since primary storage is %s", storage);
-          }
-        } finally {
-          db.rollback();
-        }
-
-        // Do not execute the NoteDbUpdateManager, as we don't want too much
-        // contention on the underlying repo, and we would rather use a single
-        // ObjectInserter/BatchRefUpdate later.
-        //
-        // TODO(dborowitz): May or may not be worth trying to batch together
-        // flushed inserters as well.
-        if (storage == PrimaryStorage.NOTE_DB) {
-          // Should have failed above if NoteDb is disabled.
-          checkState(notesMigration.commitChangeWrites());
-          noteDbResult = updateManager.stage().get(id);
-        } else if (notesMigration.commitChangeWrites()) {
-          try {
-            noteDbResult = updateManager.stage().get(id);
-          } catch (IOException ex) {
-            // Ignore all errors trying to update NoteDb at this point. We've
-            // already written the NoteDbChangeState to ReviewDb, which means
-            // if the state is out of date it will be rebuilt the next time it
-            // is needed.
-            logger.atFine().withCause(ex).log("Ignoring NoteDb update error after ReviewDb write");
-          }
-        }
-      } catch (Exception e) {
-        logDebug("Error updating change (should be rethrown)", e);
-        Throwables.propagateIfPossible(e, RestApiException.class);
-        throw new UpdateException(e);
-      } finally {
-        if (updateManager != null) {
-          updateManager.close();
-        }
-      }
-    }
-
-    private ChangeContextImpl newChangeContext(
-        ReviewDb db, Repository repo, RevWalk rw, Change.Id id) throws OrmException {
-      Change c = newChanges.get(id);
-      boolean isNew = c != null;
-      if (isNew) {
-        // New change: populate noteDbState.
-        checkState(c.getNoteDbState() == null, "noteDbState should not be filled in by callers");
-        if (notesMigration.changePrimaryStorage() == PrimaryStorage.NOTE_DB) {
-          c.setNoteDbState(NoteDbChangeState.NOTE_DB_PRIMARY_STATE);
-        }
-      } else {
-        // Existing change.
-        c = ChangeNotes.readOneReviewDbChange(db, id);
-        if (c == null) {
-          // Not in ReviewDb, but new changes are created with default primary
-          // storage as NOTE_DB, so we can assume that a missing change is
-          // NoteDb primary. Pass a synthetic change into ChangeNotes.Factory,
-          // which lets ChangeNotes take care of the existence check.
-          //
-          // TODO(dborowitz): This assumption is potentially risky, because
-          // it means once we turn this option on and start creating changes
-          // without writing anything to ReviewDb, we can't turn this option
-          // back off without making those changes inaccessible. The problem
-          // is we have no way of distinguishing a change that only exists in
-          // NoteDb because it only ever existed in NoteDb, from a change that
-          // only exists in NoteDb because it used to exist in ReviewDb and
-          // deleting from ReviewDb succeeded but deleting from NoteDb failed.
-          //
-          // TODO(dborowitz): We actually still have that problem anyway. Maybe
-          // we need a cutoff timestamp? Or maybe we need to start leaving
-          // tombstones in ReviewDb?
-          c = ChangeNotes.Factory.newNoteDbOnlyChange(project, id);
-        }
-        NoteDbChangeState.checkNotReadOnly(c, skewMs);
-      }
-      ChangeNotes notes = changeNotesFactory.createForBatchUpdate(c, !isNew);
-      return new ChangeContextImpl(notes, new BatchUpdateReviewDb(db), repo, rw);
-    }
-
-    private NoteDbUpdateManager stageNoteDbUpdate(ChangeContextImpl ctx, boolean deleted)
-        throws OrmException, IOException {
-      logDebug("Staging NoteDb update");
-      NoteDbUpdateManager updateManager =
-          updateManagerFactory
-              .create(ctx.getProject())
-              .setChangeRepo(
-                  ctx.threadLocalRepo,
-                  ctx.threadLocalRevWalk,
-                  null,
-                  new ChainedReceiveCommands(ctx.threadLocalRepo));
-      if (ctx.getUser().isIdentifiedUser()) {
-        updateManager.setRefLogIdent(
-            ctx.getUser().asIdentifiedUser().newRefLogIdent(ctx.getWhen(), tz));
-      }
-      for (ChangeUpdate u : ctx.updates.values()) {
-        updateManager.add(u);
-      }
-
-      Change c = ctx.getChange();
-      if (deleted) {
-        updateManager.deleteChange(c.getId());
-      }
-      try {
-        updateManager.stageAndApplyDelta(c);
-      } catch (MismatchedStateException ex) {
-        // Refused to apply update because NoteDb was out of sync, which can
-        // only happen if ReviewDb is the primary storage for this change.
-        //
-        // Go ahead with this ReviewDb update; it's still out of sync, but this
-        // is no worse than before, and it will eventually get rebuilt.
-        logDebug("Ignoring MismatchedStateException while staging");
-      }
-
-      return updateManager;
-    }
-
-    private boolean isNewChange(Change.Id id) {
-      return newChanges.containsKey(id);
-    }
-  }
-
-  private static Iterable<Change> changesToUpdate(ChangeContextImpl ctx) {
-    Change c = ctx.getChange();
-    if (ctx.bumpLastUpdatedOn && c.getLastUpdatedOn().before(ctx.getWhen())) {
-      c.setLastUpdatedOn(ctx.getWhen());
-    }
-    return Collections.singleton(c);
-  }
-
-  private void executePostOps() throws Exception {
-    ContextImpl ctx = new ContextImpl();
-    for (BatchUpdateOp op : ops.values()) {
-      op.postUpdate(ctx);
-    }
-
-    for (RepoOnlyOp op : repoOnlyOps) {
-      op.postUpdate(ctx);
-    }
-  }
-}
diff --git a/java/com/google/gerrit/server/util/MagicBranch.java b/java/com/google/gerrit/server/util/MagicBranch.java
index a049169..4e41be0 100644
--- a/java/com/google/gerrit/server/util/MagicBranch.java
+++ b/java/com/google/gerrit/server/util/MagicBranch.java
@@ -28,25 +28,19 @@
   public static final String NEW_CHANGE = "refs/for/";
   // TODO(xchangcheng): remove after 'repo' supports private/wip changes.
   public static final String NEW_DRAFT_CHANGE = "refs/drafts/";
-  // TODO(xchangcheng): remove after migrating tools which are using this magic branch.
-  public static final String NEW_PUBLISH_CHANGE = "refs/publish/";
 
   /** Extracts the destination from a ref name */
   public static String getDestBranchName(String refName) {
     String magicBranch = NEW_CHANGE;
     if (refName.startsWith(NEW_DRAFT_CHANGE)) {
       magicBranch = NEW_DRAFT_CHANGE;
-    } else if (refName.startsWith(NEW_PUBLISH_CHANGE)) {
-      magicBranch = NEW_PUBLISH_CHANGE;
     }
     return refName.substring(magicBranch.length());
   }
 
   /** Checks if the supplied ref name is a magic branch */
   public static boolean isMagicBranch(String refName) {
-    return refName.startsWith(NEW_DRAFT_CHANGE)
-        || refName.startsWith(NEW_PUBLISH_CHANGE)
-        || refName.startsWith(NEW_CHANGE);
+    return refName.startsWith(NEW_DRAFT_CHANGE) || refName.startsWith(NEW_CHANGE);
   }
 
   /** Returns the ref name prefix for a magic branch, {@code null} if the branch is not magic */
@@ -54,9 +48,6 @@
     if (refName.startsWith(NEW_DRAFT_CHANGE)) {
       return NEW_DRAFT_CHANGE;
     }
-    if (refName.startsWith(NEW_PUBLISH_CHANGE)) {
-      return NEW_PUBLISH_CHANGE;
-    }
     if (refName.startsWith(NEW_CHANGE)) {
       return NEW_CHANGE;
     }
@@ -80,11 +71,6 @@
     if (result != Capable.OK) {
       return result;
     }
-    result = checkMagicBranchRef(NEW_PUBLISH_CHANGE, repo, project);
-    if (result != Capable.OK) {
-      return result;
-    }
-
     return Capable.OK;
   }
 
diff --git a/java/com/google/gerrit/sshd/commands/AdminQueryShell.java b/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
deleted file mode 100644
index c520e79..0000000
--- a/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2009 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.sshd.commands;
-
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.permissions.GlobalPermission;
-import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.sshd.AdminHighPriorityCommand;
-import com.google.gerrit.sshd.CommandMetaData;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.inject.Inject;
-import org.kohsuke.args4j.Option;
-
-/** Opens a query processor. */
-@AdminHighPriorityCommand
-@RequiresCapability(GlobalCapability.ACCESS_DATABASE)
-@CommandMetaData(name = "gsql", description = "Administrative interface to active database")
-final class AdminQueryShell extends SshCommand {
-  @Inject private PermissionBackend permissionBackend;
-  @Inject private QueryShell.Factory factory;
-
-  @Option(name = "--format", usage = "Set output format")
-  private QueryShell.OutputFormat format = QueryShell.OutputFormat.PRETTY;
-
-  @Option(name = "-c", metaVar = "SQL QUERY", usage = "Query to execute")
-  private String query;
-
-  @Override
-  protected void run() throws Failure {
-    try {
-      permissionBackend.currentUser().check(GlobalPermission.ACCESS_DATABASE);
-    } catch (AuthException err) {
-      throw die(err.getMessage());
-    } catch (PermissionBackendException e) {
-      throw new Failure(1, "unavailable", e);
-    }
-
-    QueryShell shell = factory.create(in, out);
-    shell.setOutputFormat(format);
-    if (query != null) {
-      shell.execute(query);
-    } else {
-      shell.run();
-    }
-  }
-}
diff --git a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 1c857e4..87b6f02 100644
--- a/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -90,7 +90,6 @@
     command(gerrit, CreateGroupCommand.class);
     command(gerrit, CreateProjectCommand.class);
     command(gerrit, SetHeadCommand.class);
-    command(gerrit, AdminQueryShell.class);
 
     if (slaveMode) {
       command("git-receive-pack").to(ReceiveSlaveMode.class);
diff --git a/java/com/google/gerrit/sshd/commands/KillCommand.java b/java/com/google/gerrit/sshd/commands/KillCommand.java
index ef12f5f..df74f86 100644
--- a/java/com/google/gerrit/sshd/commands/KillCommand.java
+++ b/java/com/google/gerrit/sshd/commands/KillCommand.java
@@ -43,7 +43,7 @@
   @Inject private DeleteTask deleteTask;
 
   @Argument(index = 0, multiValued = true, required = true, metaVar = "ID")
-  private final List<String> taskIds = new ArrayList<>();
+  private List<String> taskIds = new ArrayList<>();
 
   @Override
   protected void run() {
diff --git a/java/com/google/gerrit/sshd/commands/PatchSetParser.java b/java/com/google/gerrit/sshd/commands/PatchSetParser.java
index 8dee69ee..a1e8f07 100644
--- a/java/com/google/gerrit/sshd/commands/PatchSetParser.java
+++ b/java/com/google/gerrit/sshd/commands/PatchSetParser.java
@@ -107,7 +107,7 @@
         throw error("\"" + token + "\" is not a valid patch set");
       }
       ChangeNotes notes = getNotes(projectState, patchSetId.getParentKey());
-      PatchSet patchSet = psUtil.get(db.get(), notes, patchSetId);
+      PatchSet patchSet = psUtil.get(notes, patchSetId);
       if (patchSet == null) {
         throw error("\"" + token + "\" no such patch set");
       }
diff --git a/java/com/google/gerrit/testing/DisabledReviewDb.java b/java/com/google/gerrit/testing/DisabledReviewDb.java
index 2bf95b0..e90474b 100644
--- a/java/com/google/gerrit/testing/DisabledReviewDb.java
+++ b/java/com/google/gerrit/testing/DisabledReviewDb.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.reviewdb.server.PatchSetAccess;
 import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.SchemaVersionAccess;
 import com.google.gwtorm.server.Access;
 import com.google.gwtorm.server.StatementExecutor;
 
@@ -65,11 +64,6 @@
   }
 
   @Override
-  public SchemaVersionAccess schemaVersion() {
-    throw new Disabled();
-  }
-
-  @Override
   public ChangeAccess changes() {
     throw new Disabled();
   }
@@ -95,16 +89,6 @@
   }
 
   @Override
-  public int nextAccountId() {
-    throw new Disabled();
-  }
-
-  @Override
-  public int nextAccountGroupId() {
-    throw new Disabled();
-  }
-
-  @Override
   public int nextChangeId() {
     throw new Disabled();
   }
diff --git a/java/com/google/gerrit/testing/FakeGroupAuditService.java b/java/com/google/gerrit/testing/FakeGroupAuditService.java
index 7c6674b..ddf03f5 100644
--- a/java/com/google/gerrit/testing/FakeGroupAuditService.java
+++ b/java/com/google/gerrit/testing/FakeGroupAuditService.java
@@ -35,8 +35,8 @@
 @Singleton
 public class FakeGroupAuditService implements GroupAuditService {
 
-  private final PluginSetContext<GroupAuditListener> groupAuditListeners;
-  private final PluginSetContext<AuditListener> auditListeners;
+  protected final PluginSetContext<GroupAuditListener> groupAuditListeners;
+  protected final PluginSetContext<AuditListener> auditListeners;
 
   public static class Module extends AbstractModule {
     @Override
@@ -63,7 +63,10 @@
 
   @Override
   public void dispatch(AuditEvent action) {
-    auditEvents.add(action);
+    synchronized (auditEvents) {
+      auditEvents.add(action);
+      auditEvents.notifyAll();
+    }
   }
 
   @Override
diff --git a/java/com/google/gerrit/testing/GerritBaseTests.java b/java/com/google/gerrit/testing/GerritBaseTests.java
index 01fb85d..d6a2261 100644
--- a/java/com/google/gerrit/testing/GerritBaseTests.java
+++ b/java/com/google/gerrit/testing/GerritBaseTests.java
@@ -15,8 +15,6 @@
 package com.google.gerrit.testing;
 
 import com.google.common.base.CharMatcher;
-import com.google.gwtorm.client.KeyUtil;
-import com.google.gwtorm.server.StandardKeyEncoder;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.rules.ExpectedException;
@@ -24,10 +22,6 @@
 
 @Ignore
 public abstract class GerritBaseTests {
-  static {
-    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
-  }
-
   @Rule public ExpectedException exception = ExpectedException.none();
   @Rule public final TestName testName = new TestName();
 
diff --git a/java/com/google/gerrit/testing/InMemoryDatabase.java b/java/com/google/gerrit/testing/InMemoryDatabase.java
index b489652..1a4b84e 100644
--- a/java/com/google/gerrit/testing/InMemoryDatabase.java
+++ b/java/com/google/gerrit/testing/InMemoryDatabase.java
@@ -14,60 +14,34 @@
 
 package com.google.gerrit.testing;
 
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.pgm.init.index.elasticsearch.ElasticIndexModuleOnInit;
 import com.google.gerrit.pgm.init.index.lucene.LuceneIndexModuleOnInit;
-import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
-import com.google.gerrit.server.schema.ReviewDbSchemaVersion;
-import com.google.gwtorm.jdbc.Database;
-import com.google.gwtorm.jdbc.SimpleDataSource;
+import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
 import java.io.IOException;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.Properties;
-import javax.sql.DataSource;
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 
-/**
- * An in-memory test instance of {@link ReviewDb} database.
- *
- * <p>Test classes should create one instance of this class for each unique test database they want
- * to use. When the tests needing this instance are complete, ensure that {@link
- * #drop(InMemoryDatabase)} is called to free the resources so the JVM running the unit tests
- * doesn't run out of heap space.
- */
+/** Husk of an in-memory ReviewDb implementation. */
+// TODO(dborowitz): Inline callers to get their own darn schemaCreator.
 public class InMemoryDatabase implements SchemaFactory<ReviewDb> {
-  public static InMemoryDatabase newDatabase(LifecycleManager lifecycle) {
-    Injector injector = Guice.createInjector(new InMemoryModule());
-    lifecycle.add(injector);
-    return injector.getInstance(InMemoryDatabase.class);
-  }
-
-  /** Drop the database from memory; does nothing if the instance was null. */
-  public static void drop(InMemoryDatabase db) {
-    if (db != null) {
-      db.dbInstance.drop();
-    }
-  }
-
-  private final ReviewDbSchemaCreator schemaCreator;
-  private final Instance dbInstance;
-
-  private boolean created;
+  private final GitRepositoryManager repoManager;
+  private final AllProjectsName allProjectsName;
+  private final SchemaCreator schemaCreator;
+  private final SchemaFactory<ReviewDb> schemaFactory;
 
   @Inject
-  InMemoryDatabase(Injector injector) throws OrmException {
+  InMemoryDatabase(Injector injector) {
     Injector childInjector =
         injector.createChildInjector(
             new AbstractModule() {
@@ -85,110 +59,40 @@
                 }
               }
             });
-    this.schemaCreator = childInjector.getInstance(ReviewDbSchemaCreator.class);
-    Instance dbInstanceFromInjector = childInjector.getInstance(Instance.class);
-    if (dbInstanceFromInjector != null) {
-      this.dbInstance = dbInstanceFromInjector;
-      this.created = true;
-    } else {
-      this.dbInstance = new Instance();
-    }
+    this.repoManager = childInjector.getInstance(GitRepositoryManager.class);
+    this.allProjectsName = childInjector.getInstance(AllProjectsName.class);
+    this.schemaCreator = childInjector.getInstance(SchemaCreator.class);
+    this.schemaFactory =
+        childInjector.getInstance(Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
   }
 
-  InMemoryDatabase(ReviewDbSchemaCreator schemaCreator) throws OrmException {
+  InMemoryDatabase(
+      GitRepositoryManager repoManager,
+      AllProjectsName allProjectsName,
+      SchemaCreator schemaCreator,
+      SchemaFactory<ReviewDb> schemaFactory) {
+    this.repoManager = repoManager;
+    this.allProjectsName = allProjectsName;
     this.schemaCreator = schemaCreator;
-    this.dbInstance = new Instance();
-  }
-
-  public Instance getDbInstance() {
-    return dbInstance;
-  }
-
-  public Database<ReviewDb> getDatabase() {
-    return dbInstance.database;
+    this.schemaFactory = schemaFactory;
   }
 
   @Override
   public ReviewDb open() throws OrmException {
-    return getDatabase().open();
+    return schemaFactory.open();
   }
 
   /** Ensure the database schema has been created and initialized. */
   public InMemoryDatabase create() throws OrmException {
-    if (!created) {
-      created = true;
-      try (ReviewDb c = open()) {
-        schemaCreator.create(c);
-      } catch (IOException | ConfigInvalidException e) {
-        throw new OrmException("Cannot create in-memory database", e);
+    try {
+      try {
+        repoManager.openRepository(allProjectsName).close();
+      } catch (RepositoryNotFoundException e) {
+        schemaCreator.create();
       }
+    } catch (IOException | ConfigInvalidException e) {
+      throw new OrmException("Cannot create in-memory database", e);
     }
     return this;
   }
-
-  public CurrentSchemaVersion getSchemaVersion() throws OrmException {
-    try (ReviewDb c = open()) {
-      return c.schemaVersion().get(new CurrentSchemaVersion.Key());
-    }
-  }
-
-  public void assertSchemaVersion() throws OrmException {
-    assertThat(getSchemaVersion().versionNbr).isEqualTo(ReviewDbSchemaVersion.getBinaryVersion());
-  }
-
-  public static class Instance {
-    private static int dbCnt;
-
-    private Connection openHandle;
-    private Database<ReviewDb> database;
-    private boolean keepOpen;
-
-    private static synchronized DataSource newDataSource() throws SQLException {
-      final Properties p = new Properties();
-      p.setProperty("driver", org.h2.Driver.class.getName());
-      p.setProperty("url", "jdbc:h2:mem:Test_" + (++dbCnt));
-      return new SimpleDataSource(p);
-    }
-
-    private Instance() throws OrmException {
-      try {
-        DataSource dataSource = newDataSource();
-
-        // Open one connection. This will peg the database into memory
-        // until someone calls drop on us, allowing subsequent connections
-        // opened against the same URL to go to the same set of tables.
-        //
-        openHandle = dataSource.getConnection();
-
-        // Build the access layer around the connection factory.
-        //
-        database = new Database<>(dataSource, ReviewDb.class);
-
-      } catch (SQLException e) {
-        throw new OrmException(e);
-      }
-    }
-
-    public void setKeepOpen(boolean keepOpen) {
-      this.keepOpen = keepOpen;
-    }
-
-    /** Drop this database from memory so it no longer exists. */
-    public void drop() {
-      if (keepOpen) {
-        return;
-      }
-
-      if (openHandle != null) {
-        try {
-          openHandle.close();
-        } catch (SQLException e) {
-          System.err.println("WARNING: Cannot close database connection");
-          e.printStackTrace(System.err);
-        }
-        openHandle = null;
-        database = null;
-      }
-    }
-  }
 }
diff --git a/java/com/google/gerrit/testing/InMemoryH2Type.java b/java/com/google/gerrit/testing/InMemoryH2Type.java
deleted file mode 100644
index ae3bf36..0000000
--- a/java/com/google/gerrit/testing/InMemoryH2Type.java
+++ /dev/null
@@ -1,30 +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.testing;
-
-import com.google.gerrit.server.schema.BaseDataSourceType;
-
-public class InMemoryH2Type extends BaseDataSourceType {
-
-  protected InMemoryH2Type() {
-    super(null);
-  }
-
-  @Override
-  public String getUrl() {
-    // not used
-    throw new UnsupportedOperationException();
-  }
-}
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index fc17816..682e8c2 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -70,10 +70,9 @@
 import com.google.gerrit.server.index.change.AllChangesIndexer;
 import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
 import com.google.gerrit.server.index.group.AllGroupsIndexer;
+import com.google.gerrit.server.index.group.GroupIndexCollection;
 import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.GwtormChangeBundleReader;
 import com.google.gerrit.server.notedb.MutableNotesMigration;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.patch.DiffExecutor;
@@ -81,16 +80,15 @@
 import com.google.gerrit.server.plugins.ServerInformationImpl;
 import com.google.gerrit.server.project.DefaultProjectNameLockManager;
 import com.google.gerrit.server.restapi.RestApiModule;
-import com.google.gerrit.server.schema.DataSourceType;
 import com.google.gerrit.server.schema.InMemoryAccountPatchReviewStore;
 import com.google.gerrit.server.schema.NotesMigrationSchemaFactory;
 import com.google.gerrit.server.schema.ReviewDbFactory;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreatorImpl;
 import com.google.gerrit.server.securestore.DefaultSecureStore;
 import com.google.gerrit.server.securestore.SecureStore;
 import com.google.gerrit.server.ssh.NoSshKeyCache;
 import com.google.gerrit.server.submit.LocalMergeSuperSetComputation;
-import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
@@ -189,15 +187,7 @@
     bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get("."));
     bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
     bind(GerritOptions.class).toInstance(new GerritOptions(false, false, false));
-    bind(PersonIdent.class)
-        .annotatedWith(GerritPersonIdent.class)
-        .toProvider(GerritPersonIdentProvider.class);
-    bind(String.class)
-        .annotatedWith(AnonymousCowardName.class)
-        .toProvider(AnonymousCowardNameProvider.class);
 
-    bind(AllProjectsName.class).toProvider(AllProjectsNameProvider.class);
-    bind(AllUsersName.class).toProvider(AllUsersNameProvider.class);
     bind(GitRepositoryManager.class).to(InMemoryRepositoryManager.class);
     bind(InMemoryRepositoryManager.class).in(SINGLETON);
     bind(TrackingFooters.class).toProvider(TrackingFootersProvider.class).in(SINGLETON);
@@ -206,8 +196,6 @@
     bind(ListeningExecutorService.class)
         .annotatedWith(ChangeUpdateExecutor.class)
         .toInstance(MoreExecutors.newDirectExecutorService());
-    bind(DataSourceType.class).to(InMemoryH2Type.class);
-    bind(ChangeBundleReader.class).to(GwtormChangeBundleReader.class);
     bind(SecureStore.class).to(DefaultSecureStore.class);
 
     TypeLiteral<SchemaFactory<ReviewDb>> schemaFactory =
@@ -215,6 +203,7 @@
     bind(schemaFactory).to(NotesMigrationSchemaFactory.class);
     bind(Key.get(schemaFactory, ReviewDbFactory.class)).to(InMemoryDatabase.class);
 
+    install(new InMemorySchemaModule());
     install(NoSshKeyCache.module());
     install(new GerritInstanceNameModule());
     install(
@@ -274,6 +263,41 @@
     install(new DefaultProjectNameLockManager.Module());
   }
 
+  /** Copy of ReviewDbSchemaModule with a slightly different server ID provider. */
+  // TODO(dborowitz): Better code sharing.
+  private class InMemorySchemaModule extends FactoryModule {
+    @Override
+    public void configure() {
+      bind(PersonIdent.class)
+          .annotatedWith(GerritPersonIdent.class)
+          .toProvider(GerritPersonIdentProvider.class);
+
+      bind(AllProjectsName.class).toProvider(AllProjectsNameProvider.class).in(SINGLETON);
+
+      bind(AllUsersName.class).toProvider(AllUsersNameProvider.class).in(SINGLETON);
+
+      bind(String.class)
+          .annotatedWith(AnonymousCowardName.class)
+          .toProvider(AnonymousCowardNameProvider.class);
+
+      bind(GroupIndexCollection.class);
+      bind(SchemaCreator.class).to(SchemaCreatorImpl.class);
+    }
+
+    @Provides
+    @Singleton
+    @GerritServerId
+    public String createServerId() {
+      String serverId =
+          cfg.getString(GerritServerIdProvider.SECTION, null, GerritServerIdProvider.KEY);
+      if (!Strings.isNullOrEmpty(serverId)) {
+        return serverId;
+      }
+
+      return "gerrit";
+    }
+  }
+
   @Provides
   @Singleton
   @SendEmailExecutor
@@ -290,21 +314,12 @@
 
   @Provides
   @Singleton
-  @GerritServerId
-  public String createServerId() {
-    String serverId =
-        cfg.getString(GerritServerIdProvider.SECTION, null, GerritServerIdProvider.KEY);
-    if (!Strings.isNullOrEmpty(serverId)) {
-      return serverId;
-    }
-
-    return "gerrit";
-  }
-
-  @Provides
-  @Singleton
-  InMemoryDatabase getInMemoryDatabase(ReviewDbSchemaCreator schemaCreator) throws OrmException {
-    return new InMemoryDatabase(schemaCreator);
+  InMemoryDatabase getInMemoryDatabase(
+      GitRepositoryManager repoManager,
+      AllProjectsName allProjectsName,
+      SchemaCreator schemaCreator,
+      SchemaFactory<ReviewDb> schemaFactory) {
+    return new InMemoryDatabase(repoManager, allProjectsName, schemaCreator, schemaFactory);
   }
 
   private Module luceneIndexModule() {
diff --git a/java/com/google/gerrit/testing/InMemoryTestEnvironment.java b/java/com/google/gerrit/testing/InMemoryTestEnvironment.java
index 02be071..f665015 100644
--- a/java/com/google/gerrit/testing/InMemoryTestEnvironment.java
+++ b/java/com/google/gerrit/testing/InMemoryTestEnvironment.java
@@ -21,7 +21,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AuthRequest;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gwtorm.server.SchemaFactory;
@@ -50,10 +50,8 @@
   @Inject private AccountManager accountManager;
   @Inject private IdentifiedUser.GenericFactory userFactory;
   @Inject private SchemaFactory<ReviewDb> schemaFactory;
-  @Inject private ReviewDbSchemaCreator schemaCreator;
+  @Inject private SchemaCreator schemaCreator;
   @Inject private ThreadLocalRequestContext requestContext;
-  // Only for use in setting up/tearing down injector.
-  @Inject private InMemoryDatabase inMemoryDatabase;
 
   private ReviewDb db;
   private LifecycleManager lifecycle;
@@ -117,9 +115,7 @@
     lifecycle.add(injector);
     lifecycle.start();
 
-    try (ReviewDb underlyingDb = inMemoryDatabase.getDatabase().open()) {
-      schemaCreator.create(underlyingDb);
-    }
+    schemaCreator.create();
     db = schemaFactory.open();
 
     // The first user is added to the "Administrators" group. See AccountManager#create().
@@ -139,6 +135,5 @@
     if (db != null) {
       db.close();
     }
-    InMemoryDatabase.drop(inMemoryDatabase);
   }
 }
diff --git a/java/com/google/gerrit/testing/NoteDbChecker.java b/java/com/google/gerrit/testing/NoteDbChecker.java
deleted file mode 100644
index 1dc8ee2..0000000
--- a/java/com/google/gerrit/testing/NoteDbChecker.java
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright (C) 2016 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 static com.google.common.truth.Truth.assertThat;
-import static java.util.Comparator.comparing;
-import static java.util.stream.Collectors.toList;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.ChangeBundle;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.MutableNotesMigration;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
-import com.google.gwtorm.client.IntKey;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Stream;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.runner.Description;
-
-@Singleton
-public class NoteDbChecker {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private final Provider<ReviewDb> dbProvider;
-  private final GitRepositoryManager repoManager;
-  private final MutableNotesMigration notesMigration;
-  private final ChangeBundleReader bundleReader;
-  private final ChangeNotes.Factory notesFactory;
-  private final ChangeRebuilder changeRebuilder;
-  private final CommentsUtil commentsUtil;
-
-  @Inject
-  NoteDbChecker(
-      Provider<ReviewDb> dbProvider,
-      GitRepositoryManager repoManager,
-      MutableNotesMigration notesMigration,
-      ChangeBundleReader bundleReader,
-      ChangeNotes.Factory notesFactory,
-      ChangeRebuilder changeRebuilder,
-      CommentsUtil commentsUtil) {
-    this.dbProvider = dbProvider;
-    this.repoManager = repoManager;
-    this.bundleReader = bundleReader;
-    this.notesMigration = notesMigration;
-    this.notesFactory = notesFactory;
-    this.changeRebuilder = changeRebuilder;
-    this.commentsUtil = commentsUtil;
-  }
-
-  public void rebuildAndCheckAllChanges() throws Exception {
-    rebuildAndCheckChanges(
-        getUnwrappedDb().changes().all().toList().stream().map(Change::getId),
-        ImmutableListMultimap.of());
-  }
-
-  public void rebuildAndCheckChanges(Change.Id... changeIds) throws Exception {
-    rebuildAndCheckChanges(Arrays.stream(changeIds), ImmutableListMultimap.of());
-  }
-
-  private void rebuildAndCheckChanges(
-      Stream<Change.Id> changeIds, ListMultimap<Change.Id, String> expectedDiffs) throws Exception {
-    ReviewDb db = getUnwrappedDb();
-
-    List<ChangeBundle> allExpected = readExpected(changeIds);
-
-    boolean oldWrite = notesMigration.rawWriteChangesSetting();
-    boolean oldRead = notesMigration.readChanges();
-    try {
-      notesMigration.setWriteChanges(true);
-      notesMigration.setReadChanges(true);
-      List<String> msgs = new ArrayList<>();
-      for (ChangeBundle expected : allExpected) {
-        Change c = expected.getChange();
-        try {
-          changeRebuilder.rebuild(db, c.getId());
-        } catch (RepositoryNotFoundException e) {
-          msgs.add("Repository not found for change, cannot convert: " + c);
-        }
-      }
-
-      checkActual(allExpected, expectedDiffs, msgs);
-    } finally {
-      notesMigration.setReadChanges(oldRead);
-      notesMigration.setWriteChanges(oldWrite);
-    }
-  }
-
-  public void checkChanges(Change.Id... changeIds) throws Exception {
-    checkActual(
-        readExpected(Arrays.stream(changeIds)), ImmutableListMultimap.of(), new ArrayList<>());
-  }
-
-  public void rebuildAndCheckChange(Change.Id changeId, String... expectedDiff) throws Exception {
-    ImmutableListMultimap.Builder<Change.Id, String> b = ImmutableListMultimap.builder();
-    b.putAll(changeId, Arrays.asList(expectedDiff));
-    rebuildAndCheckChanges(Stream.of(changeId), b.build());
-  }
-
-  public void assertNoChangeRef(Project.NameKey project, Change.Id changeId) throws Exception {
-    try (Repository repo = repoManager.openRepository(project)) {
-      assertThat(repo.exactRef(RefNames.changeMetaRef(changeId))).isNull();
-    }
-  }
-
-  public void assertNoReviewDbChanges(Description desc) throws Exception {
-    ReviewDb db = getUnwrappedDb();
-    assertThat(db.changes().all().toList()).named("Changes in " + desc.getTestClass()).isEmpty();
-    assertThat(db.changeMessages().all().toList())
-        .named("ChangeMessages in " + desc.getTestClass())
-        .isEmpty();
-    assertThat(db.patchSets().all().toList())
-        .named("PatchSets in " + desc.getTestClass())
-        .isEmpty();
-    assertThat(db.patchSetApprovals().all().toList())
-        .named("PatchSetApprovals in " + desc.getTestClass())
-        .isEmpty();
-    assertThat(db.patchComments().all().toList())
-        .named("PatchLineComments in " + desc.getTestClass())
-        .isEmpty();
-  }
-
-  private List<ChangeBundle> readExpected(Stream<Change.Id> changeIds) throws Exception {
-    boolean old = notesMigration.readChanges();
-    try {
-      notesMigration.setReadChanges(false);
-      return changeIds
-          .sorted(comparing(IntKey::get))
-          .map(this::readBundleUnchecked)
-          .collect(toList());
-    } finally {
-      notesMigration.setReadChanges(old);
-    }
-  }
-
-  private ChangeBundle readBundleUnchecked(Change.Id id) {
-    try {
-      return bundleReader.fromReviewDb(getUnwrappedDb(), id);
-    } catch (OrmException e) {
-      throw new OrmRuntimeException(e);
-    }
-  }
-
-  private void checkActual(
-      List<ChangeBundle> allExpected,
-      ListMultimap<Change.Id, String> expectedDiffs,
-      List<String> msgs)
-      throws Exception {
-    ReviewDb db = getUnwrappedDb();
-    boolean oldRead = notesMigration.readChanges();
-    boolean oldWrite = notesMigration.rawWriteChangesSetting();
-    try {
-      notesMigration.setWriteChanges(true);
-      notesMigration.setReadChanges(true);
-      for (ChangeBundle expected : allExpected) {
-        Change c = expected.getChange();
-        ChangeBundle actual;
-        try {
-          actual =
-              ChangeBundle.fromNotes(
-                  commentsUtil, notesFactory.create(db, c.getProject(), c.getId()));
-        } catch (Throwable t) {
-          String msg = "Error converting change: " + c;
-          msgs.add(msg);
-          logger.atSevere().withCause(t).log(msg);
-          continue;
-        }
-        List<String> diff = expected.differencesFrom(actual);
-        List<String> expectedDiff = expectedDiffs.get(c.getId());
-        if (!diff.equals(expectedDiff)) {
-          msgs.add("Differences between ReviewDb and NoteDb for " + c + ":");
-          msgs.addAll(diff);
-          if (!expectedDiff.isEmpty()) {
-            msgs.add("Expected differences:");
-            msgs.addAll(expectedDiff);
-          }
-          msgs.add("");
-        } else {
-          System.err.println("NoteDb conversion of change " + c.getId() + " successful");
-        }
-      }
-    } finally {
-      notesMigration.setReadChanges(oldRead);
-      notesMigration.setWriteChanges(oldWrite);
-    }
-    if (!msgs.isEmpty()) {
-      throw new AssertionError(Joiner.on('\n').join(msgs));
-    }
-  }
-
-  private ReviewDb getUnwrappedDb() {
-    ReviewDb db = dbProvider.get();
-    return ReviewDbUtil.unwrapDb(db);
-  }
-}
diff --git a/java/com/google/gerrit/testing/NoteDbMode.java b/java/com/google/gerrit/testing/NoteDbMode.java
index e46acc3..f901cce 100644
--- a/java/com/google/gerrit/testing/NoteDbMode.java
+++ b/java/com/google/gerrit/testing/NoteDbMode.java
@@ -35,13 +35,7 @@
   PRIMARY(NotesMigrationState.READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY),
 
   /** All change tables are entirely disabled, and code/meta ref updates are fused. */
-  ON(NotesMigrationState.NOTE_DB),
-
-  /**
-   * Run tests with NoteDb disabled, then convert ReviewDb to NoteDb and check that the results
-   * match.
-   */
-  CHECK(NotesMigrationState.REVIEW_DB);
+  ON(NotesMigrationState.NOTE_DB);
 
   private static final String ENV_VAR = "GERRIT_NOTEDB";
   private static final String SYS_PROP = "gerrit.notedb";
diff --git a/java/com/google/gerrit/testing/TestChanges.java b/java/com/google/gerrit/testing/TestChanges.java
index 8e752fa..0c87b38 100644
--- a/java/com/google/gerrit/testing/TestChanges.java
+++ b/java/com/google/gerrit/testing/TestChanges.java
@@ -30,7 +30,6 @@
 import com.google.gerrit.server.notedb.AbstractChangeNotes;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Injector;
 import java.util.TimeZone;
@@ -98,8 +97,7 @@
 
     ChangeNotes notes = update.getNotes();
     boolean hasPatchSets = notes.getPatchSets() != null && !notes.getPatchSets().isEmpty();
-    NotesMigration migration = injector.getInstance(NotesMigration.class);
-    if (hasPatchSets || !migration.readChanges()) {
+    if (hasPatchSets) {
       return update;
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 0f195b5..66556178 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -57,6 +57,7 @@
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
 import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
 import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -122,7 +123,6 @@
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.index.account.AccountIndexer;
 import com.google.gerrit.server.index.account.StalenessChecker;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
 import com.google.gerrit.server.project.ProjectConfig;
 import com.google.gerrit.server.project.RefPattern;
 import com.google.gerrit.server.query.account.InternalAccountQuery;
@@ -195,35 +195,23 @@
   }
 
   @Inject private Provider<PublicKeyStore> publicKeyStoreProvider;
-
   @Inject private @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider;
-
   @Inject private ExternalIds externalIds;
-
   @Inject private DynamicSet<AccountIndexedListener> accountIndexedListeners;
-
   @Inject private DynamicSet<GitReferenceUpdatedListener> refUpdateListeners;
-
   @Inject private Sequences seq;
-
   @Inject private Provider<InternalAccountQuery> accountQueryProvider;
+  @Inject private StalenessChecker stalenessChecker;
+  @Inject private AccountIndexer accountIndexer;
+  @Inject private GitReferenceUpdated gitReferenceUpdated;
+  @Inject private RetryHelper.Metrics retryMetrics;
+  @Inject private Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
+  @Inject private ExternalIdNotes.Factory extIdNotesFactory;
+  @Inject private VersionedAuthorizedKeys.Accessor authorizedKeys;
+  @Inject private ProjectOperations projectOperations;
 
   @Inject protected Emails emails;
 
-  @Inject private StalenessChecker stalenessChecker;
-
-  @Inject private AccountIndexer accountIndexer;
-
-  @Inject private GitReferenceUpdated gitReferenceUpdated;
-
-  @Inject private RetryHelper.Metrics retryMetrics;
-
-  @Inject private Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
-
-  @Inject private ExternalIdNotes.Factory extIdNotesFactory;
-
-  @Inject private VersionedAuthorizedKeys.Accessor authorizedKeys;
-
   @Inject
   @Named("accounts")
   private LoadingCache<Account.Id, Optional<AccountState>> accountsCache;
@@ -479,7 +467,7 @@
       RevCommit c = rw.parseCommit(ref.getObjectId());
       long timestampDiffMs =
           Math.abs(c.getCommitTime() * 1000L - getAccount(accountId).getRegisteredOn().getTime());
-      assertThat(timestampDiffMs).isAtMost(ChangeRebuilderImpl.MAX_WINDOW_MS);
+      assertThat(timestampDiffMs).isAtMost(SECONDS.toMillis(1));
 
       // Check the 'account.config' file.
       try (TreeWalk tw = TreeWalk.forPath(or, AccountProperties.ACCOUNT_CONFIG, c.getTree())) {
@@ -2263,12 +2251,7 @@
             externalIds,
             metaDataUpdateInternalFactory,
             new RetryHelper(
-                cfg,
-                retryMetrics,
-                null,
-                null,
-                null,
-                r -> r.withBlockStrategy(noSleepBlockStrategy)),
+                cfg, retryMetrics, null, r -> r.withBlockStrategy(noSleepBlockStrategy)),
             extIdNotesFactory,
             ident,
             ident,
@@ -2321,8 +2304,6 @@
                 cfg,
                 retryMetrics,
                 null,
-                null,
-                null,
                 r ->
                     r.withStopStrategy(StopStrategies.stopAfterAttempt(status.size()))
                         .withBlockStrategy(noSleepBlockStrategy)),
@@ -2380,12 +2361,7 @@
             externalIds,
             metaDataUpdateInternalFactory,
             new RetryHelper(
-                cfg,
-                retryMetrics,
-                null,
-                null,
-                null,
-                r -> r.withBlockStrategy(noSleepBlockStrategy)),
+                cfg, retryMetrics, null, r -> r.withBlockStrategy(noSleepBlockStrategy)),
             extIdNotesFactory,
             ident,
             ident,
@@ -2451,12 +2427,7 @@
             externalIds,
             metaDataUpdateInternalFactory,
             new RetryHelper(
-                cfg,
-                retryMetrics,
-                null,
-                null,
-                null,
-                r -> r.withBlockStrategy(noSleepBlockStrategy)),
+                cfg, retryMetrics, null, r -> r.withBlockStrategy(noSleepBlockStrategy)),
             extIdNotesFactory,
             ident,
             ident,
@@ -2602,7 +2573,7 @@
   public void deleteAllDraftComments() throws Exception {
     try {
       TestTimeUtil.resetWithClockStep(1, SECONDS);
-      Project.NameKey project2 = createProject("project2");
+      Project.NameKey project2 = projectOperations.newProject().create();
       PushOneCommit.Result r1 = createChange();
 
       TestRepository<?> tr2 = cloneProject(project2);
@@ -2921,7 +2892,7 @@
         TreeWalk.forPath(
             allUsersRepo.getRepository(),
             AccountProperties.ACCOUNT_CONFIG,
-            getHead(allUsersRepo.getRepository()).getTree())) {
+            getHead(allUsersRepo.getRepository(), "HEAD").getTree())) {
       assertThat(tw).isNotNull();
       ac.fromText(
           new String(
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 744d1e9..324f33b 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
 import static com.google.gerrit.acceptance.GitUtil.pushHead;
 import static com.google.gerrit.acceptance.PushOneCommit.FILE_CONTENT;
@@ -40,7 +39,6 @@
 import static com.google.gerrit.extensions.client.ReviewerState.REMOVED;
 import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
-import static com.google.gerrit.reviewdb.server.ReviewDbUtil.unwrapDb;
 import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
 import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
 import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
@@ -69,6 +67,7 @@
 import com.google.gerrit.acceptance.TestProjectInput;
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
 import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.FooterConstants;
 import com.google.gerrit.common.data.LabelFunction;
 import com.google.gerrit.common.data.LabelType;
@@ -132,9 +131,7 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.LabelId;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.ChangeMessagesUtil;
@@ -145,7 +142,6 @@
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.index.change.IndexedChangeQuery;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.project.testing.Util;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.restapi.change.PostReview;
@@ -161,7 +157,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -196,6 +191,7 @@
   @Inject private IndexConfig indexConfig;
 
   @Inject protected GroupOperations groupOperations;
+  @Inject private ProjectOperations projectOperations;
 
   private ChangeIndexedCounter changeIndexedCounter;
   private RegistrationHandle changeIndexedCounterHandle;
@@ -511,8 +507,6 @@
 
   @Test
   public void pendingReviewersInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     ConfigInput conf = new ConfigInput();
     conf.enableReviewerByEmail = InheritableBoolean.TRUE;
     gApi.projects().name(project.get()).config(conf);
@@ -852,16 +846,11 @@
 
     List<Integer> reviewers =
         result.get(ReviewerState.REVIEWER).stream().map(a -> a._accountId).collect(toList());
-    if (notesMigration.readChanges()) {
-      assertThat(result).containsKey(ReviewerState.CC);
-      List<Integer> ccs =
-          result.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
-      assertThat(ccs).containsExactly(accountCreator.user2().id.get());
-      assertThat(reviewers).containsExactly(user.id.get(), admin.id.get());
-    } else {
-      assertThat(reviewers)
-          .containsExactly(user.id.get(), admin.id.get(), accountCreator.user2().id.get());
-    }
+    assertThat(result).containsKey(ReviewerState.CC);
+    List<Integer> ccs =
+        result.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
+    assertThat(ccs).containsExactly(accountCreator.user2().id.get());
+    assertThat(reviewers).containsExactly(user.id.get(), admin.id.get());
   }
 
   @Test
@@ -927,17 +916,6 @@
     assertThat(cr.all).hasSize(1);
     assertThat(cr.all.get(0).value).isEqualTo(1);
 
-    if (notesMigration.changePrimaryStorage() == PrimaryStorage.REVIEW_DB) {
-      // Ensure record was actually copied under ReviewDb
-      List<PatchSetApproval> psas =
-          unwrapDb(db)
-              .patchSetApprovals()
-              .byPatchSet(new PatchSet.Id(new Change.Id(c2._number), 2))
-              .toList();
-      assertThat(psas).hasSize(1);
-      assertThat(psas.get(0).getValue()).isEqualTo((short) 1);
-    }
-
     // Rebasing the second change again should fail
     exception.expect(ResourceConflictException.class);
     exception.expectMessage("Change is already up to date");
@@ -965,7 +943,8 @@
     ri2 = ci2.revisions.get(ci2.currentRevision);
     assertThat(ri2.commit.parents.get(0).commit).isEqualTo(r1.getCommit().name());
 
-    List<RelatedChangeAndCommitInfo> related = getRelated(id2, ri2._number);
+    List<RelatedChangeAndCommitInfo> related =
+        gApi.changes().id(id2.get()).revision(ri2._number).related().changes;
     assertThat(related).hasSize(2);
     assertThat(related.get(0)._changeNumber).isEqualTo(id2.get());
     assertThat(related.get(0)._revisionNumber).isEqualTo(2);
@@ -999,7 +978,7 @@
     ri2 = ci2.revisions.get(ci2.currentRevision);
     assertThat(ri2.commit.parents.get(0).commit).isEqualTo(r1.getCommit().name());
 
-    assertThat(getRelated(id2, ri2._number)).isEmpty();
+    assertThat(gApi.changes().id(id2.get()).revision(ri2._number).related().changes).isEmpty();
   }
 
   @Test
@@ -1025,7 +1004,7 @@
     RevisionInfo ri3 = ci3.revisions.get(ci3.currentRevision);
     assertThat(ri3.commit.parents.get(0).commit).isEqualTo(r1.getCommit().name());
 
-    assertThat(getRelated(id3, ri3._number)).isEmpty();
+    assertThat(gApi.changes().id(id3.get()).revision(ri3._number).related().changes).isEmpty();
   }
 
   @Test
@@ -1539,7 +1518,7 @@
   @Test
   public void pushCommitOfOtherUserThatCannotSeeChange() throws Exception {
     // create hidden project that is only visible to administrators
-    Project.NameKey p = createProject("p");
+    Project.NameKey p = projectOperations.newProject().create();
     try (ProjectConfigUpdate u = updateProject(p)) {
       Util.allow(u.getConfig(), Permission.READ, adminGroupUuid(), "refs/*");
       Util.block(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
@@ -1613,7 +1592,7 @@
   @Test
   public void pushCommitWithFooterOfOtherUserThatCannotSeeChange() throws Exception {
     // create hidden project that is only visible to administrators
-    Project.NameKey p = createProject("p");
+    Project.NameKey p = projectOperations.newProject().create();
     try (ProjectConfigUpdate u = updateProject(p)) {
       Util.allow(u.getConfig(), Permission.READ, adminGroupUuid(), "refs/*");
       Util.block(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
@@ -1657,7 +1636,7 @@
   @Test
   public void addReviewerThatCannotSeeChange() throws Exception {
     // create hidden project that is only visible to administrators
-    Project.NameKey p = createProject("p");
+    Project.NameKey p = projectOperations.newProject().create();
     try (ProjectConfigUpdate u = updateProject(p)) {
       Util.allow(u.getConfig(), Permission.READ, adminGroupUuid(), "refs/*");
       Util.block(u.getConfig(), Permission.READ, REGISTERED_USERS, "refs/*");
@@ -1708,8 +1687,6 @@
 
   @Test
   public void addReviewerThatIsInactiveEmailFallback() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     ConfigInput conf = new ConfigInput();
     conf.enableReviewerByEmail = InheritableBoolean.TRUE;
     gApi.projects().name(project.get()).config(conf);
@@ -1925,26 +1902,6 @@
   }
 
   @Test
-  public void addReviewerWithNoteDbWhenDummyApprovalInReviewDbExists() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-    assume().that(notesMigration.changePrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
-
-    PushOneCommit.Result r = createChange();
-
-    // insert dummy approval in ReviewDb
-    PatchSetApproval psa =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(r.getPatchSetId(), user.id, new LabelId("Code-Review")),
-            (short) 0,
-            TimeUtil.nowTs());
-    db.patchSetApprovals().insert(Collections.singleton(psa));
-
-    AddReviewerInput in = new AddReviewerInput();
-    in.reviewer = user.email;
-    gApi.changes().id(r.getChangeId()).addReviewer(in);
-  }
-
-  @Test
   public void addSelfAsReviewer() throws Exception {
     TestTimeUtil.resetWithClockStep(1, SECONDS);
     PushOneCommit.Result r = createChange();
@@ -2002,39 +1959,7 @@
     in.reviewers = ImmutableList.of();
     gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
 
-    // If we're not reading from NoteDb, then the CCed user will be returned in the REVIEWER state.
-    assertThat(getReviewerState(r.getChangeId(), testAccount.id))
-        .hasValue(notesMigration.readChanges() ? CC : REVIEWER);
-  }
-
-  @Test
-  public void implicitlyCcOnNonVotingReviewGwtStyle() throws Exception {
-    testImplicitlyCcOnNonVotingReviewGwtStyle(user);
-  }
-
-  @Test
-  public void implicitlyCcOnNonVotingReviewForUserWithoutUserNameGwtStyle() throws Exception {
-    com.google.gerrit.acceptance.TestAccount accountWithoutUsername = accountCreator.create();
-    assertThat(accountWithoutUsername.username).isNull();
-    testImplicitlyCcOnNonVotingReviewGwtStyle(accountWithoutUsername);
-  }
-
-  private void testImplicitlyCcOnNonVotingReviewGwtStyle(
-      com.google.gerrit.acceptance.TestAccount testAccount) throws Exception {
-    PushOneCommit.Result r = createChange();
-    setApiUser(testAccount);
-    assertThat(getReviewerState(r.getChangeId(), testAccount.id)).isEmpty();
-
-    // Exact request format made by GWT UI at ddc6b7160fe416fed9e7e3180489d44c82fd64f8.
-    ReviewInput in = new ReviewInput();
-    in.labels = ImmutableMap.of("Code-Review", (short) 0);
-    in.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
-    in.message = "comment";
-    gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
-
-    // If we're not reading from NoteDb, then the CCed user will be returned in the REVIEWER state.
-    assertThat(getReviewerState(r.getChangeId(), testAccount.id))
-        .hasValue(notesMigration.readChanges() ? CC : REVIEWER);
+    assertThat(getReviewerState(r.getChangeId(), testAccount.id)).hasValue(CC);
   }
 
   @Test
@@ -2063,8 +1988,7 @@
         .revision(r.getCommit().name())
         .review(new ReviewInput().message("hi"));
     c = gApi.changes().id(r.getChangeId()).get();
-    ReviewerState state = notesMigration.readChanges() ? CC : REVIEWER;
-    assertThat(c.reviewers.get(state).stream().map(ai -> ai._accountId).collect(toList()))
+    assertThat(c.reviewers.get(CC).stream().map(ai -> ai._accountId).collect(toList()))
         .containsExactly(user.id.get());
   }
 
@@ -2913,8 +2837,6 @@
 
   @Test
   public void noteDbCommitsOnPatchSetCreation() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     PushOneCommit.Result r = createChange();
     pushFactory
         .create(
@@ -2974,7 +2896,7 @@
   @Test
   public void createNewPatchSetWithoutPermission() throws Exception {
     // Create new project with clean permissions
-    Project.NameKey p = createProject("addPatchSet1");
+    Project.NameKey p = projectOperations.newProject().create();
 
     // Clone separate repositories of the same project as admin and as user
     TestRepository<InMemoryRepository> adminTestRepo = cloneProject(p, admin);
@@ -3020,7 +2942,7 @@
   @Test
   public void createNewPatchSetAsOwnerWithoutPermission() throws Exception {
     // Create new project with clean permissions
-    Project.NameKey p = createProject("addPatchSet2");
+    Project.NameKey p = projectOperations.newProject().create();
     // Clone separate repositories of the same project as admin and as user
     TestRepository<?> adminTestRepo = cloneProject(project, admin);
 
@@ -3466,12 +3388,7 @@
     gApi.changes().id(changeId).current().review(input);
 
     Map<String, Short> votes = gApi.changes().id(changeId).current().reviewer(admin.email).votes();
-    if (!notesMigration.readChanges()) {
-      assertThat(votes.keySet()).containsExactly("Code-Review");
-      assertThat(votes.values()).containsExactly((short) 0);
-    } else {
-      assertThat(votes).isEmpty();
-    }
+    assertThat(votes).isEmpty();
   }
 
   @Test
@@ -3658,7 +3575,7 @@
   @Test
   public void changeCommitMessageWithoutPermissionFails() throws Exception {
     // Create new project with clean permissions
-    Project.NameKey p = createProject("addPatchSetEdit");
+    Project.NameKey p = projectOperations.newProject().create();
     TestRepository<InMemoryRepository> userTestRepo = cloneProject(p, user);
     // Block default permission
     block(p, "refs/for/*", Permission.ADD_PATCH_SET, REGISTERED_USERS);
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java
index fe7da66..7899ecd 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java
@@ -19,18 +19,21 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.extensions.restapi.DeprecatedIdentifierException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
 import org.junit.Before;
 import org.junit.Test;
 
 @NoHttpd
 public class ChangeIdIT extends AbstractDaemonTest {
   private ChangeInfo changeInfo;
+  @Inject private ProjectOperations projectOperations;
 
   @Before
   public void setup() throws Exception {
@@ -45,7 +48,7 @@
 
   @Test
   public void projectChangeNumberReturnsChangeWhenProjectContainsSlashes() throws Exception {
-    Project.NameKey p = createProject("foo/bar");
+    Project.NameKey p = projectOperations.newProject().create();
     ChangeInfo ci = gApi.changes().create(new ChangeInput(p.get(), "master", "msg")).get();
     ChangeApi cApi = gApi.changes().id(p.get(), ci._number);
     assertThat(cApi.get().changeId).isEqualTo(ci.changeId);
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 6f25d28..85fbfa6 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -27,6 +27,7 @@
 import static java.util.stream.Collectors.toList;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.truth.Correspondence;
 import com.google.common.util.concurrent.AtomicLongMap;
@@ -89,14 +90,13 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.sql.Timestamp;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.CommitBuilder;
@@ -146,25 +146,6 @@
     }
   }
 
-  // Creates a group, but with uniquified name.
-  protected String createUniqueGroup() throws Exception {
-    // TODO(hanwen): rewrite this test in terms of UUID. This requires redoing the assertion helpers
-    // too.
-    AccountGroup.UUID g = groupOperations.newGroup().ownerGroupUuid(adminGroupUuid()).create();
-    return groupRef(g).getName();
-  }
-
-  protected String createGroup(String name, String owner) throws Exception {
-    // TODO(hanwen): rewrite to use groupOperations. This requires passing the owner
-    // group's UUID rathen than its name.
-    name = name(name);
-    GroupInput in = new GroupInput();
-    in.name = name;
-    in.ownerId = owner;
-    gApi.groups().create(in);
-    return name;
-  }
-
   @Override
   protected ProjectResetter.Config resetProjects() {
     // Don't reset All-Users since deleting users makes groups inconsistent (e.g. groups would
@@ -193,12 +174,14 @@
 
   @Test
   public void addRemoveMember() throws Exception {
-    String g = createUniqueGroup();
-    gApi.groups().id(g).addMembers("user");
-    assertMembers(g, user);
+    AccountGroup.UUID group = groupOperations.newGroup().create();
 
-    gApi.groups().id(g).removeMembers("user");
-    assertNoMembers(g);
+    gApi.groups().id(group.get()).addMembers("user");
+    assertMembers(group.get(), user);
+
+    gApi.groups().id(group.get()).removeMembers("user");
+    ImmutableSet<Account.Id> members = groupOperations.group(group).get().members();
+    assertThat(members).isEmpty();
   }
 
   @Test
@@ -208,16 +191,15 @@
 
     // Fill the cache for the observed account.
     groupIncludeCache.getGroupsWithMember(accountId);
-    String groupName = createUniqueGroup();
-    AccountGroup.UUID groupUuid = new AccountGroup.UUID(gApi.groups().id(groupName).get().id);
+    AccountGroup.UUID groupUuid = groupOperations.newGroup().create();
 
-    gApi.groups().id(groupName).addMembers(username);
+    gApi.groups().id(groupUuid.get()).addMembers(username);
 
     Collection<AccountGroup.UUID> groupsWithMemberAfterAddition =
         groupIncludeCache.getGroupsWithMember(accountId);
     assertThat(groupsWithMemberAfterAddition).contains(groupUuid);
 
-    gApi.groups().id(groupName).removeMembers(username);
+    gApi.groups().id(groupUuid.get()).removeMembers(username);
 
     Collection<AccountGroup.UUID> groupsWithMemberAfterRemoval =
         groupIncludeCache.getGroupsWithMember(accountId);
@@ -240,16 +222,16 @@
 
   @Test
   public void addMultipleMembers() throws Exception {
-    String g = createUniqueGroup();
+    AccountGroup.UUID group = groupOperations.newGroup().create();
 
     String u1 = name("u1");
     accountOperations.newAccount().username(u1).create();
     String u2 = name("u2");
     accountOperations.newAccount().username(u2).create();
 
-    gApi.groups().id(g).addMembers(u1, u2);
+    gApi.groups().id(group.get()).addMembers(u1, u2);
 
-    List<AccountInfo> members = gApi.groups().id(g).members();
+    List<AccountInfo> members = gApi.groups().id(group.get()).members();
     assertThat(members)
         .comparingElementsUsing(getAccountToUsernameCorrespondence())
         .containsExactly(u1, u2);
@@ -257,13 +239,13 @@
 
   @Test
   public void membersWithAtSignInUsernameCanBeAdded() throws Exception {
-    String g = createUniqueGroup();
+    AccountGroup.UUID group = groupOperations.newGroup().create();
     String usernameWithAt = name("u1@something");
     accountOperations.newAccount().username(usernameWithAt).create();
 
-    gApi.groups().id(g).addMembers(usernameWithAt);
+    gApi.groups().id(group.get()).addMembers(usernameWithAt);
 
-    List<AccountInfo> members = gApi.groups().id(g).members();
+    List<AccountInfo> members = gApi.groups().id(group.get()).members();
     assertThat(members)
         .comparingElementsUsing(getAccountToUsernameCorrespondence())
         .containsExactly(usernameWithAt);
@@ -271,7 +253,7 @@
 
   @Test
   public void membersWithAtSignInUsernameAreNotConfusedWithSimilarUsernames() throws Exception {
-    String g = createUniqueGroup();
+    AccountGroup.UUID group = groupOperations.newGroup().create();
     String usernameWithAt = name("u1@something");
     accountOperations.newAccount().username(usernameWithAt).create();
     String usernameWithoutAt = name("u1something");
@@ -282,10 +264,10 @@
     accountOperations.newAccount().username(usernameOnlySuffix).create();
 
     gApi.groups()
-        .id(g)
+        .id(group.get())
         .addMembers(usernameWithAt, usernameWithoutAt, usernameOnlyPrefix, usernameOnlySuffix);
 
-    List<AccountInfo> members = gApi.groups().id(g).members();
+    List<AccountInfo> members = gApi.groups().id(group.get()).members();
     assertThat(members)
         .comparingElementsUsing(getAccountToUsernameCorrespondence())
         .containsExactly(usernameWithAt, usernameWithoutAt, usernameOnlyPrefix, usernameOnlySuffix);
@@ -293,52 +275,54 @@
 
   @Test
   public void includeRemoveGroup() throws Exception {
-    String p = createUniqueGroup();
-    String g = createUniqueGroup();
-    gApi.groups().id(p).addGroups(g);
-    assertIncludes(p, g);
+    AccountGroup.UUID parent = groupOperations.newGroup().create();
+    AccountGroup.UUID group = groupOperations.newGroup().create();
+    gApi.groups().id(parent.get()).addGroups(group.get());
+    assertThat(groupOperations.group(parent).get().subgroups()).containsExactly(group);
 
-    gApi.groups().id(p).removeGroups(g);
-    assertNoIncludes(p);
+    gApi.groups().id(parent.get()).removeGroups(group.get());
+    assertThat(groupOperations.group(parent).get().subgroups()).isEmpty();
   }
 
   @Test
   public void includeExternalGroup() throws Exception {
-    String g = createUniqueGroup();
+    AccountGroup.UUID group = groupOperations.newGroup().create();
     String subgroupUuid = SystemGroupBackend.REGISTERED_USERS.get();
-    gApi.groups().id(g).addGroups(subgroupUuid);
+    gApi.groups().id(group.get()).addGroups(subgroupUuid);
 
-    List<GroupInfo> subgroups = gApi.groups().id(g).includedGroups();
+    List<GroupInfo> subgroups = gApi.groups().id(group.get()).includedGroups();
     assertThat(subgroups).hasSize(1);
     assertThat(subgroups.get(0).id).isEqualTo(subgroupUuid.replace(":", "%3A"));
     assertThat(subgroups.get(0).name).isEqualTo("Registered Users");
     assertThat(subgroups.get(0).groupId).isNull();
 
-    List<? extends GroupAuditEventInfo> auditEvents = gApi.groups().id(g).auditLog();
+    List<? extends GroupAuditEventInfo> auditEvents = gApi.groups().id(group.get()).auditLog();
     assertThat(auditEvents).hasSize(1);
     assertSubgroupAuditEvent(auditEvents.get(0), Type.ADD_GROUP, admin.id, "Registered Users");
   }
 
   @Test
   public void includeExistingGroup_OK() throws Exception {
-    String p = createUniqueGroup();
-    String g = createUniqueGroup();
-    gApi.groups().id(p).addGroups(g);
-    assertIncludes(p, g);
-    gApi.groups().id(p).addGroups(g);
-    assertIncludes(p, g);
+    AccountGroup.UUID parent = groupOperations.newGroup().create();
+    AccountGroup.UUID group = groupOperations.newGroup().create();
+    groupOperations.group(parent).forUpdate().addSubgroup(group);
+
+    gApi.groups().id(parent.get()).addGroups(group.get());
+
+    ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(parent).get().subgroups();
+    assertThat(subgroups).containsExactly(group);
   }
 
   @Test
   public void addMultipleIncludes() throws Exception {
-    String p = createUniqueGroup();
-    String g1 = createUniqueGroup();
-    String g2 = createUniqueGroup();
-    List<String> groups = new ArrayList<>();
-    groups.add(g1);
-    groups.add(g2);
-    gApi.groups().id(p).addGroups(g1, g2);
-    assertIncludes(p, g1, g2);
+    AccountGroup.UUID parent = groupOperations.newGroup().create();
+    AccountGroup.UUID group1 = groupOperations.newGroup().create();
+    AccountGroup.UUID group2 = groupOperations.newGroup().create();
+
+    gApi.groups().id(parent.get()).addGroups(group1.get(), group2.get());
+
+    ImmutableSet<AccountGroup.UUID> subgroups = groupOperations.group(parent).get().subgroups();
+    assertThat(subgroups).containsExactly(group1, group2);
   }
 
   @Test
@@ -639,33 +623,39 @@
 
   @Test
   public void listEmptyGroupIncludes() throws Exception {
-    String gx = createUniqueGroup();
-    assertThat(gApi.groups().id(gx).includedGroups()).isEmpty();
+    AccountGroup.UUID gx = groupOperations.newGroup().create();
+    assertThat(gApi.groups().id(gx.get()).includedGroups()).isEmpty();
   }
 
   @Test
   public void includeNonExistingGroup() throws Exception {
-    String gx = createUniqueGroup();
+    AccountGroup.UUID gx = groupOperations.newGroup().create();
     exception.expect(UnprocessableEntityException.class);
-    gApi.groups().id(gx).addGroups("non-existing");
+    gApi.groups().id(gx.get()).addGroups("non-existing");
   }
 
   @Test
   public void listNonEmptyGroupIncludes() throws Exception {
-    String gx = createUniqueGroup();
-    String gy = createUniqueGroup();
-    String gz = createUniqueGroup();
-    gApi.groups().id(gx).addGroups(gy);
-    gApi.groups().id(gx).addGroups(gz);
-    assertIncludes(gApi.groups().id(gx).includedGroups(), gy, gz);
+    AccountGroup.UUID gz = groupOperations.newGroup().create();
+    AccountGroup.UUID gy = groupOperations.newGroup().create();
+    AccountGroup.UUID gx = groupOperations.newGroup().subgroups(gy, gz).create();
+
+    List<GroupInfo> includes = gApi.groups().id(gx.get()).includedGroups();
+
+    String gyName = groupOperations.group(gy).get().name();
+    String gzName = groupOperations.group(gz).get().name();
+    assertIncludes(includes, gyName, gzName);
   }
 
   @Test
   public void listOneIncludeMember() throws Exception {
-    String gx = createUniqueGroup();
-    String gy = createUniqueGroup();
-    gApi.groups().id(gx).addGroups(gy);
-    assertIncludes(gApi.groups().id(gx).includedGroups(), gy);
+    AccountGroup.UUID gy = groupOperations.newGroup().create();
+    AccountGroup.UUID gx = groupOperations.newGroup().subgroups(gy).create();
+
+    List<GroupInfo> includes = gApi.groups().id(gx.get()).includedGroups();
+
+    String gyName = groupOperations.group(gy).get().name();
+    assertIncludes(includes, gyName);
   }
 
   @Test
@@ -676,101 +666,101 @@
 
   @Test
   public void listEmptyGroupMembers() throws Exception {
-    String group = createUniqueGroup();
-    assertThat(gApi.groups().id(group).members()).isEmpty();
+    AccountGroup.UUID group = groupOperations.newGroup().create();
+    assertThat(gApi.groups().id(group.get()).members()).isEmpty();
   }
 
   @Test
   public void listNonEmptyGroupMembers() throws Exception {
-    String group = createUniqueGroup();
+    AccountGroup.UUID group = groupOperations.newGroup().create();
     String user1 = name("user1");
     accountOperations.newAccount().username(user1).create();
     String user2 = name("user2");
     accountOperations.newAccount().username(user2).create();
-    gApi.groups().id(group).addMembers(user1, user2);
+    gApi.groups().id(group.get()).addMembers(user1, user2);
 
-    assertMembers(gApi.groups().id(group).members(), user1, user2);
+    assertMembers(gApi.groups().id(group.get()).members(), user1, user2);
   }
 
   @Test
   public void listOneGroupMember() throws Exception {
-    String group = createUniqueGroup();
+    AccountGroup.UUID group = groupOperations.newGroup().create();
     String user = name("user1");
     accountOperations.newAccount().username(user).create();
-    gApi.groups().id(group).addMembers(user);
+    gApi.groups().id(group.get()).addMembers(user);
 
-    assertMembers(gApi.groups().id(group).members(), user);
+    assertMembers(gApi.groups().id(group.get()).members(), user);
   }
 
   @Test
   public void listGroupMembersRecursively() throws Exception {
-    String gx = createUniqueGroup();
+    AccountGroup.UUID gx = groupOperations.newGroup().create();
     String ux = name("ux");
     accountOperations.newAccount().username(ux).create();
-    gApi.groups().id(gx).addMembers(ux);
+    gApi.groups().id(gx.get()).addMembers(ux);
 
-    String gy = createUniqueGroup();
+    AccountGroup.UUID gy = groupOperations.newGroup().create();
     String uy = name("uy");
     accountOperations.newAccount().username(uy).create();
-    gApi.groups().id(gy).addMembers(uy);
+    gApi.groups().id(gy.get()).addMembers(uy);
 
-    String gz = createUniqueGroup();
+    AccountGroup.UUID gz = groupOperations.newGroup().create();
     String uz = name("uz");
     accountOperations.newAccount().username(uz).create();
-    gApi.groups().id(gz).addMembers(uz);
+    gApi.groups().id(gz.get()).addMembers(uz);
 
-    gApi.groups().id(gx).addGroups(gy);
-    gApi.groups().id(gy).addGroups(gz);
-    assertMembers(gApi.groups().id(gx).members(), ux);
-    assertMembers(gApi.groups().id(gx).members(true), ux, uy, uz);
+    gApi.groups().id(gx.get()).addGroups(gy.get());
+    gApi.groups().id(gy.get()).addGroups(gz.get());
+    assertMembers(gApi.groups().id(gx.get()).members(), ux);
+    assertMembers(gApi.groups().id(gx.get()).members(true), ux, uy, uz);
   }
 
   @Test
   public void usersSeeTheirDirectMembershipWhenListingMembersRecursively() throws Exception {
-    String group = createUniqueGroup();
-    gApi.groups().id(group).addMembers(user.username);
+    AccountGroup.UUID group = groupOperations.newGroup().create();
+    gApi.groups().id(group.get()).addMembers(user.username);
 
     setApiUser(user);
-    assertMembers(gApi.groups().id(group).members(true), user.fullName);
+    assertMembers(gApi.groups().id(group.get()).members(true), user.fullName);
   }
 
   @Test
   public void usersDoNotSeeTheirIndirectMembershipWhenListingMembersRecursively() throws Exception {
-    String group1 = createUniqueGroup();
-    String group2 = createUniqueGroup();
-    gApi.groups().id(group1).addGroups(group2);
-    gApi.groups().id(group2).addMembers(user.username);
+    AccountGroup.UUID group1 = groupOperations.newGroup().ownerGroupUuid(adminGroupUuid()).create();
+    AccountGroup.UUID group2 = groupOperations.newGroup().ownerGroupUuid(adminGroupUuid()).create();
+    gApi.groups().id(group1.get()).addGroups(group2.get());
+    gApi.groups().id(group2.get()).addMembers(user.username);
 
     setApiUser(user);
-    List<AccountInfo> listedMembers = gApi.groups().id(group1).members(true);
+    List<AccountInfo> listedMembers = gApi.groups().id(group1.get()).members(true);
 
     assertMembers(listedMembers);
   }
 
   @Test
   public void adminsSeeTheirIndirectMembershipWhenListingMembersRecursively() throws Exception {
-    String ownerGroup = createGroup("ownerGroup", null);
-    String group1 = createGroup("group1", ownerGroup);
-    String group2 = createGroup("group2", ownerGroup);
-    gApi.groups().id(group1).addGroups(group2);
-    gApi.groups().id(group2).addMembers(admin.username);
+    AccountGroup.UUID ownerGroup = groupOperations.newGroup().create();
+    AccountGroup.UUID group1 = groupOperations.newGroup().ownerGroupUuid(ownerGroup).create();
+    AccountGroup.UUID group2 = groupOperations.newGroup().ownerGroupUuid(ownerGroup).create();
+    gApi.groups().id(group1.get()).addGroups(group2.get());
+    gApi.groups().id(group2.get()).addMembers(admin.username);
 
-    List<AccountInfo> listedMembers = gApi.groups().id(group1).members(true);
+    List<AccountInfo> listedMembers = gApi.groups().id(group1.get()).members(true);
 
     assertMembers(listedMembers, admin.fullName);
   }
 
   @Test
   public void ownersSeeTheirIndirectMembershipWhenListingMembersRecursively() throws Exception {
-    String ownerGroup = createGroup("ownerGroup", null);
-    String group1 = createGroup("group1", ownerGroup);
-    String group2 = createGroup("group2", ownerGroup);
-    gApi.groups().id(group1).addGroups(group2);
-    gApi.groups().id(ownerGroup).addMembers(user.username);
-    gApi.groups().id(group2).addMembers(user.username);
+    AccountGroup.UUID ownerGroup = groupOperations.newGroup().create();
+    AccountGroup.UUID group1 = groupOperations.newGroup().ownerGroupUuid(ownerGroup).create();
+    AccountGroup.UUID group2 = groupOperations.newGroup().ownerGroupUuid(ownerGroup).create();
+    gApi.groups().id(group1.get()).addGroups(group2.get());
+    gApi.groups().id(ownerGroup.get()).addMembers(user.username);
+    gApi.groups().id(group2.get()).addMembers(user.username);
 
     setApiUser(user);
-    List<AccountInfo> listedMembers = gApi.groups().id(group1).members(true);
+    List<AccountInfo> listedMembers = gApi.groups().id(group1.get()).members(true);
 
     assertMembers(listedMembers, user.fullName);
   }
@@ -793,18 +783,21 @@
 
   @Test
   public void getGroupsByOwner() throws Exception {
-    String parent = createUniqueGroup();
-    List<String> children =
-        Arrays.asList(createGroup("test-child1", parent), createGroup("test-child2", parent));
+    AccountGroup.UUID parent = groupOperations.newGroup().ownerGroupUuid(adminGroupUuid()).create();
+    List<AccountGroup.UUID> children =
+        Arrays.asList(
+            groupOperations.newGroup().ownerGroupUuid(parent).create(),
+            groupOperations.newGroup().ownerGroupUuid(parent).create());
 
     // By UUID
-    List<GroupInfo> owned = gApi.groups().list().withOwnedBy(groupUuid(parent).get()).get();
-    assertThat(owned.stream().map(g -> g.name).collect(toList()))
+    List<GroupInfo> owned = gApi.groups().list().withOwnedBy(parent.get()).get();
+    assertThat(owned.stream().map(g -> new AccountGroup.UUID(g.id)).collect(toList()))
         .containsExactlyElementsIn(children);
 
     // By name
-    owned = gApi.groups().list().withOwnedBy(parent).get();
-    assertThat(owned.stream().map(g -> g.name).collect(toList()))
+    String parentName = groupOperations.group(parent).get().name();
+    owned = gApi.groups().list().withOwnedBy(parentName).get();
+    assertThat(owned.stream().map(g -> new AccountGroup.UUID(g.id)).collect(toList()))
         .containsExactlyElementsIn(children);
 
     // By group that does not own any others
@@ -952,8 +945,8 @@
   }
 
   /**
-   * @Sandboxed is used by this test because it deletes a group reference which introduces an
-   * inconsistency for the group storage. Once group deletion is supported, this test should be
+   * {@code @Sandboxed} is used by this test because it deletes a group reference which introduces
+   * an inconsistency for the group storage. Once group deletion is supported, this test should be
    * updated to use the API instead.
    */
   @Test
@@ -1001,8 +994,7 @@
     TestAccount groupOwner = accountCreator.user2();
     GroupInput in = new GroupInput();
     in.name = name("group");
-    in.members =
-        Collections.singleton(groupOwner).stream().map(u -> u.id.toString()).collect(toList());
+    in.members = Stream.of(groupOwner).map(u -> u.id.toString()).collect(toList());
     in.visibleToAll = true;
     GroupInfo group = gApi.groups().create(in).get();
 
@@ -1045,7 +1037,7 @@
 
   @Test
   public void pushToGroupsBranchForNonAllUsersRepo() throws Exception {
-    assertCreateGroupBranch(project, null);
+    assertCreateGroupBranch(project);
     String groupRef =
         RefNames.refsGroups(new AccountGroup.UUID(gApi.groups().create(name("foo")).get().id));
     createBranch(project, groupRef);
@@ -1054,7 +1046,7 @@
 
   @Test
   public void pushToDeletedGroupsBranchForNonAllUsersRepo() throws Exception {
-    assertCreateGroupBranch(project, null);
+    assertCreateGroupBranch(project);
     String groupRef =
         RefNames.refsDeletedGroups(
             new AccountGroup.UUID(gApi.groups().create(name("foo")).get().id));
@@ -1092,8 +1084,7 @@
     }
   }
 
-  private void assertCreateGroupBranch(Project.NameKey project, String expectedErrorOnCreate)
-      throws Exception {
+  private void assertCreateGroupBranch(Project.NameKey project) throws Exception {
     grant(project, RefNames.REFS_GROUPS + "*", Permission.CREATE, false, REGISTERED_USERS);
     grant(project, RefNames.REFS_GROUPS + "*", Permission.PUSH, false, REGISTERED_USERS);
     TestRepository<InMemoryRepository> repo = cloneProject(project);
@@ -1102,11 +1093,7 @@
             .create(db, admin.getIdent(), repo, "Update group", "arbitraryFile.txt", "some content")
             .setParents(ImmutableList.of())
             .to(RefNames.REFS_GROUPS + name("bar"));
-    if (expectedErrorOnCreate != null) {
-      r.assertErrorStatus(expectedErrorOnCreate);
-    } else {
-      r.assertOkStatus();
-    }
+    r.assertOkStatus();
   }
 
   @Test
@@ -1495,7 +1482,7 @@
   private void assertMembers(String group, TestAccount... expectedMembers) throws Exception {
     assertMembers(
         gApi.groups().id(group).members(),
-        TestAccount.names(expectedMembers).stream().toArray(String[]::new));
+        TestAccount.names(expectedMembers).toArray(new String[0]));
     assertAccountInfos(Arrays.asList(expectedMembers), gApi.groups().id(group).members());
   }
 
@@ -1505,24 +1492,12 @@
         .inOrder();
   }
 
-  private void assertNoMembers(String group) throws Exception {
-    assertThat(gApi.groups().id(group).members()).isEmpty();
-  }
-
-  private void assertIncludes(String group, String... expectedNames) throws Exception {
-    assertIncludes(gApi.groups().id(group).includedGroups(), expectedNames);
-  }
-
-  private static void assertIncludes(Iterable<GroupInfo> includes, String... expectedNames) {
-    Iterable<String> names = Iterables.transform(includes, i -> i.name);
+  private static void assertIncludes(List<GroupInfo> includes, String... expectedNames) {
+    List<String> names = includes.stream().map(i -> i.name).collect(toImmutableList());
     assertThat(names).containsExactlyElementsIn(Arrays.asList(expectedNames));
     assertThat(names).isOrdered();
   }
 
-  private void assertNoIncludes(String group) throws Exception {
-    assertThat(gApi.groups().id(group).includedGroups()).isEmpty();
-  }
-
   private void assertBadRequest(ListRequest req) throws Exception {
     try {
       req.get();
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java b/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java
index e4194a3..f0296fc 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CheckAccessIT.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.config.AccessCheckInfo;
 import com.google.gerrit.extensions.api.config.AccessCheckInput;
@@ -40,7 +41,7 @@
 import org.junit.Test;
 
 public class CheckAccessIT extends AbstractDaemonTest {
-
+  @Inject private ProjectOperations projectOperations;
   @Inject private GroupOperations groupOperations;
 
   private Project.NameKey normalProject;
@@ -50,9 +51,9 @@
 
   @Before
   public void setUp() throws Exception {
-    normalProject = createProject("normal");
-    secretProject = createProject("secret");
-    secretRefProject = createProject("secretRef");
+    normalProject = projectOperations.newProject().create();
+    secretProject = projectOperations.newProject().create();
+    secretRefProject = projectOperations.newProject().create();
     AccountGroup.UUID privilegedGroupUuid =
         groupOperations.newGroup().name(name("privilegedGroup")).create();
 
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index 995f89b..aa1ad7b 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
@@ -73,6 +74,7 @@
   private static final String JIRA_MATCH = "(jira\\\\s+#?)(\\\\d+)";
 
   @Inject private DynamicSet<ProjectIndexedListener> projectIndexedListeners;
+  @Inject private ProjectOperations projectOperations;
 
   @Inject
   @IndexExecutor(BATCH)
@@ -429,7 +431,7 @@
 
   @Test
   public void reindexProject() throws Exception {
-    createProject("child", project);
+    projectOperations.newProject().parent(project).create();
     projectIndexedCounter.clear();
 
     gApi.projects().name(allProjects.get()).index(false);
@@ -438,8 +440,8 @@
 
   @Test
   public void reindexProjectWithChildren() throws Exception {
-    Project.NameKey middle = createProject("middle", project);
-    Project.NameKey leave = createProject("leave", middle);
+    Project.NameKey middle = projectOperations.newProject().parent(project).create();
+    Project.NameKey leave = projectOperations.newProject().parent(middle).create();
     projectIndexedCounter.clear();
 
     gApi.projects().name(project.get()).index(true);
@@ -473,7 +475,7 @@
   @Test
   @GerritConfig(name = "receive.inheritProjectMaxObjectSizeLimit", value = "true")
   public void maxObjectSizeIsInheritedFromParentProject() throws Exception {
-    Project.NameKey child = createProject(name("child"), project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     ConfigInfo info = setMaxObjectSize("100k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
@@ -489,7 +491,7 @@
 
   @Test
   public void maxObjectSizeIsNotInheritedFromParentProject() throws Exception {
-    Project.NameKey child = createProject(name("child"), project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     ConfigInfo info = setMaxObjectSize("100k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
@@ -504,7 +506,7 @@
 
   @Test
   public void maxObjectSizeOverridesParentProjectWhenNotSetOnParent() throws Exception {
-    Project.NameKey child = createProject(name("child"), project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     ConfigInfo info = setMaxObjectSize("0");
     assertThat(info.maxObjectSizeLimit.value).isNull();
@@ -519,7 +521,7 @@
 
   @Test
   public void maxObjectSizeOverridesParentProjectWhenLower() throws Exception {
-    Project.NameKey child = createProject(name("child"), project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     ConfigInfo info = setMaxObjectSize("200k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
@@ -535,7 +537,7 @@
   @Test
   @GerritConfig(name = "receive.inheritProjectMaxObjectSizeLimit", value = "true")
   public void maxObjectSizeDoesNotOverrideParentProjectWhenHigher() throws Exception {
-    Project.NameKey child = createProject(name("child"), project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     ConfigInfo info = setMaxObjectSize("100k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
@@ -552,7 +554,7 @@
   @Test
   @GerritConfig(name = "receive.maxObjectSizeLimit", value = "200k")
   public void maxObjectSizeIsInheritedFromGlobalConfig() throws Exception {
-    Project.NameKey child = createProject(name("child"), project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     ConfigInfo info = getConfig();
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
@@ -577,7 +579,7 @@
   @Test
   @GerritConfig(name = "receive.maxObjectSizeLimit", value = "300k")
   public void inheritedMaxObjectSizeOverridesGlobalConfigWhenLower() throws Exception {
-    Project.NameKey child = createProject(name("child"), project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     ConfigInfo info = setMaxObjectSize("200k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
@@ -594,7 +596,7 @@
   @GerritConfig(name = "receive.maxObjectSizeLimit", value = "200k")
   @GerritConfig(name = "receive.inheritProjectMaxObjectSizeLimit", value = "true")
   public void maxObjectSizeDoesNotOverrideGlobalConfigWhenHigher() throws Exception {
-    Project.NameKey child = createProject(name("child"), project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     ConfigInfo info = setMaxObjectSize("300k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
index 6fde012..6b511f6 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
@@ -19,6 +19,7 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.RefState;
@@ -45,6 +46,7 @@
   @Inject private ProjectIndexCollection indexes;
   @Inject private IndexConfig indexConfig;
   @Inject private StalenessChecker stalenessChecker;
+  @Inject private ProjectOperations projectOperations;
 
   private static final ImmutableSet<String> FIELDS =
       ImmutableSet.of(ProjectField.NAME.getName(), ProjectField.REF_STATE.getName());
@@ -96,8 +98,8 @@
 
   @Test
   public void stalenessChecker_hierarchyChange_isStale() throws Exception {
-    Project.NameKey p1 = createProject("p1", allProjects);
-    Project.NameKey p2 = createProject("p2", allProjects);
+    Project.NameKey p1 = projectOperations.newProject().create();
+    Project.NameKey p2 = projectOperations.newProject().create();
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig().getProject().setParentName(p1);
       u.save();
diff --git a/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java b/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
index 3295f1a..c21798c 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/SetParentIT.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -27,14 +28,17 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.inject.Inject;
 import org.junit.Test;
 
 @NoHttpd
 public class SetParentIT extends AbstractDaemonTest {
 
+  @Inject private ProjectOperations projectOperations;
+
   @Test
   public void setParentNotAllowed() throws Exception {
-    String parent = createProject("parent", null, true).get();
+    String parent = projectOperations.newProject().create().get();
     setApiUser(user);
     exception.expect(AuthException.class);
     gApi.projects().name(project.get()).parent(parent);
@@ -43,7 +47,7 @@
   @Test
   @GerritConfig(name = "receive.allowProjectOwnersToChangeParent", value = "true")
   public void setParentNotAllowedForNonOwners() throws Exception {
-    String parent = createProject("parent", null, true).get();
+    String parent = projectOperations.newProject().create().get();
     setApiUser(user);
     exception.expect(AuthException.class);
     gApi.projects().name(project.get()).parent(parent);
@@ -52,7 +56,7 @@
   @Test
   @GerritConfig(name = "receive.allowProjectOwnersToChangeParent", value = "true")
   public void setParentAllowedByAdminWhenAllowProjectOwnersEnabled() throws Exception {
-    String parent = createProject("parent", null, true).get();
+    String parent = projectOperations.newProject().create().get();
 
     gApi.projects().name(project.get()).parent(parent);
     assertThat(gApi.projects().name(project.get()).parent()).isEqualTo(parent);
@@ -67,7 +71,7 @@
   @Test
   @GerritConfig(name = "receive.allowProjectOwnersToChangeParent", value = "true")
   public void setParentAllowedForOwners() throws Exception {
-    String parent = createProject("parent", null, true).get();
+    String parent = projectOperations.newProject().create().get();
     setApiUser(user);
     grant(project, "refs/*", Permission.OWNER, false, SystemGroupBackend.REGISTERED_USERS);
     gApi.projects().name(project.get()).parent(parent);
@@ -76,7 +80,7 @@
 
   @Test
   public void setParent() throws Exception {
-    String parent = createProject("parent", null, true).get();
+    String parent = projectOperations.newProject().create().get();
 
     gApi.projects().name(project.get()).parent(parent);
     assertThat(gApi.projects().name(project.get()).parent()).isEqualTo(parent);
@@ -104,7 +108,7 @@
 
   @Test
   public void setParentToOwnChildNotAllowed() throws Exception {
-    String child = createProject("child", project, true).get();
+    String child = projectOperations.newProject().parent(project).create().get();
     exception.expect(ResourceConflictException.class);
     exception.expectMessage("cycle exists between");
     gApi.projects().name(project.get()).parent(child);
@@ -112,8 +116,8 @@
 
   @Test
   public void setParentToGrandchildNotAllowed() throws Exception {
-    Project.NameKey child = createProject("child", project, true);
-    String grandchild = createProject("grandchild", child, true).get();
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
+    String grandchild = projectOperations.newProject().parent(child).create().get();
     exception.expect(ResourceConflictException.class);
     exception.expectMessage("cycle exists between");
     gApi.projects().name(project.get()).parent(grandchild);
@@ -137,7 +141,7 @@
   public void setParentForAllUsersMustBeAllProjects() throws Exception {
     gApi.projects().name(allUsers.get()).parent(allProjects.get());
 
-    String parent = createProject("parent", null, true).get();
+    String parent = projectOperations.newProject().create().get();
 
     exception.expect(BadRequestException.class);
     exception.expectMessage("All-Users must inherit from All-Projects");
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index bde042f..20e6d3f 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -795,16 +795,11 @@
     assertThat(result).containsKey(ReviewerState.REVIEWER);
     List<Integer> reviewers =
         result.get(ReviewerState.REVIEWER).stream().map(a -> a._accountId).collect(toList());
-    if (notesMigration.readChanges()) {
-      assertThat(result).containsKey(ReviewerState.CC);
-      List<Integer> ccs =
-          result.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
-      assertThat(ccs).containsExactly(user.id.get());
-      assertThat(reviewers).containsExactly(admin.id.get(), accountCreator.admin2().id.get());
-    } else {
-      assertThat(reviewers)
-          .containsExactly(user.id.get(), admin.id.get(), accountCreator.admin2().id.get());
-    }
+    assertThat(result).containsKey(ReviewerState.CC);
+    List<Integer> ccs =
+        result.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
+    assertThat(ccs).containsExactly(user.id.get());
+    assertThat(reviewers).containsExactly(admin.id.get(), accountCreator.admin2().id.get());
   }
 
   @Test
@@ -1491,7 +1486,7 @@
 
   private PushOneCommit.Result createCherryPickableMerge(
       String parent1FileName, String parent2FileName) throws Exception {
-    RevCommit initialCommit = getHead(repo());
+    RevCommit initialCommit = getHead(repo(), "HEAD");
 
     String branchAName = "branchA";
     createBranch(new Branch.NameKey(project, branchAName));
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
index d713db6..c5251f7 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.api.revision;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
 import static com.google.gerrit.extensions.common.testing.EditInfoSubject.assertThat;
 import static com.google.gerrit.extensions.common.testing.RobotCommentInfoSubject.assertThatList;
@@ -38,7 +37,6 @@
 import com.google.gerrit.extensions.common.RobotCommentInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -85,8 +83,6 @@
 
   @Test
   public void retrievingRobotCommentsBeforeAddingAnyDoesNotRaiseAnException() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     Map<String, List<RobotCommentInfo>> robotComments =
         gApi.changes().id(changeId).current().robotComments();
 
@@ -96,8 +92,6 @@
 
   @Test
   public void addedRobotCommentsCanBeRetrieved() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     RobotCommentInput in = createRobotCommentInput();
     addRobotComment(changeId, in);
 
@@ -110,8 +104,6 @@
 
   @Test
   public void addedRobotCommentsCanBeRetrievedByChange() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     RobotCommentInput in = createRobotCommentInput();
     addRobotComment(changeId, in);
 
@@ -133,8 +125,6 @@
 
   @Test
   public void robotCommentsCanBeRetrievedAsList() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     RobotCommentInput robotCommentInput = createRobotCommentInput();
     addRobotComment(changeId, robotCommentInput);
 
@@ -148,8 +138,6 @@
 
   @Test
   public void specificRobotCommentCanBeRetrieved() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     RobotCommentInput robotCommentInput = createRobotCommentInput();
     addRobotComment(changeId, robotCommentInput);
 
@@ -163,8 +151,6 @@
 
   @Test
   public void robotCommentWithoutOptionalFieldsCanBeAdded() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     RobotCommentInput in = createRobotCommentInputWithMandatoryFields();
     addRobotComment(changeId, in);
 
@@ -176,8 +162,6 @@
 
   @Test
   public void hugeRobotCommentIsRejected() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     int defaultSizeLimit = 1024 * 1024;
     int sizeOfRest = 451;
     fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - sizeOfRest + 1);
@@ -189,8 +173,6 @@
 
   @Test
   public void reasonablyLargeRobotCommentIsAccepted() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     int defaultSizeLimit = 1024 * 1024;
     int sizeOfRest = 451;
     fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - sizeOfRest);
@@ -204,8 +186,6 @@
   @Test
   @GerritConfig(name = "change.robotCommentSizeLimit", value = "10k")
   public void maximumAllowedSizeOfRobotCommentCanBeAdjusted() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     int sizeLimit = 10 * 1024;
     fixReplacementInfo.replacement = getStringFor(sizeLimit);
 
@@ -217,8 +197,6 @@
   @Test
   @GerritConfig(name = "change.robotCommentSizeLimit", value = "0")
   public void zeroForMaximumAllowedSizeOfRobotCommentRemovesRestriction() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     int defaultSizeLimit = 1024 * 1024;
     fixReplacementInfo.replacement = getStringFor(defaultSizeLimit);
 
@@ -232,8 +210,6 @@
   @GerritConfig(name = "change.robotCommentSizeLimit", value = "-1")
   public void negativeValueForMaximumAllowedSizeOfRobotCommentRemovesRestriction()
       throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     int defaultSizeLimit = 1024 * 1024;
     fixReplacementInfo.replacement = getStringFor(defaultSizeLimit);
 
@@ -245,8 +221,6 @@
 
   @Test
   public void addedFixSuggestionCanBeRetrieved() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     addRobotComment(changeId, withFixRobotCommentInput);
     List<RobotCommentInfo> robotCommentInfos = getRobotComments();
 
@@ -255,8 +229,6 @@
 
   @Test
   public void fixIdIsGeneratedForFixSuggestion() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     addRobotComment(changeId, withFixRobotCommentInput);
     List<RobotCommentInfo> robotCommentInfos = getRobotComments();
 
@@ -270,8 +242,6 @@
 
   @Test
   public void descriptionOfFixSuggestionIsAcceptedAsIs() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     addRobotComment(changeId, withFixRobotCommentInput);
     List<RobotCommentInfo> robotCommentInfos = getRobotComments();
 
@@ -284,8 +254,6 @@
 
   @Test
   public void descriptionOfFixSuggestionIsMandatory() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixSuggestionInfo.description = null;
 
     exception.expect(BadRequestException.class);
@@ -298,8 +266,6 @@
 
   @Test
   public void addedFixReplacementCanBeRetrieved() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     addRobotComment(changeId, withFixRobotCommentInput);
     List<RobotCommentInfo> robotCommentInfos = getRobotComments();
 
@@ -312,8 +278,6 @@
 
   @Test
   public void fixReplacementsAreMandatory() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixSuggestionInfo.replacements = Collections.emptyList();
 
     exception.expect(BadRequestException.class);
@@ -327,8 +291,6 @@
 
   @Test
   public void pathOfFixReplacementIsAcceptedAsIs() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     addRobotComment(changeId, withFixRobotCommentInput);
 
     List<RobotCommentInfo> robotCommentInfos = getRobotComments();
@@ -343,8 +305,6 @@
 
   @Test
   public void pathOfFixReplacementIsMandatory() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.path = null;
 
     exception.expect(BadRequestException.class);
@@ -357,8 +317,6 @@
 
   @Test
   public void rangeOfFixReplacementIsAcceptedAsIs() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     addRobotComment(changeId, withFixRobotCommentInput);
 
     List<RobotCommentInfo> robotCommentInfos = getRobotComments();
@@ -373,8 +331,6 @@
 
   @Test
   public void rangeOfFixReplacementIsMandatory() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.range = null;
 
     exception.expect(BadRequestException.class);
@@ -387,8 +343,6 @@
 
   @Test
   public void rangeOfFixReplacementNeedsToBeValid() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.range = createRange(13, 9, 5, 10);
     exception.expect(BadRequestException.class);
     exception.expectMessage("Range (13:9 - 5:10)");
@@ -398,8 +352,6 @@
   @Test
   public void rangesOfFixReplacementsOfSameFixSuggestionForSameFileMayNotOverlap()
       throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
     fixReplacementInfo1.path = FILE_NAME;
     fixReplacementInfo1.range = createRange(2, 0, 3, 1);
@@ -422,8 +374,6 @@
   @Test
   public void rangesOfFixReplacementsOfSameFixSuggestionForDifferentFileMayOverlap()
       throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
     fixReplacementInfo1.path = FILE_NAME;
     fixReplacementInfo1.range = createRange(2, 0, 3, 1);
@@ -447,8 +397,6 @@
   @Test
   public void rangesOfFixReplacementsOfDifferentFixSuggestionsForSameFileMayOverlap()
       throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
     fixReplacementInfo1.path = FILE_NAME;
     fixReplacementInfo1.range = createRange(2, 0, 3, 1);
@@ -472,8 +420,6 @@
 
   @Test
   public void fixReplacementsDoNotNeedToBeOrderedAccordingToRange() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
     fixReplacementInfo1.path = FILE_NAME;
     fixReplacementInfo1.range = createRange(2, 0, 3, 0);
@@ -501,8 +447,6 @@
 
   @Test
   public void replacementStringOfFixReplacementIsAcceptedAsIs() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     addRobotComment(changeId, withFixRobotCommentInput);
 
     List<RobotCommentInfo> robotCommentInfos = getRobotComments();
@@ -517,8 +461,6 @@
 
   @Test
   public void replacementStringOfFixReplacementIsMandatory() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.replacement = null;
 
     exception.expect(BadRequestException.class);
@@ -532,8 +474,6 @@
 
   @Test
   public void fixWithinALineCanBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.path = FILE_NAME;
     fixReplacementInfo.replacement = "Modified content";
     fixReplacementInfo.range = createRange(3, 1, 3, 3);
@@ -557,8 +497,6 @@
 
   @Test
   public void fixSpanningMultipleLinesCanBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.path = FILE_NAME;
     fixReplacementInfo.replacement = "Modified content\n5";
     fixReplacementInfo.range = createRange(3, 2, 5, 3);
@@ -581,8 +519,6 @@
 
   @Test
   public void fixWithTwoCloseReplacementsOnSameFileCanBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
     fixReplacementInfo1.path = FILE_NAME;
     fixReplacementInfo1.range = createRange(2, 0, 3, 0);
@@ -615,8 +551,6 @@
 
   @Test
   public void twoFixesOnSameFileCanBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
     fixReplacementInfo1.path = FILE_NAME;
     fixReplacementInfo1.range = createRange(2, 0, 3, 0);
@@ -650,8 +584,6 @@
 
   @Test
   public void twoConflictingFixesOnSameFileCannotBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
     fixReplacementInfo1.path = FILE_NAME;
     fixReplacementInfo1.range = createRange(2, 0, 3, 1);
@@ -679,8 +611,6 @@
 
   @Test
   public void twoFixesOfSameRobotCommentCanBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
     fixReplacementInfo1.path = FILE_NAME;
     fixReplacementInfo1.range = createRange(2, 0, 3, 0);
@@ -714,8 +644,6 @@
 
   @Test
   public void fixReferringToDifferentFileThanRobotCommentCanBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.path = FILE_NAME2;
     fixReplacementInfo.range = createRange(2, 0, 3, 0);
     fixReplacementInfo.replacement = "Modified content\n";
@@ -736,8 +664,6 @@
 
   @Test
   public void fixInvolvingTwoFilesCanBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
     fixReplacementInfo1.path = FILE_NAME;
     fixReplacementInfo1.range = createRange(2, 0, 3, 0);
@@ -775,8 +701,6 @@
 
   @Test
   public void fixReferringToNonExistentFileCannotBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.path = "a_non_existent_file.txt";
     fixReplacementInfo.range = createRange(1, 0, 2, 0);
     fixReplacementInfo.replacement = "Modified content\n";
@@ -792,8 +716,6 @@
 
   @Test
   public void fixOnPreviousPatchSetWithoutChangeEditCannotBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.path = FILE_NAME;
     fixReplacementInfo.replacement = "Modified content";
     fixReplacementInfo.range = createRange(3, 1, 3, 3);
@@ -815,8 +737,6 @@
 
   @Test
   public void fixOnPreviousPatchSetWithExistingChangeEditCanBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     // Create an empty change edit.
     gApi.changes().id(changeId).edit().create();
 
@@ -849,8 +769,6 @@
   @Test
   public void fixOnCurrentPatchSetWithChangeEditOnPreviousPatchSetCannotBeApplied()
       throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     // Create an empty change edit.
     gApi.changes().id(changeId).edit().create();
 
@@ -874,8 +792,6 @@
 
   @Test
   public void fixDoesNotModifyCommitMessageOfChangeEdit() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     String changeEditCommitMessage = "This is the commit message of the change edit.\n";
     gApi.changes().id(changeId).edit().modifyCommitMessage(changeEditCommitMessage);
 
@@ -897,8 +813,6 @@
 
   @Test
   public void applyingFixTwiceIsIdempotent() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.path = FILE_NAME;
     fixReplacementInfo.replacement = "Modified content";
     fixReplacementInfo.range = createRange(3, 1, 3, 3);
@@ -922,8 +836,6 @@
 
   @Test
   public void nonExistentFixCannotBeApplied() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.path = FILE_NAME;
     fixReplacementInfo.replacement = "Modified content";
     fixReplacementInfo.range = createRange(3, 1, 3, 3);
@@ -941,8 +853,6 @@
 
   @Test
   public void applyingFixReturnsEditInfoForCreatedChangeEdit() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     fixReplacementInfo.path = FILE_NAME;
     fixReplacementInfo.replacement = "Modified content";
     fixReplacementInfo.range = createRange(3, 1, 3, 3);
@@ -964,8 +874,6 @@
 
   @Test
   public void applyingFixOnTopOfChangeEditReturnsEditInfoForUpdatedChangeEdit() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     gApi.changes().id(changeId).edit().create();
 
     fixReplacementInfo.path = FILE_NAME;
@@ -989,7 +897,6 @@
 
   @Test
   public void createdChangeEditIsBasedOnCurrentPatchSet() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     String currentRevision = gApi.changes().id(changeId).get().currentRevision;
 
     fixReplacementInfo.path = FILE_NAME;
@@ -1008,25 +915,7 @@
   }
 
   @Test
-  public void robotCommentsNotSupportedWithoutNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-
-    RobotCommentInput in = createRobotCommentInput();
-    ReviewInput reviewInput = new ReviewInput();
-    Map<String, List<RobotCommentInput>> robotComments = new HashMap<>();
-    robotComments.put(in.path, ImmutableList.of(in));
-    reviewInput.robotComments = robotComments;
-    reviewInput.message = "comment test";
-
-    exception.expect(MethodNotAllowedException.class);
-    exception.expectMessage("robot comments not supported");
-    gApi.changes().id(changeId).current().review(reviewInput);
-  }
-
-  @Test
   public void queryChangesWithCommentCounts() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     PushOneCommit.Result r1 = createChange();
     PushOneCommit.Result r2 =
         pushFactory
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index 96566f6..37bfd42 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -34,6 +34,7 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.RawInputUtil;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.Permission;
@@ -93,6 +94,7 @@
   private static final byte[] CONTENT_NEW2 = CONTENT_NEW2_STR.getBytes(UTF_8);
 
   @Inject private SchemaFactory<ReviewDb> reviewDbProvider;
+  @Inject private ProjectOperations projectOperations;
 
   private String changeId;
   private String changeId2;
@@ -679,7 +681,7 @@
   @Test
   public void createEditWithoutPushPatchSetPermission() throws Exception {
     // Create new project with clean permissions
-    Project.NameKey p = createProject("addPatchSetEdit");
+    Project.NameKey p = projectOperations.newProject().create();
     // Clone repository as user
     TestRepository<InMemoryRepository> userTestRepo = cloneProject(p, user);
 
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index a5ff746..5878407 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -18,7 +18,6 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
 import static com.google.gerrit.acceptance.GitUtil.assertPushRejected;
 import static com.google.gerrit.acceptance.GitUtil.pushHead;
@@ -220,6 +219,60 @@
 
   @Test
   @TestProjectInput(createEmptyCommit = false)
+  public void pushInitialCommitSeriesForMasterBranch() throws Exception {
+    testPushInitialCommitSeriesForMasterBranch();
+  }
+
+  @Test
+  @TestProjectInput(createEmptyCommit = false)
+  public void pushInitialCommitSeriesForMasterBranchWithCreateNewChangeForAllNotInTarget()
+      throws Exception {
+    enableCreateNewChangeForAllNotInTarget();
+    testPushInitialCommitSeriesForMasterBranch();
+  }
+
+  private void testPushInitialCommitSeriesForMasterBranch() throws Exception {
+    RevCommit c = testRepo.commit().message("Initial commit").insertChangeId().create();
+    String id = GitUtil.getChangeId(testRepo, c).get();
+    testRepo.reset(c);
+
+    RevCommit c2 = testRepo.commit().parent(c).message("Second commit").insertChangeId().create();
+    String id2 = GitUtil.getChangeId(testRepo, c2).get();
+    testRepo.reset(c2);
+
+    String r = "refs/for/master";
+    PushResult pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+
+    ChangeInfo change = gApi.changes().id(id).info();
+    assertThat(change.branch).isEqualTo("master");
+    assertThat(change.status).isEqualTo(ChangeStatus.NEW);
+
+    ChangeInfo change2 = gApi.changes().id(id2).info();
+    assertThat(change2.branch).isEqualTo("master");
+    assertThat(change2.status).isEqualTo(ChangeStatus.NEW);
+
+    try (Repository repo = repoManager.openRepository(project)) {
+      assertThat(repo.resolve("master")).isNull();
+    }
+
+    gApi.changes().id(change.id).current().review(ReviewInput.approve());
+    gApi.changes().id(change.id).current().submit();
+
+    try (Repository repo = repoManager.openRepository(project)) {
+      assertThat(repo.resolve("master")).isEqualTo(c);
+    }
+
+    gApi.changes().id(change2.id).current().review(ReviewInput.approve());
+    gApi.changes().id(change2.id).current().submit();
+
+    try (Repository repo = repoManager.openRepository(project)) {
+      assertThat(repo.resolve("master")).isEqualTo(c2);
+    }
+  }
+
+  @Test
+  @TestProjectInput(createEmptyCommit = false)
   public void validateConnected() throws Exception {
     RevCommit c = testRepo.commit().message("Initial commit").insertChangeId().create();
     testRepo.reset(c);
@@ -489,12 +542,7 @@
     r.assertOkStatus();
     assertThat(sender.getMessages()).hasSize(1);
     Message m = sender.getMessages().get(0);
-    if (notesMigration.readChanges()) {
-      assertThat(m.rcpt()).containsExactly(user.emailAddress);
-    } else {
-      // CCs are considered reviewers in the storage layer and so get notified.
-      assertThat(m.rcpt()).containsExactly(user.emailAddress, user2.emailAddress);
-    }
+    assertThat(m.rcpt()).containsExactly(user.emailAddress);
 
     sender.clear();
     r = pushTo(pushSpec + ",notify=" + NotifyHandling.ALL);
@@ -585,23 +633,19 @@
 
     PushOneCommit.Result r =
         pushTo("refs/for/master%cc=non.existing.1@example.com,cc=non.existing.2@example.com");
-    if (notesMigration.readChanges()) {
-      r.assertOkStatus();
+    r.assertOkStatus();
 
-      ChangeInfo ci = get(r.getChangeId(), DETAILED_LABELS);
-      ImmutableList<AccountInfo> ccs =
-          firstNonNull(ci.reviewers.get(ReviewerState.CC), ImmutableList.<AccountInfo>of())
-              .stream()
-              .sorted(comparing((AccountInfo a) -> a.email))
-              .collect(toImmutableList());
-      assertThat(ccs).hasSize(2);
-      assertThat(ccs.get(0).email).isEqualTo("non.existing.1@example.com");
-      assertThat(ccs.get(0)._accountId).isNull();
-      assertThat(ccs.get(1).email).isEqualTo("non.existing.2@example.com");
-      assertThat(ccs.get(1)._accountId).isNull();
-    } else {
-      r.assertErrorStatus("non.existing.1@example.com does not identify a registered user");
-    }
+    ChangeInfo ci = get(r.getChangeId(), DETAILED_LABELS);
+    ImmutableList<AccountInfo> ccs =
+        firstNonNull(ci.reviewers.get(ReviewerState.CC), ImmutableList.<AccountInfo>of())
+            .stream()
+            .sorted(comparing((AccountInfo a) -> a.email))
+            .collect(toImmutableList());
+    assertThat(ccs).hasSize(2);
+    assertThat(ccs.get(0).email).isEqualTo("non.existing.1@example.com");
+    assertThat(ccs.get(0)._accountId).isNull();
+    assertThat(ccs.get(1).email).isEqualTo("non.existing.2@example.com");
+    assertThat(ccs.get(1)._accountId).isNull();
   }
 
   @Test
@@ -667,23 +711,19 @@
 
     PushOneCommit.Result r =
         pushTo("refs/for/master%r=non.existing.1@example.com,r=non.existing.2@example.com");
-    if (notesMigration.readChanges()) {
-      r.assertOkStatus();
+    r.assertOkStatus();
 
-      ChangeInfo ci = get(r.getChangeId(), DETAILED_LABELS);
-      ImmutableList<AccountInfo> reviewers =
-          firstNonNull(ci.reviewers.get(ReviewerState.REVIEWER), ImmutableList.<AccountInfo>of())
-              .stream()
-              .sorted(comparing((AccountInfo a) -> a.email))
-              .collect(toImmutableList());
-      assertThat(reviewers).hasSize(2);
-      assertThat(reviewers.get(0).email).isEqualTo("non.existing.1@example.com");
-      assertThat(reviewers.get(0)._accountId).isNull();
-      assertThat(reviewers.get(1).email).isEqualTo("non.existing.2@example.com");
-      assertThat(reviewers.get(1)._accountId).isNull();
-    } else {
-      r.assertErrorStatus("non.existing.1@example.com does not identify a registered user");
-    }
+    ChangeInfo ci = get(r.getChangeId(), DETAILED_LABELS);
+    ImmutableList<AccountInfo> reviewers =
+        firstNonNull(ci.reviewers.get(ReviewerState.REVIEWER), ImmutableList.<AccountInfo>of())
+            .stream()
+            .sorted(comparing((AccountInfo a) -> a.email))
+            .collect(toImmutableList());
+    assertThat(reviewers).hasSize(2);
+    assertThat(reviewers.get(0).email).isEqualTo("non.existing.1@example.com");
+    assertThat(reviewers.get(0)._accountId).isNull();
+    assertThat(reviewers.get(1).email).isEqualTo("non.existing.2@example.com");
+    assertThat(reviewers.get(1)._accountId).isNull();
   }
 
   @Test
@@ -1241,9 +1281,6 @@
 
   @Test
   public void pushForMasterWithHashtags() throws Exception {
-    // Hashtags only work when reading from NoteDB is enabled
-    assume().that(notesMigration.readChanges()).isTrue();
-
     // specify a single hashtag as option
     String hashtag1 = "tag1";
     Set<String> expected = ImmutableSet.of(hashtag1);
@@ -1274,9 +1311,6 @@
 
   @Test
   public void pushForMasterWithMultipleHashtags() throws Exception {
-    // Hashtags only work when reading from NoteDB is enabled
-    assume().that(notesMigration.readChanges()).isTrue();
-
     // specify multiple hashtags as options
     String hashtag1 = "tag1";
     String hashtag2 = "tag2";
@@ -1309,14 +1343,6 @@
   }
 
   @Test
-  public void pushForMasterWithHashtagsNoteDbDisabled() throws Exception {
-    // Push with hashtags should fail when reading from NoteDb is disabled.
-    assume().that(notesMigration.readChanges()).isFalse();
-    PushOneCommit.Result r = pushTo("refs/for/master%hashtag=tag1");
-    r.assertErrorStatus("cannot add hashtags; noteDb is disabled");
-  }
-
-  @Test
   public void pushCommitUsingSignedOffBy() throws Exception {
     PushOneCommit push =
         pushFactory.create(
@@ -1422,14 +1448,7 @@
 
   @Test
   public void pushSameCommitTwice() throws Exception {
-    try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig()
-          .getProject()
-          .setBooleanConfig(
-              BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
-              InheritableBoolean.TRUE);
-      u.save();
-    }
+    enableCreateNewChangeForAllNotInTarget();
 
     PushOneCommit push =
         pushFactory.create(
@@ -1451,14 +1470,7 @@
 
   @Test
   public void pushSameCommitTwiceWhenIndexFailed() throws Exception {
-    try (ProjectConfigUpdate u = updateProject(project)) {
-      u.getConfig()
-          .getProject()
-          .setBooleanConfig(
-              BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
-              InheritableBoolean.TRUE);
-      u.save();
-    }
+    enableCreateNewChangeForAllNotInTarget();
 
     PushOneCommit push =
         pushFactory.create(
@@ -2258,16 +2270,6 @@
   }
 
   @Test
-  public void pushToPublishMagicBranchIsAllowed() throws Exception {
-    // Push to "refs/publish/*" will be a synonym of "refs/for/*".
-    createChange("refs/publish/master");
-    PushOneCommit.Result result = pushTo("refs/publish/master");
-    result.assertOkStatus();
-    assertThat(result.getMessage())
-        .endsWith("Pushing to refs/publish/* is deprecated, use refs/for/* instead.\n");
-  }
-
-  @Test
   public void pushNoteDbRef() throws Exception {
     String ref = "refs/changes/34/1234/meta";
     RevCommit c = testRepo.commit().message("Junk NoteDb commit").create();
@@ -2314,6 +2316,113 @@
     assertPushOk(pr, "refs/heads/permitted");
   }
 
+  @Test
+  public void pushCommitsWithSameTreeNoChanges() throws Exception {
+    RevCommit c =
+        testRepo
+            .commit()
+            .message("Foo")
+            .parent(getHead(testRepo.getRepository(), "HEAD"))
+            .insertChangeId()
+            .create();
+    testRepo.reset(c);
+
+    String r = "refs/for/master";
+    PushResult pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+
+    RevCommit amended = testRepo.amend(c).create();
+    testRepo.reset(amended);
+
+    pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+    assertThat(pr.getMessages())
+        .contains(
+            "warning: no changes between prior commit "
+                + c.abbreviate(7).name()
+                + " and new commit "
+                + amended.abbreviate(7).name());
+  }
+
+  @Test
+  public void pushCommitsWithSameTreeNoFilesChangedMessageUpdated() throws Exception {
+    RevCommit c =
+        testRepo
+            .commit()
+            .message("Foo")
+            .parent(getHead(testRepo.getRepository(), "HEAD"))
+            .insertChangeId()
+            .create();
+    String id = GitUtil.getChangeId(testRepo, c).get();
+    testRepo.reset(c);
+
+    String r = "refs/for/master";
+    PushResult pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+
+    RevCommit amended =
+        testRepo.amend(c).message("Foo Bar").insertChangeId(id.substring(1)).create();
+    testRepo.reset(amended);
+
+    pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+    assertThat(pr.getMessages())
+        .contains(
+            "warning: " + amended.abbreviate(7).name() + ": no files changed, message updated");
+  }
+
+  @Test
+  public void pushCommitsWithSameTreeNoFilesChangedAuthorChanged() throws Exception {
+    RevCommit c =
+        testRepo
+            .commit()
+            .message("Foo")
+            .parent(getHead(testRepo.getRepository(), "HEAD"))
+            .insertChangeId()
+            .create();
+    testRepo.reset(c);
+
+    String r = "refs/for/master";
+    PushResult pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+
+    RevCommit amended = testRepo.amend(c).author(user.getIdent()).create();
+    testRepo.reset(amended);
+
+    pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+    assertThat(pr.getMessages())
+        .contains(
+            "warning: " + amended.abbreviate(7).name() + ": no files changed, author changed");
+  }
+
+  @Test
+  public void pushCommitsWithSameTreeNoFilesChangedWasRebased() throws Exception {
+    RevCommit head = getHead(testRepo.getRepository(), "HEAD");
+    RevCommit c = testRepo.commit().message("Foo").parent(head).insertChangeId().create();
+    testRepo.reset(c);
+
+    String r = "refs/for/master";
+    PushResult pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+
+    testRepo.reset(head);
+    RevCommit newBase = testRepo.commit().message("Base").parent(head).insertChangeId().create();
+    testRepo.reset(newBase);
+
+    pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+
+    testRepo.reset(c);
+    RevCommit amended = testRepo.amend(c).parent(newBase).create();
+    testRepo.reset(amended);
+
+    pr = pushHead(testRepo, r, false);
+    assertPushOk(pr, r);
+    assertThat(pr.getMessages())
+        .contains("warning: " + amended.abbreviate(7).name() + ": no files changed, was rebased");
+  }
+
   private DraftInput newDraft(String path, int line, String message) {
     DraftInput d = new DraftInput();
     d.path = path;
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index 9e2efd8..58051bb 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -19,7 +19,7 @@
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.common.Nullable;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.SubscribeSection;
 import com.google.gerrit.extensions.client.SubmitType;
@@ -29,6 +29,7 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.StreamSupport;
+import javax.inject.Inject;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEditor;
@@ -57,6 +58,12 @@
 
 public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
 
+  protected TestRepository<?> superRepo;
+  protected Project.NameKey superKey;
+  protected TestRepository<?> subRepo;
+  protected Project.NameKey subKey;
+  @Inject protected ProjectOperations projectOperations;
+
   protected SubmitType getSubmitType() {
     return cfg.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
   }
@@ -96,28 +103,23 @@
     return cfg;
   }
 
-  protected Project.NameKey createProjectForPush(
-      String name,
-      @Nullable Project.NameKey parent,
-      boolean createEmptyCommit,
-      SubmitType submitType)
-      throws Exception {
-    Project.NameKey project = createProject(name, parent, createEmptyCommit, submitType);
+  protected void grantPush(Project.NameKey project) throws Exception {
     grant(project, "refs/heads/*", Permission.PUSH);
     grant(project, "refs/for/refs/heads/*", Permission.SUBMIT);
+  }
+
+  protected Project.NameKey createProjectForPush(SubmitType submitType) throws Exception {
+    Project.NameKey project = projectOperations.newProject().submitType(submitType).create();
+    grantPush(project);
     return project;
   }
 
   private static AtomicInteger contentCounter = new AtomicInteger(0);
-  protected TestRepository<?> superRepo;
-  protected Project.NameKey superKey;
-  protected TestRepository<?> subRepo;
-  protected Project.NameKey subKey;
 
   @Before
   public void setUp() throws Exception {
-    superKey = createProjectForPush("super", null, true, getSubmitType());
-    subKey = createProjectForPush("sub", null, true, getSubmitType());
+    superKey = createProjectForPush(getSubmitType());
+    subKey = createProjectForPush(getSubmitType());
     superRepo = cloneProject(superKey);
     subRepo = cloneProject(subKey);
   }
diff --git a/javatests/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java b/javatests/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java
index 42e046a..90f4134 100644
--- a/javatests/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/GitOverHttpServletIT.java
@@ -17,7 +17,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.server.AuditEvent;
+import com.google.gerrit.server.audit.HttpAuditEvent;
 import java.util.Collections;
+import javax.servlet.http.HttpServletResponse;
 import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
@@ -25,6 +27,7 @@
 import org.junit.Test;
 
 public class GitOverHttpServletIT extends AbstractPushForReview {
+  private static final long AUDIT_EVENT_TIMEOUT = 500L;
 
   @Before
   public void beforeEach() throws Exception {
@@ -42,6 +45,7 @@
         .setRemote("origin")
         .setRefSpecs(new RefSpec("HEAD:refs/for/master"))
         .call();
+    waitForAudit();
 
     // Git smart protocol makes two requests:
     // https://github.com/git/git/blob/master/Documentation/technical/http-protocol.txt
@@ -51,11 +55,13 @@
     assertThat(e.who.getAccountId()).isEqualTo(admin.id);
     assertThat(e.what).endsWith("/git-receive-pack");
     assertThat(e.params).isEmpty();
+    assertThat(((HttpAuditEvent) e).httpStatus).isEqualTo(HttpServletResponse.SC_OK);
   }
 
   @Test
   public void uploadPackAuditEventLog() throws Exception {
     testRepo.git().fetch().call();
+    waitForAudit();
 
     assertThat(auditService.auditEvents.size()).isEqualTo(1);
 
@@ -64,5 +70,12 @@
     assertThat(e.params.get("service"))
         .containsExactlyElementsIn(Collections.singletonList("git-upload-pack"));
     assertThat(e.what).endsWith("service=git-upload-pack");
+    assertThat(((HttpAuditEvent) e).httpStatus).isEqualTo(HttpServletResponse.SC_OK);
+  }
+
+  private void waitForAudit() throws InterruptedException {
+    synchronized (auditService.auditEvents) {
+      auditService.auditEvents.wait(AUDIT_EVENT_TIMEOUT);
+    }
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index e4d9f7c..d2fd331 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -49,18 +49,15 @@
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.git.receive.ReceiveCommitsAdvertiseRefsHook;
 import com.google.gerrit.server.notedb.ChangeNoteUtil;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
 import com.google.gerrit.server.project.testing.Util;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.testing.NoteDbMode;
-import com.google.gerrit.testing.TestChanges;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
@@ -424,12 +421,6 @@
       PatchSet.Id psId = new PatchSet.Id(c3.getId(), 2);
       c.setCurrentPatchSet(psId, subject, c.getOriginalSubject());
 
-      if (notesMigration.changePrimaryStorage() == PrimaryStorage.REVIEW_DB) {
-        PatchSet ps = TestChanges.newPatchSet(psId, rev, admin.getId());
-        db.patchSets().insert(Collections.singleton(ps));
-        db.changes().update(Collections.singleton(c));
-      }
-
       if (notesMigration.commitChangeWrites()) {
         PersonIdent committer = serverIdent.get();
         PersonIdent author =
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmitOnPushIT.java b/javatests/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
index 700b18b..cf22a0a 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
@@ -318,7 +318,7 @@
 
   private PatchSetApproval getSubmitter(PatchSet.Id patchSetId) throws Exception {
     ChangeNotes notes = notesFactory.createChecked(db, project, patchSetId.getParentKey()).load();
-    return approvalsUtil.getSubmitter(db, notes, patchSetId);
+    return approvalsUtil.getSubmitter(notes, patchSetId);
   }
 
   private void assertSubmitApproval(PatchSet.Id patchSetId) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
index 94e3c0a..72ba420 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
@@ -403,12 +403,15 @@
 
   @Test
   public void subscriptionInheritACL() throws Exception {
-    Project.NameKey configKey = createProjectForPush("config-repo", null, true, getSubmitType());
+    Project.NameKey configKey = projectOperations.newProject().submitType(getSubmitType()).create();
+    grantPush(configKey);
     Project.NameKey config2Key =
-        createProjectForPush("config-repo2", configKey, true, getSubmitType());
+        projectOperations.newProject().parent(configKey).submitType(getSubmitType()).create();
+    grantPush(config2Key);
     cloneProject(config2Key);
 
-    subKey = createProjectForPush("subrepo", config2Key, true, getSubmitType());
+    subKey = projectOperations.newProject().parent(config2Key).submitType(getSubmitType()).create();
+    grantPush(subKey);
     subRepo = cloneProject(subKey);
 
     allowMatchingSubmoduleSubscription(configKey, "refs/heads/*", superKey, "refs/heads/*");
@@ -445,8 +448,7 @@
 
   @Test
   public void subscriptionDeepRelative() throws Exception {
-    Project.NameKey nest =
-        createProjectForPush("nested/subscribed-to-project", null, true, getSubmitType());
+    Project.NameKey nest = createProjectForPush(getSubmitType());
     TestRepository<?> subRepo = cloneProject(nest);
     // master is allowed to be subscribed to any superprojects branch:
     allowMatchingSubmoduleSubscription(nest, "refs/heads/master", superKey, null);
@@ -468,9 +470,9 @@
 
   @Test
   @GerritConfig(name = "submodule.verboseSuperprojectUpdate", value = "SUBJECT_ONLY")
-  // The value 195 must tuned to the test environment, and is sensitive to the
+  // The value 110 must tuned to the test environment, and is sensitive to the
   // length of the uniquified repository name.
-  @GerritConfig(name = "submodule.maxCombinedCommitMessageSize", value = "200")
+  @GerritConfig(name = "submodule.maxCombinedCommitMessageSize", value = "110")
   public void submoduleSubjectCommitMessageSizeLimit() throws Exception {
     assume().that(isSubmitWholeTopicEnabled()).isFalse();
     testSubmoduleSubjectCommitMessageAndExpectTruncation();
@@ -481,7 +483,6 @@
     // Make sure that the commit is created at an earlier timestamp than the submit timestamp.
     TestTimeUtil.resetWithClockStep(1, SECONDS);
     try {
-
       allowMatchingSubmoduleSubscription(
           subKey, "refs/heads/master", superKey, "refs/heads/master");
       createSubmoduleSubscription(superRepo, "master", subKey, "master");
@@ -512,8 +513,7 @@
     TestTimeUtil.resetWithClockStep(1, SECONDS);
     try {
 
-      Project.NameKey proj2 =
-          createProjectForPush("subscribed-to-project-2", null, true, getSubmitType());
+      Project.NameKey proj2 = createProjectForPush(getSubmitType());
 
       TestRepository<?> subRepo2 = cloneProject(proj2);
       allowMatchingSubmoduleSubscription(
@@ -560,8 +560,7 @@
     // is afterwards.
     TestTimeUtil.resetWithClockStep(1, SECONDS);
     try {
-      Project.NameKey proj2 =
-          createProjectForPush("subscribed-to-project-2", null, true, getSubmitType());
+      Project.NameKey proj2 = createProjectForPush(getSubmitType());
       TestRepository<InMemoryRepository> repo2 = cloneProject(proj2, user);
 
       allowMatchingSubmoduleSubscription(
@@ -605,10 +604,8 @@
 
   @Test
   public void updateOnlyRelevantSubmodules() throws Exception {
-    Project.NameKey subkey1 =
-        createProjectForPush("subscribed-to-project-1", null, true, getSubmitType());
-    Project.NameKey subkey2 =
-        createProjectForPush("subscribed-to-project-2", null, true, getSubmitType());
+    Project.NameKey subkey1 = createProjectForPush(getSubmitType());
+    Project.NameKey subkey2 = createProjectForPush(getSubmitType());
     TestRepository<?> subRepo1 = cloneProject(subkey1);
     TestRepository<?> subRepo2 = cloneProject(subkey2);
 
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index d3d1ab3..f72df99 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -16,12 +16,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.GitUtil.getChangeId;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
@@ -31,7 +29,6 @@
 import com.google.gerrit.reviewdb.client.Project.NameKey;
 import com.google.gerrit.server.change.TestSubmitInput;
 import com.google.gerrit.testing.ConfigSuite;
-import com.google.inject.Inject;
 import java.util.ArrayDeque;
 import java.util.Map;
 import org.apache.commons.lang.RandomStringUtils;
@@ -71,8 +68,6 @@
     return submitByRebaseIfNecessaryConfig();
   }
 
-  @Inject ProjectOperations projectOperations;
-
   @Test
   public void subscriptionUpdateOfManyChanges() throws Exception {
     allowMatchingSubmoduleSubscription(subKey, "refs/heads/master", superKey, "refs/heads/master");
@@ -277,15 +272,15 @@
 
   @Test
   public void updateManySubmodules() throws Exception {
-    Project.NameKey subKey[] = new NameKey[3];
-    TestRepository<?> sub[] = new TestRepository[3];
+    final int NUM = 3;
+    Project.NameKey subKey[] = new NameKey[NUM];
+    TestRepository<?> sub[] = new TestRepository[NUM];
     String prefix = RandomStringUtils.randomAlphabetic(8);
     for (int i = 0; i < subKey.length; i++) {
       subKey[i] =
           projectOperations
               .newProject()
               .name(prefix + "sub" + i)
-              .withEmptyCommit()
               .submitType(getSubmitType())
               .create();
       grant(subKey[i], "refs/heads/*", Permission.PUSH);
@@ -306,7 +301,7 @@
 
     ObjectId superPreviousId = pushChangeTo(superRepo, "master");
 
-    ObjectId subId[] = new ObjectId[3];
+    ObjectId subId[] = new ObjectId[NUM];
 
     for (int i = 0; i < sub.length; i++) {
       subId[i] = pushChangeTo(sub[i], "refs/for/master", "some message", "same-topic");
@@ -319,7 +314,7 @@
       expectToHaveSubmoduleState(superRepo, "master", subKey[i], sub[i], "master");
     }
 
-    String heads[] = new String[3];
+    String heads[] = new String[NUM];
     for (int i = 0; i < heads.length; i++) {
       heads[i] =
           sub[i]
@@ -367,8 +362,20 @@
   @Test
   public void doNotUseFastForward() throws Exception {
     // like setup, but without empty commit
-    superKey = createProjectForPush("super-nc", null, false, getSubmitType());
-    subKey = createProjectForPush("sub-nc", null, false, getSubmitType());
+    superKey =
+        projectOperations
+            .newProject()
+            .submitType(getSubmitType())
+            .createEmptyCommit(false)
+            .create();
+    grantPush(superKey);
+    subKey =
+        projectOperations
+            .newProject()
+            .submitType(getSubmitType())
+            .createEmptyCommit(false)
+            .create();
+    grantPush(subKey);
     superRepo = cloneProject(superKey);
     subRepo = cloneProject(subKey);
 
@@ -395,8 +402,20 @@
   @Test
   public void useFastForwardWhenNoSubmodule() throws Exception {
     // like setup, but without empty commit
-    superKey = createProjectForPush("super-nc", null, false, getSubmitType());
-    subKey = createProjectForPush("sub-nc", null, false, getSubmitType());
+    superKey =
+        projectOperations
+            .newProject()
+            .submitType(getSubmitType())
+            .createEmptyCommit(false)
+            .create();
+    grantPush(superKey);
+    subKey =
+        projectOperations
+            .newProject()
+            .submitType(getSubmitType())
+            .createEmptyCommit(false)
+            .create();
+    grantPush(subKey);
     superRepo = cloneProject(superKey);
     subRepo = cloneProject(subKey);
 
@@ -493,7 +512,7 @@
 
   @Test
   public void nonSubmoduleInSameTopic() throws Exception {
-    Project.NameKey standaloneKey = createProjectForPush("standalone", null, true, getSubmitType());
+    Project.NameKey standaloneKey = createProjectForPush(getSubmitType());
     TestRepository<?> standAlone = cloneProject(standaloneKey);
 
     allowMatchingSubmoduleSubscription(subKey, "refs/heads/master", superKey, "refs/heads/master");
@@ -533,9 +552,9 @@
 
   @Test
   public void recursiveSubmodules() throws Exception {
-    Project.NameKey topKey = createProjectForPush("top-project", null, true, getSubmitType());
-    Project.NameKey midKey = createProjectForPush("mid-project", null, true, getSubmitType());
-    Project.NameKey botKey = createProjectForPush("bottom-project", null, true, getSubmitType());
+    Project.NameKey topKey = createProjectForPush(getSubmitType());
+    Project.NameKey midKey = createProjectForPush(getSubmitType());
+    Project.NameKey botKey = createProjectForPush(getSubmitType());
     TestRepository<?> topRepo = cloneProject(topKey);
     TestRepository<?> midRepo = cloneProject(midKey);
     TestRepository<?> bottomRepo = cloneProject(botKey);
@@ -563,9 +582,9 @@
 
   @Test
   public void triangleSubmodules() throws Exception {
-    Project.NameKey topKey = createProjectForPush("top-project", null, true, getSubmitType());
-    Project.NameKey midKey = createProjectForPush("mid-project", null, true, getSubmitType());
-    Project.NameKey botKey = createProjectForPush("bottom-project", null, true, getSubmitType());
+    Project.NameKey topKey = createProjectForPush(getSubmitType());
+    Project.NameKey midKey = createProjectForPush(getSubmitType());
+    Project.NameKey botKey = createProjectForPush(getSubmitType());
     TestRepository<?> topRepo = cloneProject(topKey);
     TestRepository<?> midRepo = cloneProject(midKey);
     TestRepository<?> bottomRepo = cloneProject(botKey);
@@ -597,9 +616,9 @@
   }
 
   private String prepareBranchCircularSubscription() throws Exception {
-    Project.NameKey topKey = createProjectForPush("top-project", null, true, getSubmitType());
-    Project.NameKey midKey = createProjectForPush("mid-project", null, true, getSubmitType());
-    Project.NameKey botKey = createProjectForPush("bottom-project", null, true, getSubmitType());
+    Project.NameKey topKey = createProjectForPush(getSubmitType());
+    Project.NameKey midKey = createProjectForPush(getSubmitType());
+    Project.NameKey botKey = createProjectForPush(getSubmitType());
     TestRepository<?> topRepo = cloneProject(topKey);
     TestRepository<?> midRepo = cloneProject(midKey);
     TestRepository<?> bottomRepo = cloneProject(botKey);
@@ -662,8 +681,8 @@
 
   @Test
   public void projectNoSubscriptionWholeTopic() throws Exception {
-    Project.NameKey keyA = createProjectForPush("project-a", null, true, getSubmitType());
-    Project.NameKey keyB = createProjectForPush("project-b", null, true, getSubmitType());
+    Project.NameKey keyA = createProjectForPush(getSubmitType());
+    Project.NameKey keyB = createProjectForPush(getSubmitType());
 
     TestRepository<?> repoA = cloneProject(keyA);
     TestRepository<?> repoB = cloneProject(keyB);
@@ -733,8 +752,8 @@
 
   @Test
   public void twoProjectsMultipleBranchesWholeTopic() throws Exception {
-    Project.NameKey keyA = createProjectForPush("project-a", null, true, getSubmitType());
-    Project.NameKey keyB = createProjectForPush("project-b", null, true, getSubmitType());
+    Project.NameKey keyA = createProjectForPush(getSubmitType());
+    Project.NameKey keyB = createProjectForPush(getSubmitType());
     TestRepository<?> repoA = cloneProject(keyA);
     TestRepository<?> repoB = cloneProject(keyB);
     // bootstrap the dev branch
@@ -780,11 +799,9 @@
 
   @Test
   public void retrySubmitAfterTornTopicOnLockFailure() throws Exception {
-    assume().that(notesMigration.disableChangeReviewDb()).isTrue();
-
-    Project.NameKey subKey1 = createProjectForPush("sub1", null, true, getSubmitType());
+    Project.NameKey subKey1 = createProjectForPush(getSubmitType());
     TestRepository<?> sub1 = cloneProject(subKey1);
-    Project.NameKey subKey2 = createProjectForPush("sub2", null, true, getSubmitType());
+    Project.NameKey subKey2 = createProjectForPush(getSubmitType());
     TestRepository<?> sub2 = cloneProject(subKey2);
 
     allowMatchingSubmoduleSubscription(subKey1, "refs/heads/master", superKey, "refs/heads/master");
@@ -844,9 +861,9 @@
 
   @Test
   public void skipUpdatingBrokenGitlinkPointer() throws Exception {
-    Project.NameKey subKey1 = createProjectForPush("sub1", null, true, getSubmitType());
+    Project.NameKey subKey1 = createProjectForPush(getSubmitType());
     TestRepository<?> sub1 = cloneProject(subKey1);
-    Project.NameKey subKey2 = createProjectForPush("sub2", null, true, getSubmitType());
+    Project.NameKey subKey2 = createProjectForPush(getSubmitType());
     TestRepository<?> sub2 = cloneProject(subKey2);
 
     allowMatchingSubmoduleSubscription(subKey1, "refs/heads/master", superKey, "refs/heads/master");
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index 29a5bd0..bb7cff7 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -26,11 +26,6 @@
 public class ElasticReindexIT extends AbstractReindexTests {
 
   @ConfigSuite.Default
-  public static Config elasticsearchV2() {
-    return getConfig(ElasticVersion.V2_4);
-  }
-
-  @ConfigSuite.Config
   public static Config elasticsearchV5() {
     return getConfig(ElasticVersion.V5_6);
   }
@@ -40,6 +35,11 @@
     return getConfig(ElasticVersion.V6_5);
   }
 
+  @ConfigSuite.Config
+  public static Config elasticsearchV7() {
+    return getConfig(ElasticVersion.V7_0);
+  }
+
   @Override
   public void configureIndex(Injector injector) throws Exception {
     createAllIndexes(injector);
diff --git a/javatests/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java b/javatests/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java
deleted file mode 100644
index 1bb23fb..0000000
--- a/javatests/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java
+++ /dev/null
@@ -1,363 +0,0 @@
-// Copyright (C) 2014 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.pgm;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.common.truth.Truth8.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.StandaloneSiteTest;
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.common.ChangeInput;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
-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.notedb.NoteDbChangeState;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.NoteDbChangeState.RefState;
-import com.google.gerrit.server.notedb.NotesMigrationState;
-import com.google.gerrit.server.schema.ReviewDbFactory;
-import com.google.gerrit.testing.NoteDbMode;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Key;
-import com.google.inject.TypeLiteral;
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.stream.Stream;
-import org.eclipse.jgit.internal.storage.file.FileRepository;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Tests for NoteDb migrations where the entry point is through a program, {@code
- * migrate-to-note-db} or {@code daemon}.
- *
- * <p><strong>Note:</strong> These tests are very slow due to the repeated daemon startup. Prefer
- * adding tests to {@link com.google.gerrit.acceptance.server.notedb.OnlineNoteDbMigrationIT} if
- * possible.
- */
-@NoHttpd
-public class StandaloneNoteDbMigrationIT extends StandaloneSiteTest {
-  private StoredConfig gerritConfig;
-  private StoredConfig noteDbConfig;
-
-  private Project.NameKey project;
-  private Change.Id changeId;
-
-  @Before
-  public void setUp() throws Exception {
-    assume().that(NoteDbMode.get()).isEqualTo(NoteDbMode.OFF);
-    gerritConfig = new FileBasedConfig(sitePaths.gerrit_config.toFile(), FS.detect());
-    // Unlike in the running server, for tests, we don't stack notedb.config on gerrit.config.
-    noteDbConfig = new FileBasedConfig(sitePaths.notedb_config.toFile(), FS.detect());
-
-    // Set gc.pruneExpire=now so GC prunes all unreachable objects from All-Users, which allows us
-    // to reliably test that it behaves as expected.
-    Path cfgPath = sitePaths.site_path.resolve("git").resolve("All-Users.git").resolve("config");
-    assertWithMessage("Expected All-Users config at %s", cfgPath)
-        .that(Files.isRegularFile(cfgPath))
-        .isTrue();
-    FileBasedConfig cfg = new FileBasedConfig(cfgPath.toFile(), FS.detect());
-    cfg.setString("gc", null, "pruneExpire", "now");
-    cfg.save();
-  }
-
-  @Test
-  public void rebuildOneChangeTrialMode() throws Exception {
-    assertNoAutoMigrateConfig(gerritConfig);
-    assertNoAutoMigrateConfig(noteDbConfig);
-    assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
-    setUpOneChange();
-
-    migrate("--trial");
-    assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
-
-    try (ServerContext ctx = startServer()) {
-      GitRepositoryManager repoManager = ctx.getInjector().getInstance(GitRepositoryManager.class);
-      ObjectId metaId;
-      try (Repository repo = repoManager.openRepository(project)) {
-        Ref ref = repo.exactRef(RefNames.changeMetaRef(changeId));
-        assertThat(ref).isNotNull();
-        metaId = ref.getObjectId();
-      }
-
-      try (ReviewDb db = openUnderlyingReviewDb(ctx)) {
-        Change c = db.changes().get(changeId);
-        assertThat(c).isNotNull();
-        NoteDbChangeState state = NoteDbChangeState.parse(c);
-        assertThat(state).isNotNull();
-        assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
-        assertThat(state.getRefState()).hasValue(RefState.create(metaId, ImmutableMap.of()));
-      }
-    }
-  }
-
-  @Test
-  public void migrateOneChange() throws Exception {
-    assertNoAutoMigrateConfig(gerritConfig);
-    assertNoAutoMigrateConfig(noteDbConfig);
-    assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
-    setUpOneChange();
-
-    migrate();
-    assertNotesMigrationState(NotesMigrationState.NOTE_DB);
-
-    File allUsersDir;
-    try (ServerContext ctx = startServer()) {
-      GitRepositoryManager repoManager = ctx.getInjector().getInstance(GitRepositoryManager.class);
-      try (Repository repo = repoManager.openRepository(project)) {
-        assertThat(repo.exactRef(RefNames.changeMetaRef(changeId))).isNotNull();
-      }
-      assertThat(repoManager).isInstanceOf(LocalDiskRepositoryManager.class);
-      try (Repository repo =
-          repoManager.openRepository(ctx.getInjector().getInstance(AllUsersName.class))) {
-        allUsersDir = repo.getDirectory();
-      }
-
-      try (ReviewDb db = openUnderlyingReviewDb(ctx)) {
-        Change c = db.changes().get(changeId);
-        assertThat(c).isNotNull();
-        NoteDbChangeState state = NoteDbChangeState.parse(c);
-        assertThat(state).isNotNull();
-        assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.NOTE_DB);
-        assertThat(state.getRefState()).isEmpty();
-
-        ChangeInput in = new ChangeInput(project.get(), "master", "NoteDb-only change");
-        in.newBranch = true;
-        GerritApi gApi = ctx.getInjector().getInstance(GerritApi.class);
-        Change.Id id2 = new Change.Id(gApi.changes().create(in).info()._number);
-        assertThat(db.changes().get(id2)).isNull();
-      }
-    }
-    assertNoAutoMigrateConfig(gerritConfig);
-    assertAutoMigrateConfig(noteDbConfig, false);
-
-    try (FileRepository repo = new FileRepository(allUsersDir)) {
-      try (Stream<Path> paths = Files.walk(repo.getObjectsDirectory().toPath())) {
-        assertThat(paths.filter(p -> !p.toString().contains("pack") && Files.isRegularFile(p)))
-            .named("loose object files in All-Users")
-            .isEmpty();
-      }
-      assertThat(repo.getObjectDatabase().getPacks()).named("packfiles in All-Users").hasSize(1);
-    }
-  }
-
-  @Test
-  public void migrationWithReindex() throws Exception {
-    assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
-    setUpOneChange();
-
-    int version = ChangeSchemaDefinitions.INSTANCE.getLatest().getVersion();
-    GerritIndexStatus status = new GerritIndexStatus(sitePaths);
-    assertThat(status.getReady(ChangeSchemaDefinitions.NAME, version)).isTrue();
-    status.setReady(ChangeSchemaDefinitions.NAME, version, false);
-    status.save();
-    assertServerStartupFails();
-
-    migrate();
-    assertNotesMigrationState(NotesMigrationState.NOTE_DB);
-
-    status = new GerritIndexStatus(sitePaths);
-    assertThat(status.getReady(ChangeSchemaDefinitions.NAME, version)).isTrue();
-  }
-
-  @Test
-  public void onlineMigrationViaDaemon() throws Exception {
-    assertNoAutoMigrateConfig(gerritConfig);
-    assertNoAutoMigrateConfig(noteDbConfig);
-
-    testOnlineMigration(u -> startServer(u.module(), "--migrate-to-note-db", "true"));
-
-    assertNoAutoMigrateConfig(gerritConfig);
-    assertAutoMigrateConfig(noteDbConfig, false);
-  }
-
-  @Test
-  public void onlineMigrationViaConfig() throws Exception {
-    assertNoAutoMigrateConfig(gerritConfig);
-    assertNoAutoMigrateConfig(noteDbConfig);
-
-    testOnlineMigration(
-        u -> {
-          gerritConfig.setBoolean("noteDb", "changes", "autoMigrate", true);
-          gerritConfig.save();
-          return startServer(u.module());
-        });
-
-    // Auto-migration is turned off in notedb.config, which takes precedence, but is still on in
-    // gerrit.config. This means Puppet can continue overwriting gerrit.config without turning
-    // auto-migration back on.
-    assertAutoMigrateConfig(gerritConfig, true);
-    assertAutoMigrateConfig(noteDbConfig, false);
-  }
-
-  @Test
-  public void onlineMigrationTrialModeViaFlag() throws Exception {
-    assertNoAutoMigrateConfig(gerritConfig);
-    assertNoTrialConfig(gerritConfig);
-
-    assertNoAutoMigrateConfig(noteDbConfig);
-    assertNoTrialConfig(noteDbConfig);
-
-    testOnlineMigration(
-        u -> startServer(u.module(), "--migrate-to-note-db", "--trial"),
-        NotesMigrationState.READ_WRITE_NO_SEQUENCE);
-
-    assertNoAutoMigrateConfig(gerritConfig);
-    assertNoTrialConfig(gerritConfig);
-
-    assertAutoMigrateConfig(noteDbConfig, true);
-    assertTrialConfig(noteDbConfig, true);
-  }
-
-  @Test
-  public void onlineMigrationTrialModeViaConfig() throws Exception {
-    assertNoAutoMigrateConfig(gerritConfig);
-    assertNoTrialConfig(gerritConfig);
-
-    assertNoAutoMigrateConfig(noteDbConfig);
-    assertNoTrialConfig(noteDbConfig);
-
-    testOnlineMigration(
-        u -> {
-          gerritConfig.setBoolean("noteDb", "changes", "autoMigrate", true);
-          gerritConfig.setBoolean("noteDb", "changes", "trial", true);
-          gerritConfig.save();
-          return startServer(u.module());
-        },
-        NotesMigrationState.READ_WRITE_NO_SEQUENCE);
-
-    assertAutoMigrateConfig(gerritConfig, true);
-    assertTrialConfig(gerritConfig, true);
-
-    assertAutoMigrateConfig(noteDbConfig, true);
-    assertTrialConfig(noteDbConfig, true);
-  }
-
-  @FunctionalInterface
-  private interface StartServerWithMigration {
-    ServerContext start(IndexUpgradeController u) throws Exception;
-  }
-
-  private void testOnlineMigration(StartServerWithMigration start) throws Exception {
-    testOnlineMigration(start, NotesMigrationState.NOTE_DB);
-  }
-
-  private void testOnlineMigration(
-      StartServerWithMigration start, NotesMigrationState expectedEndState) throws Exception {
-    assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
-    int prevVersion = ChangeSchemaDefinitions.INSTANCE.getPrevious().getVersion();
-    int currVersion = ChangeSchemaDefinitions.INSTANCE.getLatest().getVersion();
-
-    // Before storing any changes, switch back to the previous version.
-    GerritIndexStatus status = new GerritIndexStatus(sitePaths);
-    status.setReady(ChangeSchemaDefinitions.NAME, currVersion, false);
-    status.setReady(ChangeSchemaDefinitions.NAME, prevVersion, true);
-    status.save();
-
-    setOnlineUpgradeConfig(false);
-    setUpOneChange();
-    setOnlineUpgradeConfig(true);
-
-    IndexUpgradeController u = new IndexUpgradeController(1);
-    try (ServerContext ctx = start.start(u)) {
-      ChangeIndexCollection indexes = ctx.getInjector().getInstance(ChangeIndexCollection.class);
-      assertThat(indexes.getSearchIndex().getSchema().getVersion()).isEqualTo(prevVersion);
-
-      // Index schema upgrades happen after NoteDb migration, so waiting for those to complete
-      // should be sufficient.
-      u.runUpgrades();
-
-      assertThat(indexes.getSearchIndex().getSchema().getVersion()).isEqualTo(currVersion);
-      assertNotesMigrationState(expectedEndState);
-    }
-  }
-
-  private void setUpOneChange() throws Exception {
-    project = new Project.NameKey("project");
-    try (ServerContext ctx = startServer()) {
-      GerritApi gApi = ctx.getInjector().getInstance(GerritApi.class);
-      gApi.projects().create("project");
-
-      ChangeInput in = new ChangeInput(project.get(), "master", "Test change");
-      in.newBranch = true;
-      changeId = new Change.Id(gApi.changes().create(in).info()._number);
-    }
-  }
-
-  private void migrate(String... additionalArgs) throws Exception {
-    runGerrit(
-        ImmutableList.of(
-            "migrate-to-note-db", "-d", sitePaths.site_path.toString(), "--show-stack-trace"),
-        ImmutableList.copyOf(additionalArgs));
-  }
-
-  private void assertNotesMigrationState(NotesMigrationState expected) throws Exception {
-    noteDbConfig.load();
-    assertThat(NotesMigrationState.forConfig(noteDbConfig)).hasValue(expected);
-  }
-
-  private ReviewDb openUnderlyingReviewDb(ServerContext ctx) throws Exception {
-    return ctx.getInjector()
-        .getInstance(Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}, ReviewDbFactory.class))
-        .open();
-  }
-
-  private static void assertNoAutoMigrateConfig(StoredConfig cfg) throws Exception {
-    cfg.load();
-    assertThat(cfg.getString("noteDb", "changes", "autoMigrate")).isNull();
-  }
-
-  private static void assertAutoMigrateConfig(StoredConfig cfg, boolean expected) throws Exception {
-    cfg.load();
-    assertThat(cfg.getString("noteDb", "changes", "autoMigrate")).isNotNull();
-    assertThat(cfg.getBoolean("noteDb", "changes", "autoMigrate", false)).isEqualTo(expected);
-  }
-
-  private static void assertNoTrialConfig(StoredConfig cfg) throws Exception {
-    cfg.load();
-    assertThat(cfg.getString("noteDb", "changes", "trial")).isNull();
-  }
-
-  private static void assertTrialConfig(StoredConfig cfg, boolean expected) throws Exception {
-    cfg.load();
-    assertThat(cfg.getString("noteDb", "changes", "trial")).isNotNull();
-    assertThat(cfg.getBoolean("noteDb", "changes", "trial", false)).isEqualTo(expected);
-  }
-
-  private void setOnlineUpgradeConfig(boolean enable) throws Exception {
-    gerritConfig.load();
-    gerritConfig.setBoolean("index", null, "onlineUpgrade", enable);
-    gerritConfig.save();
-  }
-}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
index 65c95f8..dd26347 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.rest.account;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
 import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
@@ -116,7 +115,7 @@
     assertThat(psa.getRealAccountId()).isEqualTo(admin.id);
 
     ChangeData cd = r.getChange();
-    ChangeMessage m = Iterables.getLast(cmUtil.byChange(db, cd.notes()));
+    ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
     assertThat(m.getMessage()).endsWith(in.message);
     assertThat(m.getAuthor()).isEqualTo(user.id);
     assertThat(m.getRealAuthor()).isEqualTo(admin.id);
@@ -191,7 +190,6 @@
 
   @Test
   public void voteOnBehalfOfWithCommentWritingJson() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     testVoteOnBehalfOfWithComment();
   }
 
@@ -218,7 +216,7 @@
     assertThat(psa.getRealAccountId()).isEqualTo(admin.id);
 
     ChangeData cd = r.getChange();
-    Comment c = Iterables.getOnlyElement(commentsUtil.publishedByChange(db, cd.notes()));
+    Comment c = Iterables.getOnlyElement(commentsUtil.publishedByChange(cd.notes()));
     assertThat(c.message).isEqualTo(ci.message);
     assertThat(c.author.getId()).isEqualTo(user.id);
     assertThat(c.getRealAuthor().getId()).isEqualTo(admin.id);
@@ -226,7 +224,6 @@
 
   @Test
   public void voteOnBehalfOfWithRobotComment() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     allowCodeReviewOnBehalfOf();
     PushOneCommit.Result r = createChange();
 
@@ -342,7 +339,7 @@
     ChangeData cd = r.getChange();
     assertThat(cd.change().getStatus()).isEqualTo(Change.Status.MERGED);
     PatchSetApproval submitter =
-        approvalsUtil.getSubmitter(db, cd.notes(), cd.change().currentPatchSetId());
+        approvalsUtil.getSubmitter(cd.notes(), cd.change().currentPatchSetId());
     assertThat(submitter.getAccountId()).isEqualTo(admin2.id);
     assertThat(submitter.getRealAccountId()).isEqualTo(admin.id);
   }
@@ -520,7 +517,7 @@
     assertThat(psa.getRealAccountId()).isEqualTo(admin.id); // not user2
 
     ChangeData cd = r.getChange();
-    ChangeMessage m = Iterables.getLast(cmUtil.byChange(db, cd.notes()));
+    ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
     assertThat(m.getMessage()).endsWith(in.message);
     assertThat(m.getAuthor()).isEqualTo(user.id);
     assertThat(m.getRealAuthor()).isEqualTo(admin.id); // not user2
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java b/javatests/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java
index bc84593..f9bc539 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java
@@ -18,21 +18,25 @@
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.client.ProjectWatchInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.inject.Inject;
 import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 
 public class WatchedProjectsIT extends AbstractDaemonTest {
+  @Inject private ProjectOperations projectOperations;
 
   private static final String NEW_PROJECT_NAME = "newProjectAccess";
 
   @Test
   public void setAndGetWatchedProjects() throws Exception {
-    String projectName1 = createProject(NEW_PROJECT_NAME).get();
-    String projectName2 = createProject(NEW_PROJECT_NAME + "2").get();
+    String projectName1 = projectOperations.newProject().name(NEW_PROJECT_NAME).create().get();
+    String projectName2 =
+        projectOperations.newProject().name(NEW_PROJECT_NAME + "2").create().get();
 
     List<ProjectWatchInfo> projectsToWatch = new ArrayList<>(2);
 
@@ -57,8 +61,8 @@
 
   @Test
   public void setAndDeleteWatchedProjects() throws Exception {
-    String projectName1 = createProject(NEW_PROJECT_NAME).get();
-    String projectName2 = createProject(NEW_PROJECT_NAME + "2").get();
+    String projectName1 = projectOperations.newProject().create().get();
+    String projectName2 = projectOperations.newProject().create().get();
 
     List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
 
@@ -91,7 +95,7 @@
 
   @Test
   public void setConflictingWatches() throws Exception {
-    String projectName = createProject(NEW_PROJECT_NAME).get();
+    String projectName = projectOperations.newProject().create().get();
 
     List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
 
@@ -115,7 +119,7 @@
 
   @Test
   public void setAndGetEmptyWatch() throws Exception {
-    String projectName = createProject(NEW_PROJECT_NAME).get();
+    String projectName = projectOperations.newProject().create().get();
 
     List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
index 628b63f..db5dfab 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.acceptance.rest.binding;
 
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.rest.util.RestCall.Method.GET;
 import static com.google.gerrit.extensions.common.testing.RobotCommentInfoSubject.assertThatList;
 import static java.util.stream.Collectors.toList;
@@ -113,14 +112,6 @@
           RestCall.delete("/changes/%s"));
 
   /**
-   * Change REST endpoints to be tested with NoteDb, each URL contains a placeholder for the change
-   * identifier.
-   */
-  private static final ImmutableList<RestCall> CHANGE_ENDPOINTS_NOTEDB =
-      ImmutableList.of(
-          RestCall.post("/changes/%s/hashtags"), RestCall.post("/changes/%s/rebuild.notedb"));
-
-  /**
    * Reviewer REST endpoints to be tested, each URL contains placeholders for the change identifier
    * and the reviewer identifier.
    */
@@ -285,14 +276,6 @@
   }
 
   @Test
-  public void changeEndpointsNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
-    String changeId = createChange().getChangeId();
-    RestApiCallHelper.execute(adminRestSession, CHANGE_ENDPOINTS_NOTEDB, changeId);
-  }
-
-  @Test
   public void reviewerEndpoints() throws Exception {
     String changeId = createChange().getChangeId();
 
@@ -394,8 +377,6 @@
 
   @Test
   public void robotCommentEndpoints() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     String changeId = createChange().getChangeId();
 
     RobotCommentInput robotCommentInput = new RobotCommentInput();
@@ -421,8 +402,6 @@
 
   @Test
   public void fixEndpoints() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     String changeId = createChange("Subject", FILENAME, "content").getChangeId();
 
     RobotCommentInput robotCommentInput = new RobotCommentInput();
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
index 0c18dbb..c838cf9 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
@@ -27,11 +27,13 @@
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.rest.util.RestApiCallHelper;
 import com.google.gerrit.acceptance.rest.util.RestCall;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.api.projects.TagInput;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -156,6 +158,7 @@
       ImmutableList.of(RestCall.get("/projects/%s/commits/%s/files/%s/content"));
 
   private static final String FILENAME = "test.txt";
+  @Inject private ProjectOperations projectOperations;
 
   @Test
   public void projectEndpoints() throws Exception {
@@ -164,7 +167,7 @@
 
   @Test
   public void childProjectEndpoints() throws Exception {
-    Project.NameKey childProject = createProject("test-child-repo", project);
+    Project.NameKey childProject = projectOperations.newProject().parent(project).create();
     RestApiCallHelper.execute(
         adminRestSession, CHILD_PROJECT_ENDPOINTS, project.get(), childProject.get());
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 08a76e4..a4d9a24 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -37,7 +37,7 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.TestProjectInput;
-import com.google.gerrit.common.Nullable;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
@@ -120,6 +120,7 @@
   @Inject private Submit submitHandler;
 
   @Inject private IdentifiedUser.GenericFactory userFactory;
+  @Inject private ProjectOperations projectOperations;
 
   @Inject private DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners;
   private RegistrationHandle onSubmitValidatorHandle;
@@ -315,7 +316,7 @@
   @Test
   public void submitNoPermission() throws Exception {
     // create project where submit is blocked
-    Project.NameKey p = createProject("p");
+    Project.NameKey p = projectOperations.newProject().create();
     block(p, "refs/*", Permission.SUBMIT, REGISTERED_USERS);
 
     TestRepository<InMemoryRepository> repo = cloneProject(p, admin);
@@ -329,7 +330,7 @@
   @Test
   public void noSelfSubmit() throws Exception {
     // create project where submit is blocked for the change owner
-    Project.NameKey p = createProject("p");
+    Project.NameKey p = projectOperations.newProject().create();
     try (ProjectConfigUpdate u = updateProject(p)) {
       Util.block(u.getConfig(), Permission.SUBMIT, CHANGE_OWNER, "refs/*");
       Util.allow(u.getConfig(), Permission.SUBMIT, REGISTERED_USERS, "refs/heads/*");
@@ -355,7 +356,7 @@
   @Test
   public void onlySelfSubmit() throws Exception {
     // create project where only the change owner can submit
-    Project.NameKey p = createProject("p");
+    Project.NameKey p = projectOperations.newProject().create();
     try (ProjectConfigUpdate u = updateProject(p)) {
       Util.block(u.getConfig(), Permission.SUBMIT, REGISTERED_USERS, "refs/*");
       Util.allow(u.getConfig(), Permission.SUBMIT, CHANGE_OWNER, "refs/*");
@@ -385,8 +386,10 @@
     String topic = "test-topic";
 
     // Create test projects
-    TestRepository<?> repoA = createProjectWithPush("project-a", null, getSubmitType());
-    TestRepository<?> repoB = createProjectWithPush("project-b", null, getSubmitType());
+    Project.NameKey keyA = createProjectForPush(getSubmitType());
+    TestRepository<?> repoA = cloneProject(keyA);
+    Project.NameKey keyB = createProjectForPush(getSubmitType());
+    TestRepository<?> repoB = cloneProject(keyB);
 
     // Create changes on project-a
     PushOneCommit.Result change1 =
@@ -419,15 +422,15 @@
     String topic = "test-topic";
 
     // Create test project
-    String projectName = "project-a";
-    TestRepository<?> repoA = createProjectWithPush(projectName, null, getSubmitType());
+    Project.NameKey keyA = createProjectForPush(getSubmitType());
+    TestRepository<?> repoA = cloneProject(keyA);
 
-    RevCommit initialHead = getRemoteHead(new Project.NameKey(name(projectName)), "master");
+    RevCommit initialHead = getRemoteHead(keyA, "master");
 
     // Create the dev branch on the test project
     BranchInput in = new BranchInput();
     in.revision = initialHead.name();
-    gApi.projects().name(name(projectName)).branch("dev").create(in);
+    gApi.projects().name(keyA.get()).branch("dev").create(in);
 
     // Create changes on master
     PushOneCommit.Result change1 =
@@ -769,8 +772,10 @@
     String topic = "test-topic";
 
     // Create test projects
-    TestRepository<?> repoA = createProjectWithPush("project-a", null, getSubmitType());
-    TestRepository<?> repoB = createProjectWithPush("project-b", null, getSubmitType());
+    Project.NameKey keyA = createProjectForPush(getSubmitType());
+    TestRepository<?> repoA = cloneProject(keyA);
+    Project.NameKey keyB = createProjectForPush(getSubmitType());
+    TestRepository<?> repoB = cloneProject(keyB);
 
     // Create changes on project-a
     PushOneCommit.Result change1 =
@@ -815,15 +820,13 @@
           }
         });
     submitWithConflict(change4.getChangeId(), "time to fail");
-    assertThat(projectsCalled).containsExactly(name("project-a"), name("project-b"));
+    assertThat(projectsCalled).containsExactly(keyA.get(), keyB.get());
     for (PushOneCommit.Result change : changes) {
       change.assertChange(Change.Status.NEW, name(topic), admin);
     }
 
     submit(change4.getChangeId());
-    assertThat(projectsCalled)
-        .containsExactly(
-            name("project-a"), name("project-b"), name("project-a"), name("project-b"));
+    assertThat(projectsCalled).containsExactly(keyA.get(), keyB.get(), keyA.get(), keyB.get());
     for (PushOneCommit.Result change : changes) {
       change.assertChange(Change.Status.MERGED, name(topic), admin);
     }
@@ -902,8 +905,6 @@
 
   @Test
   public void retrySubmitSingleChangeOnLockFailure() throws Exception {
-    assume().that(notesMigration.disableChangeReviewDb()).isTrue();
-
     PushOneCommit.Result change = createChange();
     String id = change.getChangeId();
     approve(id);
@@ -929,13 +930,14 @@
 
   @Test
   public void retrySubmitAfterTornTopicOnLockFailure() throws Exception {
-    assume().that(notesMigration.disableChangeReviewDb()).isTrue();
     assume().that(isSubmitWholeTopicEnabled()).isTrue();
 
     String topic = "test-topic";
 
-    TestRepository<?> repoA = createProjectWithPush("project-a", null, getSubmitType());
-    TestRepository<?> repoB = createProjectWithPush("project-b", null, getSubmitType());
+    Project.NameKey keyA = createProjectForPush(getSubmitType());
+    Project.NameKey keyB = createProjectForPush(getSubmitType());
+    TestRepository<?> repoA = cloneProject(keyA);
+    TestRepository<?> repoB = cloneProject(keyB);
 
     PushOneCommit.Result change1 =
         createChange(repoA, "master", "Change 1", "a.txt", "content", topic);
@@ -962,13 +964,13 @@
 
     repoA.git().fetch().call();
     RevWalk rwA = repoA.getRevWalk();
-    RevCommit masterA = rwA.parseCommit(getRemoteHead(name("project-a"), "master"));
+    RevCommit masterA = rwA.parseCommit(getRemoteHead(keyA, "master"));
     RevCommit change1Ps = parseCurrentRevision(rwA, change1.getChangeId());
     assertThat(rwA.isMergedInto(change1Ps, masterA)).isTrue();
 
     repoB.git().fetch().call();
     RevWalk rwB = repoB.getRevWalk();
-    RevCommit masterB = rwB.parseCommit(getRemoteHead(name("project-b"), "master"));
+    RevCommit masterB = rwB.parseCommit(getRemoteHead(keyB, "master"));
     RevCommit change2Ps = parseCurrentRevision(rwB, change2.getChangeId());
     assertThat(rwB.isMergedInto(change2Ps, masterB)).isTrue();
 
@@ -1276,7 +1278,7 @@
     Change c = getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change();
     ChangeNotes cn = notesFactory.createChecked(db, c);
     PatchSetApproval submitter =
-        approvalsUtil.getSubmitter(db, cn, new PatchSet.Id(cn.getChangeId(), psId));
+        approvalsUtil.getSubmitter(cn, new PatchSet.Id(cn.getChangeId(), psId));
     assertThat(submitter).isNotNull();
     assertThat(submitter.isLegacySubmit()).isTrue();
     assertThat(submitter.getAccountId()).isEqualTo(user.getId());
@@ -1286,7 +1288,7 @@
     Change c = getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change();
     ChangeNotes cn = notesFactory.createChecked(db, c);
     PatchSetApproval submitter =
-        approvalsUtil.getSubmitter(db, cn, new PatchSet.Id(cn.getChangeId(), psId));
+        approvalsUtil.getSubmitter(cn, new PatchSet.Id(cn.getChangeId(), psId));
     assertThat(submitter).isNull();
   }
 
@@ -1300,7 +1302,7 @@
 
   protected void assertRebase(TestRepository<?> testRepo, boolean contentMerge) throws Exception {
     Repository repo = testRepo.getRepository();
-    RevCommit localHead = getHead(repo);
+    RevCommit localHead = getHead(repo, "HEAD");
     RevCommit remoteHead = getRemoteHead();
     assertThat(localHead.getId()).isNotEqualTo(remoteHead.getId());
     assertThat(remoteHead.getParentCount()).isEqualTo(1);
@@ -1353,12 +1355,12 @@
     }
   }
 
-  private TestRepository<?> createProjectWithPush(
-      String name, @Nullable Project.NameKey parent, SubmitType submitType) throws Exception {
-    Project.NameKey project = createProject(name, parent, true, submitType);
+  // TODO(hanwen): the submodule tests have a similar method; maybe we could share code?
+  protected Project.NameKey createProjectForPush(SubmitType submitType) throws Exception {
+    Project.NameKey project = projectOperations.newProject().submitType(submitType).create();
     grant(project, "refs/heads/*", Permission.PUSH);
     grant(project, "refs/for/refs/heads/*", Permission.SUBMIT);
-    return cloneProject(project);
+    return project;
   }
 
   protected PushOneCommit.Result createChange(
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
index 29a81ca..fecb6c5 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
@@ -17,21 +17,10 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 
-import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestProjectInput;
-import com.google.gerrit.extensions.api.changes.SubmitInput;
-import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.InheritableBoolean;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.change.TestSubmitInput;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
 import org.junit.Test;
 
 public abstract class AbstractSubmitByMerge extends AbstractSubmit {
@@ -124,58 +113,4 @@
     assertThat(head.getParent(0)).isEqualTo(change1.getCommit());
     assertThat(head.getParent(1)).isEqualTo(change2.getCommit());
   }
-
-  @Test
-  public void repairChangeStateAfterFailure() throws Exception {
-    // In NoteDb-only mode, repo and meta updates are atomic (at least in InMemoryRepository).
-    assume().that(notesMigration.disableChangeReviewDb()).isFalse();
-
-    RevCommit initialHead = getRemoteHead();
-    PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
-    submit(change.getChangeId());
-    RevCommit afterChange1Head = getRemoteHead();
-
-    testRepo.reset(initialHead);
-    PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
-    Change.Id id2 = change2.getChange().getId();
-    TestSubmitInput failInput = new TestSubmitInput();
-    failInput.failAfterRefUpdates = true;
-    submit(
-        change2.getChangeId(),
-        failInput,
-        ResourceConflictException.class,
-        "Failing after ref updates");
-
-    // Bad: ref advanced but change wasn't updated.
-    PatchSet.Id psId1 = new PatchSet.Id(id2, 1);
-    ChangeInfo info = gApi.changes().id(id2.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.NEW);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
-
-    RevCommit tip;
-    try (Repository repo = repoManager.openRepository(project);
-        RevWalk rw = new RevWalk(repo)) {
-      ObjectId rev1 = repo.exactRef(psId1.toRefName()).getObjectId();
-      assertThat(rev1).isNotNull();
-
-      tip = rw.parseCommit(repo.exactRef("refs/heads/master").getObjectId());
-      assertThat(tip.getParentCount()).isEqualTo(2);
-      assertThat(tip.getParent(0)).isEqualTo(afterChange1Head);
-      assertThat(tip.getParent(1)).isEqualTo(change2.getCommit());
-    }
-
-    submit(change2.getChangeId(), new SubmitInput(), null, null);
-
-    // Change status and patch set entities were updated, and branch tip stayed
-    // the same.
-    info = gApi.changes().id(id2.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
-    assertThat(Iterables.getLast(info.messages).message)
-        .isEqualTo("Change has been successfully merged by Administrator");
-
-    try (Repository repo = repoManager.openRepository(project)) {
-      assertThat(repo.exactRef("refs/heads/master").getObjectId()).isEqualTo(tip);
-    }
-  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
index 0a92cfb..e8e896d 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
@@ -15,13 +15,11 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.GitUtil.getChangeId;
 import static com.google.gerrit.acceptance.GitUtil.pushHead;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.TestProjectInput;
@@ -30,12 +28,8 @@
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.change.TestSubmitInput;
 import com.google.gerrit.server.project.testing.Util;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
@@ -242,80 +236,6 @@
     assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name());
   }
 
-  @Test
-  public void repairChangeStateAfterFailure() throws Exception {
-    // In NoteDb-only mode, repo and meta updates are atomic (at least in InMemoryRepository).
-    assume().that(notesMigration.disableChangeReviewDb()).isFalse();
-
-    RevCommit initialHead = getRemoteHead();
-    PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
-    submit(change.getChangeId());
-
-    RevCommit headAfterFirstSubmit = getRemoteHead();
-    testRepo.reset(initialHead);
-    PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
-    Change.Id id2 = change2.getChange().getId();
-    TestSubmitInput failInput = new TestSubmitInput();
-    failInput.failAfterRefUpdates = true;
-    submit(
-        change2.getChangeId(),
-        failInput,
-        ResourceConflictException.class,
-        "Failing after ref updates");
-    RevCommit headAfterFailedSubmit = getRemoteHead();
-
-    // Bad: ref advanced but change wasn't updated.
-    PatchSet.Id psId1 = new PatchSet.Id(id2, 1);
-    PatchSet.Id psId2 = new PatchSet.Id(id2, 2);
-    ChangeInfo info = gApi.changes().id(id2.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.NEW);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
-    assertThat(getPatchSet(psId2)).isNull();
-
-    ObjectId rev2;
-    try (Repository repo = repoManager.openRepository(project);
-        RevWalk rw = new RevWalk(repo)) {
-      ObjectId rev1 = repo.exactRef(psId1.toRefName()).getObjectId();
-      assertThat(rev1).isNotNull();
-
-      rev2 = repo.exactRef(psId2.toRefName()).getObjectId();
-      assertThat(rev2).isNotNull();
-      assertThat(rev2).isNotEqualTo(rev1);
-      assertThat(rw.parseCommit(rev2).getParent(0)).isEqualTo(headAfterFirstSubmit);
-
-      assertThat(repo.exactRef("refs/heads/master").getObjectId()).isEqualTo(rev2);
-    }
-
-    submit(change2.getChangeId());
-    RevCommit headAfterSecondSubmit = getRemoteHead();
-    assertThat(headAfterSecondSubmit).isEqualTo(headAfterFailedSubmit);
-
-    // Change status and patch set entities were updated, and branch tip stayed
-    // the same.
-    info = gApi.changes().id(id2.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(2);
-    PatchSet ps2 = getPatchSet(psId2);
-    assertThat(ps2).isNotNull();
-    assertThat(ps2.getRevision().get()).isEqualTo(rev2.name());
-    assertThat(Iterables.getLast(info.messages).message)
-        .isEqualTo(
-            "Change has been successfully rebased and submitted as "
-                + rev2.name()
-                + " by Administrator");
-
-    try (Repository repo = repoManager.openRepository(project)) {
-      assertThat(repo.exactRef("refs/heads/master").getObjectId()).isEqualTo(rev2);
-    }
-
-    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
-    assertChangeMergedEvents(
-        change.getChangeId(),
-        headAfterFirstSubmit.name(),
-        change2.getChangeId(),
-        headAfterSecondSubmit.name());
-  }
-
   protected RevCommit parse(ObjectId id) throws Exception {
     try (Repository repo = repoManager.openRepository(project);
         RevWalk rw = new RevWalk(repo)) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
index 69035f2..c925d88 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -79,7 +78,6 @@
 
   @Test
   public void getPastAssignees() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     PushOneCommit.Result r = createChange();
     setAssignee(r, user.email);
     setAssignee(r, admin.email);
@@ -92,14 +90,7 @@
 
   @Test
   public void assigneeAddedAsReviewer() throws Exception {
-    ReviewerState state;
-    // Assignee is added as CC, if back-end is reviewDb (that does not support
-    // CC) CC is stored as REVIEWER
-    if (notesMigration.readChanges()) {
-      state = ReviewerState.CC;
-    } else {
-      state = ReviewerState.REVIEWER;
-    }
+    ReviewerState state = ReviewerState.CC;
     PushOneCommit.Result r = createChange();
     Iterable<AccountInfo> reviewers = getReviewers(r, state);
     assertThat(reviewers).isNull();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
index 790b884..48cb050 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -251,10 +251,7 @@
     List<ChangeMessageInfo> messagesBeforeDeletion = gApi.changes().id(changeNum).messages();
 
     List<CommentInfo> commentsBefore = getChangeSortedComments(changeNum);
-    List<RevCommit> commitsBefore = new ArrayList<>();
-    if (notesMigration.readChanges()) {
-      commitsBefore = getChangeMetaCommitsInReverseOrder(new Change.Id(changeNum));
-    }
+    List<RevCommit> commitsBefore = getChangeMetaCommitsInReverseOrder(new Change.Id(changeNum));
 
     String id = messagesBeforeDeletion.get(deletedMessageIndex).id;
     DeleteChangeMessageInput input = new DeleteChangeMessageInput(reason);
@@ -271,11 +268,9 @@
     List<ChangeInfo> changes = gApi.changes().query("message removed").get();
     assertThat(changes.stream().map(c -> c._number).collect(toSet())).contains(changeNum);
 
-    // Verifies states of commits if NoteDb is on.
-    if (notesMigration.readChanges()) {
-      assertMetaCommitsAfterDeletion(
-          commitsBefore, changeNum, deletedMessageIndex, deletedBy, reason);
-    }
+    // Verifies states of commits.
+    assertMetaCommitsAfterDeletion(
+        commitsBefore, changeNum, deletedMessageIndex, deletedBy, reason);
   }
 
   private void assertMessagesAfterDeletion(
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
index ac0d0aa..164fe60 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
@@ -19,11 +19,13 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.inject.Inject;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.junit.Before;
@@ -32,6 +34,7 @@
 public class ChangeOwnerIT extends AbstractDaemonTest {
 
   private TestAccount user2;
+  @Inject private ProjectOperations projectOperations;
 
   @Before
   public void setUp() throws Exception {
@@ -63,7 +66,7 @@
   public void testChangeOwner_OwnerACLGrantedOnParentProject() throws Exception {
     setApiUser(admin);
     grantApproveToChangeOwner(project);
-    Project.NameKey child = createProject("child", project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     setApiUser(user);
     TestRepository<InMemoryRepository> childRepo = cloneProject(child, user);
@@ -74,7 +77,7 @@
   public void testChangeOwner_BlockedOnParentProject() throws Exception {
     setApiUser(admin);
     blockApproveForChangeOwner(project);
-    Project.NameKey child = createProject("child", project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     setApiUser(user);
     grantApproveToAll(child);
@@ -92,7 +95,7 @@
   public void testChangeOwner_BlockedOnParentProjectAndExclusiveAllowOnChild() throws Exception {
     setApiUser(admin);
     blockApproveForChangeOwner(project);
-    Project.NameKey child = createProject("child", project);
+    Project.NameKey child = projectOperations.newProject().parent(project).create();
 
     setApiUser(user);
     grantExclusiveApproveToAll(child);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java
index 257c88b..2a397e4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java
@@ -15,15 +15,14 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.api.changes.AddReviewerResult;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -35,11 +34,12 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.testing.FakeEmailSender.Message;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
 import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 
-@NoHttpd
 public class ChangeReviewersByEmailIT extends AbstractDaemonTest {
 
   @Before
@@ -51,7 +51,6 @@
 
   @Test
   public void addByEmail() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
 
     for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
@@ -71,7 +70,6 @@
 
   @Test
   public void addByEmailAndById() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     AccountInfo byEmail = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
     AccountInfo byId = new AccountInfo(user.id.get());
 
@@ -96,8 +94,34 @@
   }
 
   @Test
+  public void listReviewersByEmail() throws Exception {
+    AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
+
+    for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
+      PushOneCommit.Result r = createChange();
+
+      AddReviewerInput input = new AddReviewerInput();
+      input.reviewer = toRfcAddressString(acc);
+      input.state = state;
+      gApi.changes().id(r.getChangeId()).addReviewer(input);
+
+      RestResponse restResponse =
+          adminRestSession.get("/changes/" + r.getChangeId() + "/reviewers/");
+      restResponse.assertOK();
+      Type type = new TypeToken<List<ReviewerInfo>>() {}.getType();
+      List<ReviewerInfo> reviewers = newGson().fromJson(restResponse.getReader(), type);
+      restResponse.consume();
+
+      assertThat(reviewers).hasSize(1);
+      ReviewerInfo reviewerInfo = Iterables.getOnlyElement(reviewers);
+      assertThat(reviewerInfo._accountId).isNull();
+      assertThat(reviewerInfo.name).isEqualTo(acc.name);
+      assertThat(reviewerInfo.email).isEqualTo(acc.email);
+    }
+  }
+
+  @Test
   public void removeByEmail() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
 
     for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
@@ -117,7 +141,6 @@
 
   @Test
   public void convertFromCCToReviewer() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
 
     PushOneCommit.Result r = createChange();
@@ -139,7 +162,6 @@
 
   @Test
   public void addedReviewersGetNotified() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
 
     for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
@@ -159,7 +181,6 @@
 
   @Test
   public void removingReviewerTriggersNotification() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
 
     for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
@@ -192,7 +213,6 @@
 
   @Test
   public void reviewerAndCCReceiveRegularNotification() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
 
     for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
@@ -215,8 +235,6 @@
 
   @Test
   public void reviewerAndCCReceiveSameEmail() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     PushOneCommit.Result r = createChange();
     for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
       for (int i = 0; i < 10; i++) {
@@ -241,8 +259,6 @@
 
   @Test
   public void addingMultipleReviewersAndCCsAtOnceSendsOnlyOneEmail() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     PushOneCommit.Result r = createChange();
     ReviewInput reviewInput = new ReviewInput();
     for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
@@ -259,7 +275,6 @@
 
   @Test
   public void rejectMissingEmail() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     PushOneCommit.Result r = createChange();
 
     AddReviewerResult result = gApi.changes().id(r.getChangeId()).addReviewer("");
@@ -269,7 +284,6 @@
 
   @Test
   public void rejectMalformedEmail() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     PushOneCommit.Result r = createChange();
 
     AddReviewerResult result = gApi.changes().id(r.getChangeId()).addReviewer("Foo Bar <foo.bar@");
@@ -279,8 +293,6 @@
 
   @Test
   public void rejectWhenFeatureIsDisabled() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     ConfigInput conf = new ConfigInput();
     conf.enableReviewerByEmail = InheritableBoolean.FALSE;
     gApi.projects().name(project.get()).config(conf);
@@ -297,7 +309,6 @@
 
   @Test
   public void reviewersByEmailAreServedFromIndex() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
 
     for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
@@ -322,7 +333,6 @@
 
   @Test
   public void addExistingReviewerByEmailShortCircuits() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     PushOneCommit.Result r = createChange();
 
     AddReviewerInput input = new AddReviewerInput();
@@ -340,7 +350,6 @@
 
   @Test
   public void addExistingCcByEmailShortCircuits() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     PushOneCommit.Result r = createChange();
 
     AddReviewerInput input = new AddReviewerInput();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
index 6a9a27c..069607a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
 import static com.google.gerrit.extensions.client.ReviewerState.CC;
 import static com.google.gerrit.extensions.client.ReviewerState.REMOVED;
@@ -134,31 +133,18 @@
     assertThat(result.confirm).isNull();
     assertThat(result.error).isNull();
     ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
-    if (notesMigration.readChanges()) {
-      assertThat(result.reviewers).isNull();
-      assertThat(result.ccs).hasSize(1);
-      AccountInfo ai = result.ccs.get(0);
-      assertThat(ai._accountId).isEqualTo(user.id.get());
-      assertReviewers(c, CC, user);
-    } else {
-      assertThat(result.ccs).isNull();
-      assertThat(result.reviewers).hasSize(1);
-      AccountInfo ai = result.reviewers.get(0);
-      assertThat(ai._accountId).isEqualTo(user.id.get());
-      assertReviewers(c, REVIEWER, user);
-    }
+    assertThat(result.reviewers).isNull();
+    assertThat(result.ccs).hasSize(1);
+    AccountInfo ai = result.ccs.get(0);
+    assertThat(ai._accountId).isEqualTo(user.id.get());
+    assertReviewers(c, CC, user);
 
     // Verify email was sent to CCed account.
     List<Message> messages = sender.getMessages();
     assertThat(messages).hasSize(1);
     Message m = messages.get(0);
     assertThat(m.rcpt()).containsExactly(user.emailAddress);
-    if (notesMigration.readChanges()) {
-      assertThat(m.body()).contains(admin.fullName + " has uploaded this change for review.");
-    } else {
-      assertThat(m.body()).contains("Hello " + user.fullName + ",\n");
-      assertThat(m.body()).contains("I'd like you to do a code review.");
-    }
+    assertThat(m.body()).contains(admin.fullName + " has uploaded this change for review.");
   }
 
   @Test
@@ -185,18 +171,9 @@
     assertThat(result.input).isEqualTo(in.reviewer);
     assertThat(result.confirm).isNull();
     assertThat(result.error).isNull();
-    if (notesMigration.readChanges()) {
-      assertThat(result.reviewers).isNull();
-    } else {
-      assertThat(result.ccs).isNull();
-    }
+    assertThat(result.reviewers).isNull();
     ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
-    if (notesMigration.readChanges()) {
-      assertReviewers(c, CC, firstUsers);
-    } else {
-      assertReviewers(c, REVIEWER, firstUsers);
-      assertReviewers(c, CC);
-    }
+    assertReviewers(c, CC, firstUsers);
 
     // Verify emails were sent to each of the group's accounts.
     List<Message> messages = sender.getMessages();
@@ -222,19 +199,10 @@
     assertThat(result.confirm).isNull();
     assertThat(result.error).isNull();
     c = gApi.changes().id(r.getChangeId()).get();
-    if (notesMigration.readChanges()) {
-      assertThat(result.ccs).hasSize(3);
-      assertThat(result.reviewers).isNull();
-      assertReviewers(c, REVIEWER, reviewer);
-      assertReviewers(c, CC, users);
-    } else {
-      assertThat(result.ccs).isNull();
-      assertThat(result.reviewers).hasSize(3);
-      List<TestAccount> expectedUsers = new ArrayList<>(users.size() + 2);
-      expectedUsers.addAll(users);
-      expectedUsers.add(reviewer);
-      assertReviewers(c, REVIEWER, expectedUsers);
-    }
+    assertThat(result.ccs).hasSize(3);
+    assertThat(result.reviewers).isNull();
+    assertReviewers(c, REVIEWER, reviewer);
+    assertReviewers(c, CC, users);
 
     messages = sender.getMessages();
     assertThat(messages).hasSize(1);
@@ -243,11 +211,6 @@
     for (int i = 0; i < 3; i++) {
       expectedAddresses.add(users.get(users.size() - i - 1).emailAddress);
     }
-    if (!notesMigration.readChanges()) {
-      for (int i = 0; i < 3; i++) {
-        expectedAddresses.add(users.get(i).emailAddress);
-      }
-    }
     expectedAddresses.add(reviewer.emailAddress);
     assertThat(m.rcpt()).containsExactlyElementsIn(expectedAddresses);
   }
@@ -261,13 +224,8 @@
     in.state = CC;
     addReviewer(changeId, in);
     ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
-    if (notesMigration.readChanges()) {
-      assertReviewers(c, REVIEWER);
-      assertReviewers(c, CC, user);
-    } else {
-      assertReviewers(c, REVIEWER, user);
-      assertReviewers(c, CC);
-    }
+    assertReviewers(c, REVIEWER);
+    assertReviewers(c, CC, user);
 
     in.state = REVIEWER;
     addReviewer(changeId, in);
@@ -293,15 +251,8 @@
 
     // Verify user is added to CC list.
     ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
-    if (notesMigration.readChanges()) {
-      assertReviewers(c, REVIEWER);
-      assertReviewers(c, CC, user);
-    } else {
-      // If we aren't reading from NoteDb, the user will appear as a
-      // reviewer.
-      assertReviewers(c, REVIEWER, user);
-      assertReviewers(c, CC);
-    }
+    assertReviewers(c, REVIEWER);
+    assertReviewers(c, CC, user);
   }
 
   @Test
@@ -350,26 +301,13 @@
 
     // Verify reviewer state.
     ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
-    if (notesMigration.readChanges()) {
-      assertReviewers(c, REVIEWER);
-      assertReviewers(c, CC, user);
-      // Verify no approvals were added.
-      assertThat(c.labels).isNotNull();
-      LabelInfo label = c.labels.get("Code-Review");
-      assertThat(label).isNotNull();
-      assertThat(label.all).isNull();
-    } else {
-      // When approvals are stored in ReviewDb, we still create a label for
-      // the reviewing user, and force them into the REVIEWER state.
-      assertReviewers(c, REVIEWER, user);
-      assertReviewers(c, CC);
-      LabelInfo label = c.labels.get("Code-Review");
-      assertThat(label).isNotNull();
-      assertThat(label.all).isNotNull();
-      assertThat(label.all).hasSize(1);
-      ApprovalInfo approval = label.all.get(0);
-      assertThat(approval._accountId).isEqualTo(user.getId().get());
-    }
+    assertReviewers(c, REVIEWER);
+    assertReviewers(c, CC, user);
+    // Verify no approvals were added.
+    assertThat(c.labels).isNotNull();
+    LabelInfo label = c.labels.get("Code-Review");
+    assertThat(label).isNotNull();
+    assertThat(label.all).isNull();
   }
 
   @Test
@@ -449,14 +387,8 @@
     // Verify reviewer and CC were added. If not in NoteDb read mode, both
     // parties will be returned as CCed.
     ChangeInfo c = gApi.changes().id(r.getChangeId()).get();
-    if (notesMigration.readChanges()) {
-      assertReviewers(c, REVIEWER, admin, user);
-      assertReviewers(c, CC, observer);
-    } else {
-      // In legacy mode, everyone should be a reviewer.
-      assertReviewers(c, REVIEWER, admin, user, observer);
-      assertReviewers(c, CC);
-    }
+    assertReviewers(c, REVIEWER, admin, user);
+    assertReviewers(c, CC, observer);
 
     // Verify emails were sent to added reviewers.
     List<Message> messages = sender.getMessages();
@@ -549,23 +481,12 @@
     c = gApi.changes().id(r.getChangeId()).get();
     assertThat(c.messages).hasSize(2);
 
-    if (notesMigration.readChanges()) {
-      assertReviewers(c, REVIEWER, admin, user);
-      assertReviewers(c, CC, users.subList(0, mediumGroupSize));
-    } else {
-      // If not in NoteDb mode, then everyone is a REVIEWER.
-      List<TestAccount> expected = users.subList(0, mediumGroupSize);
-      expected.add(admin);
-      expected.add(user);
-      assertReviewers(c, REVIEWER, expected);
-      assertReviewers(c, CC);
-    }
+    assertReviewers(c, REVIEWER, admin, user);
+    assertReviewers(c, CC, users.subList(0, mediumGroupSize));
   }
 
   @Test
   public void noteDbAddReviewerToReviewerChangeInfo() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     PushOneCommit.Result r = createChange();
     String changeId = r.getChangeId();
     AddReviewerInput in = new AddReviewerInput();
@@ -645,9 +566,6 @@
     assertThat(reviewerResult.reviewers).hasSize(1);
 
     // Repeat the above for CCs
-    if (!notesMigration.readChanges()) {
-      return;
-    }
     r = createChange();
     input = ReviewInput.approve().reviewer(group1, CC, false).reviewer(group2, CC, false);
     result = review(r.getChangeId(), r.getCommit().name(), input);
@@ -792,7 +710,6 @@
 
   @Test
   public void addExistingReviewerShortCircuits() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     PushOneCommit.Result r = createChange();
 
     AddReviewerInput input = new AddReviewerInput();
@@ -809,7 +726,6 @@
 
   @Test
   public void addExistingCcShortCircuits() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     PushOneCommit.Result r = createChange();
 
     AddReviewerInput input = new AddReviewerInput();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
index baf56de..c5ba5e4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
@@ -144,8 +144,8 @@
   public void rejectDoubleInheritance() throws Exception {
     setApiUser(admin);
     // Create separate projects to test the config
-    Project.NameKey parent = createProject("projectToInheritFrom");
-    Project.NameKey child = createProject("projectWithMalformedConfig");
+    Project.NameKey parent = createProjectOverAPI("projectToInheritFrom", null, true, null);
+    Project.NameKey child = createProjectOverAPI("projectWithMalformedConfig", null, true, null);
 
     String config =
         gApi.projects()
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index 9218336..28252e5 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.common.data.Permission.READ;
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
@@ -276,8 +275,6 @@
 
   @Test
   public void noteDbCommit() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     ChangeInfo c = assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
     try (Repository repo = repoManager.openRepository(project);
         RevWalk rw = new RevWalk(repo)) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
index 864f08d..47ec0d2 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static java.util.Objects.requireNonNull;
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -33,17 +32,11 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.testing.TestTimeUtil;
 import org.junit.AfterClass;
-import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
 @NoHttpd
 public class HashtagsIT extends AbstractDaemonTest {
-  @Before
-  public void before() {
-    assume().that(notesMigration.readChanges()).isTrue();
-  }
-
   @BeforeClass
   public static void setTimeForTesting() {
     TestTimeUtil.resetWithClockStep(1, SECONDS);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
index bb114e7..7cdd266 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -35,6 +36,7 @@
 public class IndexChangeIT extends AbstractDaemonTest {
 
   @Inject protected GroupOperations groupOperations;
+  @Inject private ProjectOperations projectOperations;
 
   @Test
   public void indexChange() throws Exception {
@@ -58,7 +60,7 @@
     gApi.groups().id(group).addMembers("admin", "user", user2.username);
 
     // Create a project and restrict its visibility to the group
-    Project.NameKey p = createProject("p");
+    Project.NameKey p = projectOperations.newProject().create();
     try (ProjectConfigUpdate u = updateProject(p)) {
       Util.allow(
           u.getConfig(),
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java b/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java
index 0ece00a..c2d3817 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java
@@ -19,12 +19,14 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.api.projects.ConfigInput;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -34,11 +36,12 @@
 public class PrivateByDefaultIT extends AbstractDaemonTest {
   private Project.NameKey project1;
   private Project.NameKey project2;
+  @Inject private ProjectOperations projectOperations;
 
   @Before
   public void setUp() throws Exception {
-    project1 = createProject("project-1");
-    project2 = createProject("project-2", project1);
+    project1 = projectOperations.newProject().create();
+    project2 = projectOperations.newProject().parent(project1).create();
     setPrivateByDefault(project1, InheritableBoolean.FALSE);
   }
 
@@ -127,7 +130,7 @@
   public void pushDraftsWithPrivateByDefaultAndDisablePrivateChangesTrue() throws Exception {
     setPrivateByDefault(project2, InheritableBoolean.TRUE);
 
-    RevCommit initialHead = getRemoteHead();
+    RevCommit initialHead = getRemoteHead(project2, "master");
     TestRepository<InMemoryRepository> testRepo = cloneProject(project2);
     PushOneCommit.Result result =
         pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master%draft");
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
index 8160d9a..8a81d6d 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
 import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
 
@@ -31,19 +30,13 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.change.TestSubmitInput;
 import com.google.gerrit.server.git.ChangeMessageModifier;
 import com.google.gerrit.server.submit.CommitMergeStatus;
 import com.google.inject.Inject;
 import java.util.List;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
 import org.junit.Test;
 
 public class SubmitByCherryPickIT extends AbstractSubmit {
@@ -389,76 +382,4 @@
         change2.getChangeId(),
         headAfterFirstSubmit.name());
   }
-
-  @Test
-  public void repairChangeStateAfterFailure() throws Exception {
-    // In NoteDb-only mode, repo and meta updates are atomic (at least in InMemoryRepository).
-    assume().that(notesMigration.disableChangeReviewDb()).isFalse();
-
-    RevCommit initialHead = getRemoteHead();
-    PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
-    submit(change.getChangeId());
-
-    RevCommit headAfterFirstSubmit = getRemoteHead();
-    testRepo.reset(initialHead);
-    PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
-    Change.Id id2 = change2.getChange().getId();
-    TestSubmitInput failInput = new TestSubmitInput();
-    failInput.failAfterRefUpdates = true;
-    submit(
-        change2.getChangeId(),
-        failInput,
-        ResourceConflictException.class,
-        "Failing after ref updates");
-    RevCommit headAfterFailedSubmit = getRemoteHead();
-
-    // Bad: ref advanced but change wasn't updated.
-    PatchSet.Id psId1 = new PatchSet.Id(id2, 1);
-    PatchSet.Id psId2 = new PatchSet.Id(id2, 2);
-    ChangeInfo info = gApi.changes().id(id2.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.NEW);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
-    assertThat(getPatchSet(psId2)).isNull();
-
-    ObjectId rev2;
-    try (Repository repo = repoManager.openRepository(project);
-        RevWalk rw = new RevWalk(repo)) {
-      ObjectId rev1 = repo.exactRef(psId1.toRefName()).getObjectId();
-      assertThat(rev1).isNotNull();
-
-      rev2 = repo.exactRef(psId2.toRefName()).getObjectId();
-      assertThat(rev2).isNotNull();
-      assertThat(rev2).isNotEqualTo(rev1);
-      assertThat(rw.parseCommit(rev2).getParent(0)).isEqualTo(headAfterFirstSubmit);
-
-      assertThat(repo.exactRef("refs/heads/master").getObjectId()).isEqualTo(rev2);
-    }
-
-    submit(change2.getChangeId());
-
-    // Change status and patch set entities were updated, and branch tip stayed
-    // the same.
-    RevCommit headAfterSecondSubmit = getRemoteHead();
-    assertThat(headAfterSecondSubmit).isEqualTo(headAfterFailedSubmit);
-    info = gApi.changes().id(id2.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(2);
-    PatchSet ps2 = getPatchSet(psId2);
-    assertThat(ps2).isNotNull();
-    assertThat(ps2.getRevision().get()).isEqualTo(rev2.name());
-    assertThat(Iterables.getLast(info.messages).message)
-        .isEqualTo(
-            "Change has been successfully cherry-picked as " + rev2.name() + " by Administrator");
-
-    try (Repository repo = repoManager.openRepository(project)) {
-      assertThat(repo.exactRef("refs/heads/master").getObjectId()).isEqualTo(rev2);
-    }
-
-    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
-    assertChangeMergedEvents(
-        change.getChangeId(),
-        headAfterFirstSubmit.name(),
-        change2.getChangeId(),
-        headAfterSecondSubmit.name());
-  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index ea8b98a..ccb684c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -15,26 +15,16 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.GitUtil.pushHead;
 
-import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ActionInfo;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.change.TestSubmitInput;
 import java.util.Map;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.PushResult;
 import org.junit.Test;
 
@@ -146,52 +136,6 @@
   }
 
   @Test
-  public void repairChangeStateAfterFailure() throws Exception {
-    // In NoteDb-only mode, repo and meta updates are atomic (at least in InMemoryRepository).
-    assume().that(notesMigration.disableChangeReviewDb()).isFalse();
-
-    PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
-    Change.Id id = change.getChange().getId();
-    TestSubmitInput failInput = new TestSubmitInput();
-    failInput.failAfterRefUpdates = true;
-    submit(
-        change.getChangeId(),
-        failInput,
-        ResourceConflictException.class,
-        "Failing after ref updates");
-
-    // Bad: ref advanced but change wasn't updated.
-    PatchSet.Id psId = new PatchSet.Id(id, 1);
-    ChangeInfo info = gApi.changes().id(id.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.NEW);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
-
-    ObjectId rev;
-    try (Repository repo = repoManager.openRepository(project);
-        RevWalk rw = new RevWalk(repo)) {
-      rev = repo.exactRef(psId.toRefName()).getObjectId();
-      assertThat(rev).isNotNull();
-      assertThat(repo.exactRef("refs/heads/master").getObjectId()).isEqualTo(rev);
-    }
-
-    submit(change.getChangeId());
-
-    // Change status was updated, and branch tip stayed the same.
-    info = gApi.changes().id(id.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
-    assertThat(Iterables.getLast(info.messages).message)
-        .isEqualTo("Change has been successfully merged by Administrator");
-
-    try (Repository repo = repoManager.openRepository(project)) {
-      assertThat(repo.exactRef("refs/heads/master").getObjectId()).isEqualTo(rev);
-    }
-
-    assertRefUpdatedEvents();
-    assertChangeMergedEvents(change.getChangeId(), getRemoteHead().name());
-  }
-
-  @Test
   public void submitSameCommitsAsInExperimentalBranch() throws Exception {
     RevCommit initialHead = getRemoteHead();
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
index 229122b..d92cf30 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -21,6 +21,7 @@
 
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.api.changes.CherryPickInput;
@@ -34,6 +35,7 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
 import java.io.File;
 import java.io.InputStream;
 import java.nio.file.Files;
@@ -51,6 +53,7 @@
 import org.junit.Test;
 
 public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
+  @Inject private ProjectOperations projectOperations;
 
   @Override
   protected SubmitType getSubmitType() {
@@ -132,9 +135,9 @@
 
   @Test
   public void submitChangesAcrossRepos() throws Exception {
-    Project.NameKey p1 = createProject("project-where-we-submit");
-    Project.NameKey p2 = createProject("project-impacted-via-topic");
-    Project.NameKey p3 = createProject("project-impacted-indirectly-via-topic");
+    Project.NameKey p1 = projectOperations.newProject().create();
+    Project.NameKey p2 = projectOperations.newProject().create();
+    Project.NameKey p3 = projectOperations.newProject().create();
 
     RevCommit initialHead2 = getRemoteHead(p2, "master");
     RevCommit initialHead3 = getRemoteHead(p3, "master");
@@ -209,9 +212,9 @@
 
   @Test
   public void submitChangesAcrossReposBlocked() throws Exception {
-    Project.NameKey p1 = createProject("project-where-we-submit");
-    Project.NameKey p2 = createProject("project-impacted-via-topic");
-    Project.NameKey p3 = createProject("project-impacted-indirectly-via-topic");
+    Project.NameKey p1 = projectOperations.newProject().create();
+    Project.NameKey p2 = projectOperations.newProject().create();
+    Project.NameKey p3 = projectOperations.newProject().create();
 
     TestRepository<?> repo1 = cloneProject(p1);
     TestRepository<?> repo2 = cloneProject(p2);
@@ -388,7 +391,7 @@
             "3",
             "a-topic-here");
 
-    Project.NameKey p3 = createProject("project-related-to-change3");
+    Project.NameKey p3 = projectOperations.newProject().create();
     TestRepository<?> repo3 = cloneProject(p3);
     RevCommit repo3Head = getRemoteHead(p3, "master");
     PushOneCommit.Result change3b =
@@ -710,8 +713,8 @@
     //                    (c2a) <= private
     assume().that(isSubmitWholeTopicEnabled()).isTrue();
 
-    Project.NameKey p1 = createProject("project-where-we-submit");
-    Project.NameKey p2 = createProject("project-impacted-via-topic");
+    Project.NameKey p1 = projectOperations.newProject().create();
+    Project.NameKey p2 = projectOperations.newProject().create();
 
     grantLabel("Code-Review", -2, 2, p1, "refs/heads/*", false, REGISTERED_USERS, false);
     grant(p1, "refs/*", Permission.SUBMIT, false, REGISTERED_USERS);
@@ -764,7 +767,7 @@
 
   @Test
   public void testPreviewSubmitTgz() throws Exception {
-    Project.NameKey p1 = createProject("project-name");
+    Project.NameKey p1 = projectOperations.newProject().create();
 
     TestRepository<?> repo1 = cloneProject(p1);
     PushOneCommit.Result change1 = createChange(repo1, "master", "test", "a.txt", "1", "topic");
@@ -789,6 +792,6 @@
         untarredFiles.add(entry.getName());
       }
     }
-    assertThat(untarredFiles).containsExactly(name("project-name") + ".git");
+    assertThat(untarredFiles).containsExactly(p1.get() + ".git");
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
index 4d499f0..981ba34 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
 import static java.util.stream.Collectors.toList;
@@ -23,31 +24,33 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.api.accounts.EmailInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.common.ChangeInput;
-import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
-import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.group.InternalGroup;
-import com.google.gerrit.server.restapi.group.CreateGroup;
 import com.google.inject.Inject;
-import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.IntStream;
 import org.junit.Before;
 import org.junit.Test;
 
 public class SuggestReviewersIT extends AbstractDaemonTest {
-  @Inject private CreateGroup createGroup;
+  @Inject private ProjectOperations projectOperations;
+  @Inject private AccountOperations accountOperations;
+  @Inject private GroupOperations groupOperations;
 
-  private InternalGroup group1;
-  private InternalGroup group2;
-  private InternalGroup group3;
+  private AccountGroup.UUID group1;
+  private AccountGroup.UUID group2;
+  private AccountGroup.UUID group3;
 
   private TestAccount user1;
   private TestAccount user2;
@@ -56,14 +59,14 @@
 
   @Before
   public void setUp() throws Exception {
-    group1 = newGroup("users1");
-    group2 = newGroup("users2");
-    group3 = newGroup("users3");
-
-    user1 = user("user1", "First1 Last1", group1);
-    user2 = user("user2", "First2 Last2", group2);
-    user3 = user("user3", "First3 Last3", group1, group2);
+    user1 = user("user1", "First1 Last1");
+    user2 = user("user2", "First2 Last2");
+    user3 = user("user3", "First3 Last3");
     user4 = user("jdoe", "John Doe", "JDOE");
+
+    group1 = groupOperations.newGroup().name(name("users1")).members(user1.id, user3.id).create();
+    group2 = groupOperations.newGroup().name(name("users2")).members(user2.id, user3.id).create();
+    group3 = groupOperations.newGroup().name(name("users3")).members(user1.id).create();
   }
 
   @Test
@@ -105,7 +108,8 @@
     assertReviewers(
         reviewers, ImmutableList.of(user1, user2, user3), ImmutableList.of(group1, group2));
 
-    reviewers = suggestReviewers(changeId, group3.getName(), 10);
+    String group3Name = groupOperations.group(group3).get().name();
+    reviewers = suggestReviewers(changeId, group3Name, 10);
     assertReviewers(reviewers, ImmutableList.of(), ImmutableList.of(group3));
 
     // Suggested accounts are ordered by activity. All users have no activity,
@@ -151,7 +155,7 @@
 
     setApiUser(user3);
     block("refs/*", "read", ANONYMOUS_USERS);
-    allow("refs/*", "read", group1.getGroupUUID());
+    allow("refs/*", "read", group1);
     reviewers = suggestReviewers(changeId, user2.username, 2);
     assertThat(reviewers).isEmpty();
   }
@@ -167,7 +171,7 @@
     assertThat(reviewers).isEmpty();
 
     setApiUser(user1); // Clear cached group info.
-    allowGlobalCapabilities(group1.getGroupUUID(), GlobalCapability.VIEW_ALL_ACCOUNTS);
+    allowGlobalCapabilities(group1, GlobalCapability.VIEW_ALL_ACCOUNTS);
     reviewers = suggestReviewers(changeId, user2.username, 2);
     assertThat(reviewers).hasSize(1);
     assertThat(Iterables.getOnlyElement(reviewers).account.name).isEqualTo(user2.fullName);
@@ -243,17 +247,11 @@
   }
 
   @Test
-  @GerritConfig(name = "addreviewer.maxAllowed", value = "2")
+  @GerritConfig(name = "addreviewer.maxAllowed", value = "1")
   @GerritConfig(name = "addreviewer.maxWithoutConfirmation", value = "1")
-  public void suggestReviewersGroupSizeConsiderations() throws Exception {
-    InternalGroup largeGroup = newGroup("large");
-    InternalGroup mediumGroup = newGroup("medium");
-
-    // Both groups have Administrator as a member. Add two users to large
-    // group to push it past maxAllowed, and one to medium group to push it
-    // past maxWithoutConfirmation.
-    user("individual 0", "Test0 Last0", largeGroup, mediumGroup);
-    user("individual 1", "Test1 Last1", largeGroup);
+  public void confirmationIsNeverRequestedForAccounts() throws Exception {
+    user("individual 0", "Test0 Last0");
+    user("individual 1", "Test1 Last1");
 
     String changeId = createChange().getChangeId();
     List<SuggestedReviewerInfo> reviewers;
@@ -265,21 +263,68 @@
     reviewer = reviewers.get(0);
     assertThat(reviewer.count).isEqualTo(1);
     assertThat(reviewer.confirm).isNull();
+  }
+
+  @Test
+  @GerritConfig(name = "addreviewer.maxAllowed", value = "2")
+  @GerritConfig(name = "addreviewer.maxWithoutConfirmation", value = "1")
+  public void suggestReviewersGroupSizeConsiderations() throws Exception {
+    AccountGroup.UUID largeGroup = createGroupWithArbitraryMembers(3);
+    String largeGroupName = groupOperations.group(largeGroup).get().name();
+    AccountGroup.UUID mediumGroup = createGroupWithArbitraryMembers(2);
+    String mediumGroupName = groupOperations.group(mediumGroup).get().name();
+
+    String changeId = createChange().getChangeId();
+    List<SuggestedReviewerInfo> reviewers;
+    SuggestedReviewerInfo reviewer;
 
     // Large group should never be suggested.
-    reviewers = suggestReviewers(changeId, largeGroup.getName(), 10);
+    reviewers = suggestReviewers(changeId, largeGroupName, 10);
     assertThat(reviewers).isEmpty();
 
     // Medium group should be suggested with appropriate count and confirm.
-    reviewers = suggestReviewers(changeId, mediumGroup.getName(), 10);
+    reviewers = suggestReviewers(changeId, mediumGroupName, 10);
     assertThat(reviewers).hasSize(1);
     reviewer = reviewers.get(0);
-    assertThat(reviewer.group.name).isEqualTo(mediumGroup.getName());
+    assertThat(reviewer.group.id).isEqualTo(mediumGroup.get());
     assertThat(reviewer.count).isEqualTo(2);
     assertThat(reviewer.confirm).isTrue();
   }
 
   @Test
+  @GerritConfig(name = "addreviewer.maxAllowed", value = "20")
+  @GerritConfig(name = "addreviewer.maxWithoutConfirmation", value = "0")
+  public void confirmationIsNotNecessaryForLargeGroupWhenLimitIsRemoved() throws Exception {
+    String changeId = createChange().getChangeId();
+    int numMembers = 15;
+    AccountGroup.UUID largeGroup = createGroupWithArbitraryMembers(numMembers);
+    String groupName = groupOperations.group(largeGroup).get().name();
+
+    List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, groupName, 10);
+
+    assertThat(reviewers).hasSize(1);
+    SuggestedReviewerInfo reviewer = Iterables.getOnlyElement(reviewers);
+    assertThat(reviewer.group.id).isEqualTo(largeGroup.get());
+    // Confirmation should not be necessary.
+    assertThat(reviewer.confirm).isNull();
+  }
+
+  @Test
+  @GerritConfig(name = "addreviewer.maxAllowed", value = "0")
+  public void largeGroupIsSuggestedWhenLimitIsRemoved() throws Exception {
+    String changeId = createChange().getChangeId();
+    int numMembers = 30;
+    AccountGroup.UUID largeGroup = createGroupWithArbitraryMembers(numMembers);
+    String groupName = groupOperations.group(largeGroup).get().name();
+
+    List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, groupName, 10);
+
+    assertThat(reviewers).hasSize(1);
+    SuggestedReviewerInfo reviewer = Iterables.getOnlyElement(reviewers);
+    assertThat(reviewer.group.id).isEqualTo(largeGroup.get());
+  }
+
+  @Test
   public void defaultReviewerSuggestion() throws Exception {
     TestAccount user1 = user("customuser1", "User1");
     TestAccount reviewer1 = user("customuser2", "User2");
@@ -372,7 +417,7 @@
   @Test
   public void reviewerRankingProjectIsolation() throws Exception {
     // Create new project
-    Project.NameKey newProject = createProject("test");
+    Project.NameKey newProject = projectOperations.newProject().create();
 
     // Create users who review changes in both the default and the new project
     String fullName = "Primum Finalis";
@@ -490,21 +535,20 @@
     return gApi.changes().id(changeId).suggestReviewers(query).withLimit(n).get();
   }
 
-  private InternalGroup newGroup(String name) throws Exception {
-    GroupInfo group =
-        createGroup.apply(TopLevelResource.INSTANCE, IdString.fromDecoded(name(name)), null);
-    return group(new AccountGroup.UUID(group.id));
+  private AccountGroup.UUID createGroupWithArbitraryMembers(int numMembers) {
+    Set<Account.Id> members =
+        IntStream.rangeClosed(1, numMembers)
+            .mapToObj(i -> accountOperations.newAccount().create())
+            .collect(toImmutableSet());
+    return groupOperations.newGroup().members(members).create();
   }
 
-  private TestAccount user(String name, String fullName, String emailName, InternalGroup... groups)
-      throws Exception {
-    String[] groupNames = Arrays.stream(groups).map(InternalGroup::getName).toArray(String[]::new);
-    return accountCreator.create(
-        name(name), name(emailName) + "@example.com", fullName, groupNames);
+  private TestAccount user(String name, String fullName, String emailName) throws Exception {
+    return accountCreator.create(name(name), name(emailName) + "@example.com", fullName);
   }
 
-  private TestAccount user(String name, String fullName, InternalGroup... groups) throws Exception {
-    return user(name, fullName, name, groups);
+  private TestAccount user(String name, String fullName) throws Exception {
+    return user(name, fullName, name);
   }
 
   private void reviewChange(String changeId) throws RestApiException {
@@ -525,10 +569,10 @@
     return gApi.changes().create(ci).get().changeId;
   }
 
-  private void assertReviewers(
+  private static void assertReviewers(
       List<SuggestedReviewerInfo> actual,
       List<TestAccount> expectedUsers,
-      List<InternalGroup> expectedGroups) {
+      List<AccountGroup.UUID> expectedGroups) {
     List<Integer> actualAccountIds =
         actual
             .stream()
@@ -542,7 +586,7 @@
         actual.stream().filter(i -> i.group != null).map(i -> i.group.id).collect(toList());
     assertThat(actualGroupIds)
         .containsExactlyElementsIn(
-            expectedGroups.stream().map(g -> g.getGroupUUID().get()).collect(toList()))
+            expectedGroups.stream().map(AccountGroup.UUID::get).collect(toList()))
         .inOrder();
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/WorkInProgressByDefaultIT.java b/javatests/com/google/gerrit/acceptance/rest/change/WorkInProgressByDefaultIT.java
index 34d87d0..e2fc14f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/WorkInProgressByDefaultIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/WorkInProgressByDefaultIT.java
@@ -18,12 +18,14 @@
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.api.projects.ConfigInput;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.junit.After;
@@ -33,11 +35,12 @@
 public class WorkInProgressByDefaultIT extends AbstractDaemonTest {
   private Project.NameKey project1;
   private Project.NameKey project2;
+  @Inject private ProjectOperations projectOperations;
 
   @Before
   public void setUp() throws Exception {
-    project1 = createProject("project-1");
-    project2 = createProject("project-2", project1);
+    project1 = projectOperations.newProject().create();
+    project2 = projectOperations.newProject().parent(project1).create();
   }
 
   @After
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
index 54195d1..e688216 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -76,7 +76,7 @@
 
   @Before
   public void setUp() throws Exception {
-    newProjectName = projectOperations.newProject().withEmptyCommit().create();
+    newProjectName = projectOperations.newProject().create();
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/BUILD b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
index dad3ca9..9a40346 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
@@ -8,6 +8,7 @@
         ":project",
         ":push_tag_util",
         ":refassert",
+        "//lib/commons:lang",
     ],
 )
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
index b426a37..d673f83 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.projects.BranchApi;
 import com.google.gerrit.extensions.api.projects.BranchInput;
@@ -31,16 +32,18 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.inject.Inject;
 import org.junit.Before;
 import org.junit.Test;
 
 public class DeleteBranchIT extends AbstractDaemonTest {
+  @Inject private ProjectOperations projectOperations;
 
   private Branch.NameKey testBranch;
 
   @Before
   public void setUp() throws Exception {
-    project = createProject(name("p"));
+    project = projectOperations.newProject().create();
     testBranch = new Branch.NameKey(project, "test");
     branch(testBranch).create(new BranchInput());
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
index 78d0270..48527af 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
@@ -18,12 +18,14 @@
 import com.google.gerrit.acceptance.GcAssert;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.inject.Inject;
 import org.junit.Before;
 import org.junit.Test;
 
 public class GarbageCollectionIT extends AbstractDaemonTest {
+  @Inject private ProjectOperations projectOperations;
 
   @Inject private GcAssert gcAssert;
 
@@ -31,7 +33,7 @@
 
   @Before
   public void setUp() throws Exception {
-    project2 = createProject("p2");
+    project2 = projectOperations.newProject().create();
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
index d5e811d..d736578 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
@@ -18,13 +18,16 @@
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
 import org.junit.Test;
 
 @NoHttpd
 public class GetChildProjectIT extends AbstractDaemonTest {
+  @Inject private ProjectOperations projectOperations;
 
   @Test
   public void getNonExistingChildProject_NotFound() throws Exception {
@@ -33,15 +36,15 @@
 
   @Test
   public void getNonChildProject_NotFound() throws Exception {
-    Project.NameKey p1 = createProject("p1");
-    Project.NameKey p2 = createProject("p2");
+    Project.NameKey p1 = projectOperations.newProject().create();
+    Project.NameKey p2 = projectOperations.newProject().create();
 
     assertChildNotFound(p1, p2.get());
   }
 
   @Test
   public void getChildProject() throws Exception {
-    Project.NameKey child = createProject("p1");
+    Project.NameKey child = projectOperations.newProject().create();
     ProjectInfo childInfo = gApi.projects().name(allProjects.get()).child(child.get()).get();
 
     assertProjectInfo(projectCache.get(child).getProject(), childInfo);
@@ -49,16 +52,16 @@
 
   @Test
   public void getGrandChildProject_NotFound() throws Exception {
-    Project.NameKey child = createProject("p1");
-    Project.NameKey grandChild = createProject("p1.1", child);
+    Project.NameKey child = projectOperations.newProject().create();
+    Project.NameKey grandChild = projectOperations.newProject().parent(child).create();
 
     assertChildNotFound(allProjects, grandChild.get());
   }
 
   @Test
   public void getGrandChildProjectWithRecursiveFlag() throws Exception {
-    Project.NameKey child = createProject("p1");
-    Project.NameKey grandChild = createProject("p1.1", child);
+    Project.NameKey child = projectOperations.newProject().create();
+    Project.NameKey grandChild = projectOperations.newProject().parent(child).create();
 
     ProjectInfo grandChildInfo =
         gApi.projects().name(allProjects.get()).child(grandChild.get()).get(true);
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
index dd92a7a..a0bc450 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
@@ -18,12 +18,16 @@
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
+import org.apache.commons.lang.RandomStringUtils;
 import org.junit.Test;
 
 @NoHttpd
 public class ListChildProjectsIT extends AbstractDaemonTest {
+  @Inject private ProjectOperations projectOperations;
 
   @Test
   public void listChildrenOfNonExistingProject_NotFound() throws Exception {
@@ -39,23 +43,27 @@
 
   @Test
   public void listChildren() throws Exception {
-    Project.NameKey child1 = createProject("p1");
-    Project.NameKey child1_1 = createProject("p1.1", child1);
-    Project.NameKey child1_2 = createProject("p1.2", child1);
+    Project.NameKey child1 = projectOperations.newProject().create();
+    Project.NameKey child1_1 = projectOperations.newProject().parent(child1).create();
+    Project.NameKey child1_2 = projectOperations.newProject().parent(child1).create();
 
+    assertThatNameList(gApi.projects().name(child1.get()).children()).isOrdered();
     assertThatNameList(gApi.projects().name(child1.get()).children())
-        .containsExactly(child1_1, child1_2)
-        .inOrder();
+        .containsExactly(child1_1, child1_2);
   }
 
   @Test
   public void listChildrenRecursively() throws Exception {
-    Project.NameKey child1 = createProject("p1");
-    createProject("p2");
-    Project.NameKey child1_1 = createProject("p1.1", child1);
-    Project.NameKey child1_2 = createProject("p1.2", child1);
-    Project.NameKey child1_1_1 = createProject("p1.1.1", child1_1);
-    Project.NameKey child1_1_1_1 = createProject("p1.1.1.1", child1_1_1);
+    String prefix = RandomStringUtils.randomAlphabetic(8);
+    Project.NameKey child1 = projectOperations.newProject().name(prefix + "p1").create();
+    Project.NameKey child1_1 =
+        projectOperations.newProject().parent(child1).name(prefix + "p1.1").create();
+    Project.NameKey child1_2 =
+        projectOperations.newProject().parent(child1).name(prefix + "p1.2").create();
+    Project.NameKey child1_1_1 =
+        projectOperations.newProject().parent(child1_1).name(prefix + "p1.1.1").create();
+    Project.NameKey child1_1_1_1 =
+        projectOperations.newProject().parent(child1_1_1).name(prefix + "p1.1.1.1").create();
 
     assertThatNameList(gApi.projects().name(child1.get()).children(true))
         .containsExactly(child1_1, child1_1_1, child1_1_1_1, child1_2)
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
index cd88a56..7009e76 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.Sandboxed;
 import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.projects.ConfigInfo;
 import com.google.gerrit.extensions.api.projects.ConfigInput;
@@ -33,6 +34,7 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.project.testing.Util;
+import com.google.inject.Inject;
 import java.util.List;
 import java.util.Map;
 import org.junit.Test;
@@ -40,13 +42,14 @@
 @NoHttpd
 @Sandboxed
 public class ListProjectsIT extends AbstractDaemonTest {
+  @Inject private ProjectOperations projectOperations;
 
   @Test
   public void listProjects() throws Exception {
-    Project.NameKey someProject = createProject("some-project");
-    assertThatNameList(filter(gApi.projects().list().get()))
-        .containsExactly(allProjects, allUsers, project, someProject)
-        .inOrder();
+    Project.NameKey someProject = projectOperations.newProject().create();
+    assertThatNameList(gApi.projects().list().get())
+        .containsExactly(allProjects, allUsers, project, someProject);
+    assertThatNameList(gApi.projects().list().get()).isOrdered();
   }
 
   @Test
@@ -59,7 +62,7 @@
       u.save();
     }
 
-    assertThatNameList(filter(gApi.projects().list().get())).doesNotContain(project);
+    assertThatNameList(gApi.projects().list().get()).doesNotContain(project);
   }
 
   @Test
@@ -87,91 +90,94 @@
 
   @Test
   public void listProjectsWithLimit() throws Exception {
-    for (int i = 0; i < 5; i++) {
-      createProject("someProject" + i);
+    String pre = "lpwl-someProject";
+    int n = 6;
+    for (int i = 0; i < n; i++) {
+      projectOperations.newProject().name(pre + i).create();
     }
 
-    String p = name("");
-    // 5, plus p which was automatically created.
-    int n = 6;
     for (int i = 1; i <= n + 2; i++) {
-      assertThatNameList(gApi.projects().list().withPrefix(p).withLimit(i).get())
+      assertThatNameList(gApi.projects().list().withPrefix(pre).withLimit(i).get())
           .hasSize(Math.min(i, n));
     }
   }
 
   @Test
   public void listProjectsWithPrefix() throws Exception {
-    Project.NameKey someProject = createProject("some-project");
-    Project.NameKey someOtherProject = createProject("some-other-project");
-    createProject("project-awesome");
+    Project.NameKey someProject = projectOperations.newProject().name("listtest-p1").create();
+    Project.NameKey someOtherProject = projectOperations.newProject().name("listtest-p2").create();
+    projectOperations.newProject().name("other-prefix-project").create();
 
-    String p = name("some");
+    String p = "listtest";
     assertBadRequest(gApi.projects().list().withPrefix(p).withRegex(".*"));
     assertBadRequest(gApi.projects().list().withPrefix(p).withSubstring(p));
-    assertThatNameList(filter(gApi.projects().list().withPrefix(p).get()))
-        .containsExactly(someOtherProject, someProject)
-        .inOrder();
-    p = name("SOME");
-    assertThatNameList(filter(gApi.projects().list().withPrefix(p).get())).isEmpty();
+    assertThatNameList(gApi.projects().list().withPrefix(p).get())
+        .containsExactly(someOtherProject, someProject);
+    p = "notlisttest";
+    assertThatNameList(gApi.projects().list().withPrefix(p).get()).isEmpty();
   }
 
   @Test
   public void listProjectsWithRegex() throws Exception {
-    Project.NameKey someProject = createProject("some-project");
-    Project.NameKey someOtherProject = createProject("some-other-project");
-    Project.NameKey projectAwesome = createProject("project-awesome");
+    Project.NameKey someProject = projectOperations.newProject().name("lpwr-some-project").create();
+    Project.NameKey someOtherProject =
+        projectOperations.newProject().name("lpwr-some-other-project").create();
+    Project.NameKey projectAwesome =
+        projectOperations.newProject().name("lpwr-project-awesome").create();
 
     assertBadRequest(gApi.projects().list().withRegex("[.*"));
     assertBadRequest(gApi.projects().list().withRegex(".*").withPrefix("p"));
     assertBadRequest(gApi.projects().list().withRegex(".*").withSubstring("p"));
 
-    assertThatNameList(filter(gApi.projects().list().withRegex(".*some").get()))
+    assertThatNameList(gApi.projects().list().withRegex(".*some").get())
         .containsExactly(projectAwesome);
-    String r = name("some-project$").replace(".", "\\.");
-    assertThatNameList(filter(gApi.projects().list().withRegex(r).get()))
-        .containsExactly(someProject);
-    assertThatNameList(filter(gApi.projects().list().withRegex(".*").get()))
+    String r = ("lpwr-some-project$").replace(".", "\\.");
+    assertThatNameList(gApi.projects().list().withRegex(r).get()).containsExactly(someProject);
+    assertThatNameList(gApi.projects().list().withRegex(".*").get())
         .containsExactly(
-            allProjects, allUsers, project, projectAwesome, someOtherProject, someProject)
-        .inOrder();
+            allProjects, allUsers, project, projectAwesome, someOtherProject, someProject);
   }
 
   @Test
   public void listProjectsWithStart() throws Exception {
+    String pre = "lpws-";
     for (int i = 0; i < 5; i++) {
-      createProject(new Project.NameKey("someProject" + i).get());
+      projectOperations.newProject().name(pre + i).create();
     }
 
-    String p = name("");
-    List<ProjectInfo> all = gApi.projects().list().withPrefix(p).get();
-    // 5, plus p which was automatically created.
-    int n = 6;
+    List<ProjectInfo> all = gApi.projects().list().withPrefix(pre).get();
+    int n = 5;
     assertThat(all).hasSize(n);
-    assertThatNameList(gApi.projects().list().withPrefix(p).withStart(n - 1).get())
+    assertThatNameList(gApi.projects().list().withPrefix(pre).withStart(n - 1).get())
         .containsExactly(new Project.NameKey(Iterables.getLast(all).name));
   }
 
   @Test
   public void listProjectsWithSubstring() throws Exception {
-    Project.NameKey someProject = createProject("some-project");
-    Project.NameKey someOtherProject = createProject("some-other-project");
-    Project.NameKey projectAwesome = createProject("project-awesome");
+    Project.NameKey someProject = projectOperations.newProject().name("some-project").create();
+    Project.NameKey someOtherProject =
+        projectOperations.newProject().name("some-other-project").create();
+    Project.NameKey projectAwesome =
+        projectOperations.newProject().name("project-awesome").create();
 
     assertBadRequest(gApi.projects().list().withSubstring("some").withRegex(".*"));
     assertBadRequest(gApi.projects().list().withSubstring("some").withPrefix("some"));
-    assertThatNameList(filter(gApi.projects().list().withSubstring("some").get()))
-        .containsExactly(projectAwesome, someOtherProject, someProject)
-        .inOrder();
-    assertThatNameList(filter(gApi.projects().list().withSubstring("SOME").get()))
-        .containsExactly(projectAwesome, someOtherProject, someProject)
-        .inOrder();
+    assertThatNameList(gApi.projects().list().withSubstring("some").get())
+        .containsExactly(projectAwesome, someOtherProject, someProject);
+    assertThatNameList(gApi.projects().list().withSubstring("SOME").get())
+        .containsExactly(projectAwesome, someOtherProject, someProject);
   }
 
   @Test
   public void listProjectsWithTree() throws Exception {
-    Project.NameKey someParentProject = createProject("some-parent-project");
-    Project.NameKey someChildProject = createProject("some-child-project", someParentProject);
+    Project.NameKey someParentProject =
+        projectOperations.newProject().name("some-parent-project").create();
+    Project.NameKey someChildProject =
+        projectOperations
+            .newProject()
+            .name("some-child-project")
+            .parent(someParentProject)
+            .create();
 
     Map<String, ProjectInfo> result = gApi.projects().list().withTree(true).getAsMap();
     assertThat(result).containsKey(someChildProject.get());
@@ -184,15 +190,14 @@
         gApi.projects().list().withType(FilterType.PERMISSIONS).getAsMap();
     assertThat(result.keySet()).containsExactly(allProjects.get(), allUsers.get());
 
-    assertThatNameList(filter(gApi.projects().list().withType(FilterType.ALL).get()))
-        .containsExactly(allProjects, allUsers, project)
-        .inOrder();
+    assertThatNameList(gApi.projects().list().withType(FilterType.ALL).get())
+        .containsExactly(allProjects, allUsers, project);
   }
 
   @Test
   public void listWithHiddenAndReadonlyProjects() throws Exception {
-    Project.NameKey hidden = createProject("project-to-hide");
-    Project.NameKey readonly = createProject("project-to-read");
+    Project.NameKey hidden = projectOperations.newProject().create();
+    Project.NameKey readonly = projectOperations.newProject().create();
 
     // Set project read-only
     ConfigInput input = new ConfigInput();
@@ -203,8 +208,7 @@
     // The hidden project is included because it was not hidden yet.
     // The read-only project is included.
     assertThatNameList(gApi.projects().list().get())
-        .containsExactly(allProjects, allUsers, project, hidden, readonly)
-        .inOrder();
+        .containsExactly(allProjects, allUsers, project, hidden, readonly);
 
     // Hide the project
     input.state = ProjectState.HIDDEN;
@@ -216,18 +220,15 @@
 
     // Hidden project is not included in the list
     assertThatNameList(gApi.projects().list().get())
-        .containsExactly(allProjects, allUsers, project, readonly)
-        .inOrder();
+        .containsExactly(allProjects, allUsers, project, readonly);
 
     // ALL filter applies to type, and doesn't include hidden state
     assertThatNameList(gApi.projects().list().withType(FilterType.ALL).get())
-        .containsExactly(allProjects, allUsers, project, readonly)
-        .inOrder();
+        .containsExactly(allProjects, allUsers, project, readonly);
 
     // "All" boolean option causes hidden projects to be included
     assertThatNameList(gApi.projects().list().withAll(true).get())
-        .containsExactly(allProjects, allUsers, project, hidden, readonly)
-        .inOrder();
+        .containsExactly(allProjects, allUsers, project, hidden, readonly);
 
     // "State" option causes only the projects in that state to be included
     assertThatNameList(gApi.projects().list().withState(ProjectState.HIDDEN).get())
@@ -235,8 +236,7 @@
     assertThatNameList(gApi.projects().list().withState(ProjectState.READ_ONLY).get())
         .containsExactly(readonly);
     assertThatNameList(gApi.projects().list().withState(ProjectState.ACTIVE).get())
-        .containsExactly(allProjects, allUsers, project)
-        .inOrder();
+        .containsExactly(allProjects, allUsers, project);
 
     // Cannot use "all" and "state" together
     assertBadRequest(gApi.projects().list().withAll(true).withState(ProjectState.ACTIVE));
@@ -250,16 +250,4 @@
       // Expected.
     }
   }
-
-  private Iterable<ProjectInfo> filter(Iterable<ProjectInfo> infos) {
-    String prefix = name("");
-    return Iterables.filter(
-        infos,
-        p -> {
-          return p.name != null
-              && (p.name.equals(allProjects.get())
-                  || p.name.equals(allUsers.get())
-                  || p.name.startsWith(prefix));
-        });
-  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
index c78b47b..3e51260 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
@@ -19,9 +19,11 @@
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
 import java.util.Arrays;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
@@ -29,6 +31,8 @@
 import org.junit.Test;
 
 public class ProjectLevelConfigIT extends AbstractDaemonTest {
+  @Inject private ProjectOperations projectOperations;
+
   @Before
   public void setUp() throws Exception {
     fetch(testRepo, RefNames.REFS_CONFIG + ":refs/heads/config");
@@ -82,7 +86,7 @@
         .to(RefNames.REFS_CONFIG)
         .assertOkStatus();
 
-    Project.NameKey childProject = createProject("child", project);
+    Project.NameKey childProject = projectOperations.newProject().parent(project).create();
     TestRepository<?> childTestRepo = cloneProject(childProject);
     fetch(childTestRepo, RefNames.REFS_CONFIG + ":refs/heads/config");
     childTestRepo.reset("refs/heads/config");
@@ -137,7 +141,7 @@
         .to(RefNames.REFS_CONFIG)
         .assertOkStatus();
 
-    Project.NameKey childProject = createProject("child", project);
+    Project.NameKey childProject = projectOperations.newProject().parent(project).create();
     TestRepository<?> childTestRepo = cloneProject(childProject);
     fetch(childTestRepo, RefNames.REFS_CONFIG + ":refs/heads/config");
     childTestRepo.reset("refs/heads/config");
diff --git a/javatests/com/google/gerrit/acceptance/rest/util/RestCall.java b/javatests/com/google/gerrit/acceptance/rest/util/RestCall.java
index a322089..7b0002c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/util/RestCall.java
+++ b/javatests/com/google/gerrit/acceptance/rest/util/RestCall.java
@@ -18,8 +18,8 @@
 
 import com.google.auto.value.AutoValue;
 import java.util.Optional;
-import jdk.nashorn.internal.ir.annotations.Ignore;
 import org.apache.commons.lang.StringUtils;
+import org.junit.Ignore;
 
 /** Data container for test REST requests. */
 @Ignore
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 0d40a1c..a352baa 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
 import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
 import static java.util.stream.Collectors.groupingBy;
@@ -946,7 +945,6 @@
 
   @Test
   public void jsonCommentHasLegacyFormatFalse() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     PushOneCommit.Result result = createChange();
     Change.Id changeId = result.getChange().getId();
     addComment(result.getChangeId(), "comment");
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index 2af90a8..460cd30 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.server.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.extensions.common.ProblemInfo.Status.FIXED;
 import static com.google.gerrit.extensions.common.ProblemInfo.Status.FIX_FAILED;
 import static com.google.gerrit.testing.TestChanges.newPatchSet;
@@ -35,7 +34,6 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
@@ -43,7 +41,6 @@
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.ConsistencyChecker;
 import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
@@ -52,8 +49,6 @@
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gerrit.server.update.RepoContext;
 import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.NoteDbMode;
 import com.google.gerrit.testing.TestChanges;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -88,8 +83,6 @@
 
   @Inject private ChangeNoteUtil noteUtil;
 
-  @Inject @AnonymousCowardName private String anonymousCowardName;
-
   @Inject private Sequences sequences;
 
   private RevCommit tip;
@@ -97,11 +90,6 @@
   private ConsistencyChecker checker;
   private TestRepository<InMemoryRepository> serverSideTestRepo;
 
-  private void assumeNoteDbDisabled() {
-    assume().that(notesMigration.readChanges()).isFalse();
-    assume().that(NoteDbMode.get()).isNotEqualTo(NoteDbMode.CHECK);
-  }
-
   @Before
   public void setUp() throws Exception {
     serverSideTestRepo =
@@ -134,38 +122,6 @@
     assertProblems(notes, null, problem("Missing change owner: " + owner.getId()));
   }
 
-  @Test
-  public void missingRepo() throws Exception {
-    // NoteDb can't have a change without a repo.
-    assumeNoteDbDisabled();
-
-    ChangeNotes notes = insertChange();
-    Project.NameKey name = notes.getProjectName();
-    ((InMemoryRepositoryManager) repoManager).deleteRepository(name);
-    assertThat(checker.check(notes, null).problems())
-        .containsExactly(problem("Destination repository not found: " + name));
-  }
-
-  @Test
-  public void invalidRevision() throws Exception {
-    // NoteDb always parses the revision when inserting a patch set, so we can't
-    // create an invalid patch set.
-    assumeNoteDbDisabled();
-
-    ChangeNotes notes = insertChange();
-    PatchSet ps =
-        newPatchSet(
-            notes.getChange().currentPatchSetId(),
-            "fooooooooooooooooooooooooooooooooooooooo",
-            adminId);
-    db.patchSets().update(singleton(ps));
-
-    assertProblems(
-        notes,
-        null,
-        problem("Invalid revision on patch set 1: fooooooooooooooooooooooooooooooooooooooo"));
-  }
-
   // No test for ref existing but object missing; InMemoryRepository won't let
   // us do such a thing.
 
@@ -201,7 +157,7 @@
   public void patchSetRefMissing() throws Exception {
     ChangeNotes notes = insertChange();
     serverSideTestRepo.update(
-        "refs/other/foo", ObjectId.fromString(psUtil.current(db, notes).getRevision().get()));
+        "refs/other/foo", ObjectId.fromString(psUtil.current(notes).getRevision().get()));
     String refName = notes.getChange().currentPatchSetId().toRefName();
     deleteRef(refName);
 
@@ -211,7 +167,7 @@
   @Test
   public void patchSetRefMissingWithFix() throws Exception {
     ChangeNotes notes = insertChange();
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     serverSideTestRepo.update("refs/other/foo", ObjectId.fromString(rev));
     String refName = notes.getChange().currentPatchSetId().toRefName();
     deleteRef(refName);
@@ -225,7 +181,7 @@
   @Test
   public void patchSetObjectAndRefMissingWithDeletingPatchSet() throws Exception {
     ChangeNotes notes = insertChange();
-    PatchSet ps1 = psUtil.current(db, notes);
+    PatchSet ps1 = psUtil.current(notes);
 
     String rev2 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
     PatchSet ps2 = insertMissingPatchSet(notes, rev2);
@@ -241,20 +197,20 @@
 
     notes = reload(notes);
     assertThat(notes.getChange().currentPatchSetId().get()).isEqualTo(1);
-    assertThat(psUtil.get(db, notes, ps1.getId())).isNotNull();
-    assertThat(psUtil.get(db, notes, ps2.getId())).isNull();
+    assertThat(psUtil.get(notes, ps1.getId())).isNotNull();
+    assertThat(psUtil.get(notes, ps2.getId())).isNull();
   }
 
   @Test
   public void patchSetMultipleObjectsMissingWithDeletingPatchSets() throws Exception {
     ChangeNotes notes = insertChange();
-    PatchSet ps1 = psUtil.current(db, notes);
+    PatchSet ps1 = psUtil.current(notes);
 
     String rev2 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
     PatchSet ps2 = insertMissingPatchSet(notes, rev2);
 
     notes = incrementPatchSet(reload(notes));
-    PatchSet ps3 = psUtil.current(db, notes);
+    PatchSet ps3 = psUtil.current(notes);
 
     String rev4 = "c0ffeeeec0ffeeeec0ffeeeec0ffeeeec0ffeeee";
     PatchSet ps4 = insertMissingPatchSet(notes, rev4);
@@ -272,27 +228,20 @@
 
     notes = reload(notes);
     assertThat(notes.getChange().currentPatchSetId().get()).isEqualTo(3);
-    assertThat(psUtil.get(db, notes, ps1.getId())).isNotNull();
-    assertThat(psUtil.get(db, notes, ps2.getId())).isNull();
-    assertThat(psUtil.get(db, notes, ps3.getId())).isNotNull();
-    assertThat(psUtil.get(db, notes, ps4.getId())).isNull();
+    assertThat(psUtil.get(notes, ps1.getId())).isNotNull();
+    assertThat(psUtil.get(notes, ps2.getId())).isNull();
+    assertThat(psUtil.get(notes, ps3.getId())).isNotNull();
+    assertThat(psUtil.get(notes, ps4.getId())).isNull();
   }
 
   @Test
   public void onlyPatchSetObjectMissingWithFix() throws Exception {
     Change c = TestChanges.newChange(project, admin.getId(), sequences.nextChangeId());
 
-    // Set review started, mimicking Schema_153, so tests pass with NoteDbMode.CHECK.
-    c.setReviewStarted(true);
-
     PatchSet.Id psId = c.currentPatchSetId();
     String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
     PatchSet ps = newPatchSet(psId, rev, adminId);
 
-    if (notesMigration.changePrimaryStorage() == PrimaryStorage.REVIEW_DB) {
-      db.changes().insert(singleton(c));
-      db.patchSets().insert(singleton(ps));
-    }
     addNoteDbCommit(
         c.getId(),
         "Create change\n"
@@ -327,23 +276,13 @@
 
     notes = reload(notes);
     assertThat(notes.getChange().currentPatchSetId().get()).isEqualTo(1);
-    assertThat(psUtil.current(db, notes)).isNotNull();
-  }
-
-  @Test
-  public void currentPatchSetMissing() throws Exception {
-    // NoteDb can't create a change without a patch set.
-    assumeNoteDbDisabled();
-
-    ChangeNotes notes = insertChange();
-    db.patchSets().deleteKeys(singleton(notes.getChange().currentPatchSetId()));
-    assertProblems(notes, null, problem("Current patch set 1 not found"));
+    assertThat(psUtil.current(notes)).isNotNull();
   }
 
   @Test
   public void duplicatePatchSetRevisions() throws Exception {
     ChangeNotes notes = insertChange();
-    PatchSet ps1 = psUtil.current(db, notes);
+    PatchSet ps1 = psUtil.current(notes);
     String rev = ps1.getRevision().get();
 
     notes =
@@ -386,7 +325,7 @@
     }
     notes = reload(notes);
 
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     ObjectId tip = getDestRef(notes);
     assertProblems(
         notes,
@@ -403,7 +342,7 @@
   @Test
   public void newChangeIsMerged() throws Exception {
     ChangeNotes notes = insertChange();
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     serverSideTestRepo
         .branch(notes.getChange().getDest().get())
         .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
@@ -423,7 +362,7 @@
   @Test
   public void newChangeIsMergedWithFix() throws Exception {
     ChangeNotes notes = insertChange();
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     serverSideTestRepo
         .branch(notes.getChange().getDest().get())
         .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
@@ -449,7 +388,7 @@
   @Test
   public void extensionApiReturnsUpdatedValueAfterFix() throws Exception {
     ChangeNotes notes = insertChange();
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     serverSideTestRepo
         .branch(notes.getChange().getDest().get())
         .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
@@ -464,7 +403,7 @@
   @Test
   public void expectedMergedCommitIsLatestPatchSet() throws Exception {
     ChangeNotes notes = insertChange();
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     serverSideTestRepo
         .branch(notes.getChange().getDest().get())
         .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
@@ -492,7 +431,7 @@
   @Test
   public void expectedMergedCommitNotMergedIntoDestination() throws Exception {
     ChangeNotes notes = insertChange();
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
     serverSideTestRepo.branch(notes.getChange().getDest().get()).update(commit);
 
@@ -515,7 +454,7 @@
   public void createNewPatchSetForExpectedMergeCommitWithNoChangeId() throws Exception {
     ChangeNotes notes = insertChange();
     String dest = notes.getChange().getDest().get();
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
 
     RevCommit mergedAs =
@@ -547,7 +486,7 @@
     notes = reload(notes);
     PatchSet.Id psId2 = new PatchSet.Id(notes.getChangeId(), 2);
     assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId2);
-    assertThat(psUtil.get(db, notes, psId2).getRevision().get()).isEqualTo(mergedAs.name());
+    assertThat(psUtil.get(notes, psId2).getRevision().get()).isEqualTo(mergedAs.name());
 
     assertNoProblems(notes, null);
   }
@@ -556,7 +495,7 @@
   public void createNewPatchSetForExpectedMergeCommitWithChangeId() throws Exception {
     ChangeNotes notes = insertChange();
     String dest = notes.getChange().getDest().get();
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
 
     RevCommit mergedAs =
@@ -595,7 +534,7 @@
     notes = reload(notes);
     PatchSet.Id psId2 = new PatchSet.Id(notes.getChangeId(), 2);
     assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId2);
-    assertThat(psUtil.get(db, notes, psId2).getRevision().get()).isEqualTo(mergedAs.name());
+    assertThat(psUtil.get(notes, psId2).getRevision().get()).isEqualTo(mergedAs.name());
 
     assertNoProblems(notes, null);
   }
@@ -603,10 +542,10 @@
   @Test
   public void expectedMergedCommitIsOldPatchSetOfSameChange() throws Exception {
     ChangeNotes notes = insertChange();
-    PatchSet ps1 = psUtil.current(db, notes);
+    PatchSet ps1 = psUtil.current(notes);
     String rev1 = ps1.getRevision().get();
     notes = incrementPatchSet(notes);
-    PatchSet ps2 = psUtil.current(db, notes);
+    PatchSet ps2 = psUtil.current(notes);
     serverSideTestRepo
         .branch(notes.getChange().getDest().get())
         .update(serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev1)));
@@ -636,14 +575,14 @@
     PatchSet.Id psId3 = new PatchSet.Id(notes.getChangeId(), 3);
     assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId3);
     assertThat(notes.getChange().getStatus()).isEqualTo(Change.Status.MERGED);
-    assertThat(psUtil.byChangeAsMap(db, notes).keySet()).containsExactly(ps2.getId(), psId3);
-    assertThat(psUtil.get(db, notes, psId3).getRevision().get()).isEqualTo(rev1);
+    assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps2.getId(), psId3);
+    assertThat(psUtil.get(notes, psId3).getRevision().get()).isEqualTo(rev1);
   }
 
   @Test
   public void expectedMergedCommitIsDanglingPatchSetOlderThanCurrent() throws Exception {
     ChangeNotes notes = insertChange();
-    PatchSet ps1 = psUtil.current(db, notes);
+    PatchSet ps1 = psUtil.current(notes);
 
     // Create dangling ref so next ID in the database becomes 3.
     PatchSet.Id psId2 = new PatchSet.Id(notes.getChangeId(), 2);
@@ -652,7 +591,7 @@
     serverSideTestRepo.branch(psId2.toRefName()).update(commit2);
 
     notes = incrementPatchSet(notes);
-    PatchSet ps3 = psUtil.current(db, notes);
+    PatchSet ps3 = psUtil.current(notes);
     assertThat(ps3.getId().get()).isEqualTo(3);
 
     serverSideTestRepo
@@ -684,15 +623,15 @@
     PatchSet.Id psId4 = new PatchSet.Id(notes.getChangeId(), 4);
     assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId4);
     assertThat(notes.getChange().getStatus()).isEqualTo(Change.Status.MERGED);
-    assertThat(psUtil.byChangeAsMap(db, notes).keySet())
+    assertThat(psUtil.byChangeAsMap(notes).keySet())
         .containsExactly(ps1.getId(), ps3.getId(), psId4);
-    assertThat(psUtil.get(db, notes, psId4).getRevision().get()).isEqualTo(rev2);
+    assertThat(psUtil.get(notes, psId4).getRevision().get()).isEqualTo(rev2);
   }
 
   @Test
   public void expectedMergedCommitIsDanglingPatchSetNewerThanCurrent() throws Exception {
     ChangeNotes notes = insertChange();
-    PatchSet ps1 = psUtil.current(db, notes);
+    PatchSet ps1 = psUtil.current(notes);
 
     // Create dangling ref with no patch set.
     PatchSet.Id psId2 = new PatchSet.Id(notes.getChangeId(), 2);
@@ -721,8 +660,8 @@
     notes = reload(notes);
     assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId2);
     assertThat(notes.getChange().getStatus()).isEqualTo(Change.Status.MERGED);
-    assertThat(psUtil.byChangeAsMap(db, notes).keySet()).containsExactly(ps1.getId(), psId2);
-    assertThat(psUtil.get(db, notes, psId2).getRevision().get()).isEqualTo(rev2);
+    assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps1.getId(), psId2);
+    assertThat(psUtil.get(notes, psId2).getRevision().get()).isEqualTo(rev2);
   }
 
   @Test
@@ -730,7 +669,7 @@
     ChangeNotes notes = insertChange();
     String dest = notes.getChange().getDest().get();
     RevCommit parent = serverSideTestRepo.branch(dest).commit().message("parent").create();
-    String rev = psUtil.current(db, notes).getRevision().get();
+    String rev = psUtil.current(notes).getRevision().get();
     RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
     serverSideTestRepo.branch(dest).update(commit);
 
@@ -764,19 +703,19 @@
   @Test
   public void expectedMergedCommitMatchesMultiplePatchSets() throws Exception {
     ChangeNotes notes1 = insertChange();
-    PatchSet.Id psId1 = psUtil.current(db, notes1).getId();
+    PatchSet.Id psId1 = psUtil.current(notes1).getId();
     String dest = notes1.getChange().getDest().get();
-    String rev = psUtil.current(db, notes1).getRevision().get();
+    String rev = psUtil.current(notes1).getRevision().get();
     RevCommit commit = serverSideTestRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
     serverSideTestRepo.branch(dest).update(commit);
 
     ChangeNotes notes2 = insertChange();
     notes2 = incrementPatchSet(notes2, commit);
-    PatchSet.Id psId2 = psUtil.current(db, notes2).getId();
+    PatchSet.Id psId2 = psUtil.current(notes2).getId();
 
     ChangeNotes notes3 = insertChange();
     notes3 = incrementPatchSet(notes3, commit);
-    PatchSet.Id psId3 = psUtil.current(db, notes3).getId();
+    PatchSet.Id psId3 = psUtil.current(notes3).getId();
 
     FixInput fix = new FixInput();
     fix.expectMergedAs = commit.name();
@@ -921,7 +860,7 @@
 
   private ChangeNotes mergeChange(ChangeNotes notes) throws Exception {
     final ObjectId oldId = getDestRef(notes);
-    final ObjectId newId = ObjectId.fromString(psUtil.current(db, notes).getRevision().get());
+    final ObjectId newId = ObjectId.fromString(psUtil.current(notes).getRevision().get());
     final String dest = notes.getChange().getDest().get();
 
     try (BatchUpdate bu = newUpdate(adminId)) {
diff --git a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
index 8e8aeac..40afa8a 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -572,7 +572,7 @@
 
     ChangeData cd = getChange(last);
     assertThat(cd.patchSets()).hasSize(n);
-    assertThat(GetRelated.getAllGroups(cd.notes(), db, psUtil)).hasSize(n);
+    assertThat(GetRelated.getAllGroups(cd.notes(), psUtil)).hasSize(n);
 
     assertRelated(cd.change().currentPatchSetId());
   }
@@ -610,8 +610,8 @@
           new BatchUpdateOp() {
             @Override
             public boolean updateChange(ChangeContext ctx) throws OrmException {
-              PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
-              psUtil.setGroups(ctx.getDb(), ctx.getUpdate(psId), ps, ImmutableList.<String>of());
+              PatchSet ps = psUtil.get(ctx.getNotes(), psId);
+              psUtil.setGroups(ctx.getUpdate(psId), ps, ImmutableList.<String>of());
               ctx.dontBumpLastUpdatedOn();
               return true;
             }
@@ -622,7 +622,8 @@
 
   private void assertRelated(PatchSet.Id psId, RelatedChangeAndCommitInfo... expected)
       throws Exception {
-    List<RelatedChangeAndCommitInfo> actual = getRelated(psId);
+    List<RelatedChangeAndCommitInfo> actual =
+        gApi.changes().id(psId.getParentKey().get()).revision(psId.get()).related().changes;
     assertThat(actual).named("related to " + psId).hasSize(expected.length);
     for (int i = 0; i < actual.size(); i++) {
       String name = "index " + i + " related to " + psId;
diff --git a/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java b/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
index 304a1e4..ddc3905 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.ListChangesOption;
@@ -29,6 +30,7 @@
 import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.testing.ConfigSuite;
+import com.google.inject.Inject;
 import java.util.EnumSet;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
@@ -41,6 +43,8 @@
     return submitWholeTopicEnabledConfig();
   }
 
+  @Inject private ProjectOperations projectOperations;
+
   @Test
   public void doesNotIncludeCurrentFiles() throws Exception {
     RevCommit c1_1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
@@ -226,7 +230,8 @@
 
   @Test
   public void newBranchTwoChangesTogether() throws Exception {
-    Project.NameKey p1 = createProject("a-new-project", null, false);
+    Project.NameKey p1 = projectOperations.newProject().noEmptyCommit().create();
+
     TestRepository<?> repo1 = cloneProject(p1);
 
     RevCommit c1 =
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
index 209d0a2..35d9e88 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.acceptance.server.mail;
 
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.extensions.api.changes.NotifyHandling.ALL;
 import static com.google.gerrit.extensions.api.changes.NotifyHandling.NONE;
 import static com.google.gerrit.extensions.api.changes.NotifyHandling.OWNER;
@@ -255,31 +254,7 @@
    * AddReviewerSender tests.
    */
 
-  private void addReviewerToReviewableChangeInReviewDb(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
-    addReviewer(adder, sc.changeId, sc.owner, reviewer.email);
-    assertThat(sender)
-        .sent("newchange", sc)
-        .to(reviewer)
-        .cc(sc.reviewer, sc.ccer)
-        .cc(sc.reviewerByEmail, sc.ccerByEmail)
-        .noOneElse();
-  }
-
-  @Test
-  public void addReviewerToReviewableChangeInReviewDbSingly() throws Exception {
-    addReviewerToReviewableChangeInReviewDb(singly());
-  }
-
-  @Test
-  public void addReviewerToReviewableChangeInReviewDbBatch() throws Exception {
-    addReviewerToReviewableChangeInReviewDb(batch());
-  }
-
-  private void addReviewerToReviewableChangeInNoteDb(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  private void addReviewerToReviewableChange(Adder adder) throws Exception {
     StagedChange sc = stageReviewableChange();
     TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
     addReviewer(adder, sc.changeId, sc.owner, reviewer.email);
@@ -293,17 +268,16 @@
   }
 
   @Test
-  public void addReviewerToReviewableChangeInNoteDbSingly() throws Exception {
-    addReviewerToReviewableChangeInNoteDb(singly());
+  public void addReviewerToReviewableChangeSingly() throws Exception {
+    addReviewerToReviewableChange(singly());
   }
 
   @Test
-  public void addReviewerToReviewableChangeInNoteDbBatch() throws Exception {
-    addReviewerToReviewableChangeInNoteDb(batch());
+  public void addReviewerToReviewableChangeBatch() throws Exception {
+    addReviewerToReviewableChange(batch());
   }
 
-  private void addReviewerToReviewableChangeByOwnerCcingSelfInNoteDb(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  private void addReviewerToReviewableChangeByOwnerCcingSelf(Adder adder) throws Exception {
     StagedChange sc = stageReviewableChange();
     TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
     addReviewer(adder, sc.changeId, sc.owner, reviewer.email, CC_ON_OWN_COMMENTS, null);
@@ -317,17 +291,16 @@
   }
 
   @Test
-  public void addReviewerToReviewableChangeByOwnerCcingSelfInNoteDbSingly() throws Exception {
-    addReviewerToReviewableChangeByOwnerCcingSelfInNoteDb(singly());
+  public void addReviewerToReviewableChangeByOwnerCcingSelfSingly() throws Exception {
+    addReviewerToReviewableChangeByOwnerCcingSelf(singly());
   }
 
   @Test
-  public void addReviewerToReviewableChangeByOwnerCcingSelfInNoteDbBatch() throws Exception {
-    addReviewerToReviewableChangeByOwnerCcingSelfInNoteDb(batch());
+  public void addReviewerToReviewableChangeByOwnerCcingSelfBatch() throws Exception {
+    addReviewerToReviewableChangeByOwnerCcingSelf(batch());
   }
 
-  private void addReviewerToReviewableChangeByOtherInNoteDb(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  private void addReviewerToReviewableChangeByOther(Adder adder) throws Exception {
     TestAccount other = accountCreator.create("other", "other@example.com", "other");
     StagedChange sc = stageReviewableChange();
     TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
@@ -342,17 +315,16 @@
   }
 
   @Test
-  public void addReviewerToReviewableChangeByOtherInNoteDbSingly() throws Exception {
-    addReviewerToReviewableChangeByOtherInNoteDb(singly());
+  public void addReviewerToReviewableChangeByOtherSingly() throws Exception {
+    addReviewerToReviewableChangeByOther(singly());
   }
 
   @Test
-  public void addReviewerToReviewableChangeByOtherInNoteDbBatch() throws Exception {
-    addReviewerToReviewableChangeByOtherInNoteDb(batch());
+  public void addReviewerToReviewableChangeByOtherBatch() throws Exception {
+    addReviewerToReviewableChangeByOther(batch());
   }
 
-  private void addReviewerToReviewableChangeByOtherCcingSelfInNoteDb(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  private void addReviewerToReviewableChangeByOtherCcingSelf(Adder adder) throws Exception {
     TestAccount other = accountCreator.create("other", "other@example.com", "other");
     StagedChange sc = stageReviewableChange();
     TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
@@ -367,35 +339,16 @@
   }
 
   @Test
-  public void addReviewerToReviewableChangeByOtherCcingSelfInNoteDbSingly() throws Exception {
-    addReviewerToReviewableChangeByOtherCcingSelfInNoteDb(singly());
+  public void addReviewerToReviewableChangeByOtherCcingSelfSingly() throws Exception {
+    addReviewerToReviewableChangeByOtherCcingSelf(singly());
   }
 
   @Test
-  public void addReviewerToReviewableChangeByOtherCcingSelfInNoteDbBatch() throws Exception {
-    addReviewerToReviewableChangeByOtherCcingSelfInNoteDb(batch());
+  public void addReviewerToReviewableChangeByOtherCcingSelfBatch() throws Exception {
+    addReviewerToReviewableChangeByOtherCcingSelf(batch());
   }
 
-  private void addReviewerByEmailToReviewableChangeInReviewDb(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    String email = "addedbyemail@example.com";
-    StagedChange sc = stageReviewableChange();
-    addReviewer(adder, sc.changeId, sc.owner, email);
-    assertThat(sender).notSent();
-  }
-
-  @Test
-  public void addReviewerByEmailToReviewableChangeInReviewDbSingly() throws Exception {
-    addReviewerByEmailToReviewableChangeInReviewDb(singly());
-  }
-
-  @Test
-  public void addReviewerByEmailToReviewableChangeInReviewDbBatch() throws Exception {
-    addReviewerByEmailToReviewableChangeInReviewDb(batch());
-  }
-
-  private void addReviewerByEmailToReviewableChangeInNoteDb(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  private void addReviewerByEmailToReviewableChange(Adder adder) throws Exception {
     String email = "addedbyemail@example.com";
     StagedChange sc = stageReviewableChange();
     addReviewer(adder, sc.changeId, sc.owner, email);
@@ -409,13 +362,13 @@
   }
 
   @Test
-  public void addReviewerByEmailToReviewableChangeInNoteDbSingly() throws Exception {
-    addReviewerByEmailToReviewableChangeInNoteDb(singly());
+  public void addReviewerByEmailToReviewableChangeSingly() throws Exception {
+    addReviewerByEmailToReviewableChange(singly());
   }
 
   @Test
-  public void addReviewerByEmailToReviewableChangeInNoteDbBatch() throws Exception {
-    addReviewerByEmailToReviewableChangeInNoteDb(batch());
+  public void addReviewerByEmailToReviewableChangeBatch() throws Exception {
+    addReviewerByEmailToReviewableChange(batch());
   }
 
   private void addReviewerToWipChange(Adder adder) throws Exception {
@@ -452,8 +405,7 @@
     addReviewerToReviewableWipChange(batch());
   }
 
-  private void addReviewerToWipChangeInNoteDbNotifyAll(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  private void addReviewerToWipChangeNotifyAll(Adder adder) throws Exception {
     StagedChange sc = stageWipChange();
     TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
     addReviewer(adder, sc.changeId, sc.owner, reviewer.email, NotifyHandling.ALL);
@@ -467,41 +419,16 @@
   }
 
   @Test
-  public void addReviewerToWipChangeInNoteDbNotifyAllSingly() throws Exception {
-    addReviewerToWipChangeInNoteDbNotifyAll(singly());
+  public void addReviewerToWipChangeNotifyAllSingly() throws Exception {
+    addReviewerToWipChangeNotifyAll(singly());
   }
 
   @Test
-  public void addReviewerToWipChangeInNoteDbNotifyAllBatch() throws Exception {
-    addReviewerToWipChangeInNoteDbNotifyAll(batch());
+  public void addReviewerToWipChangeNotifyAllBatch() throws Exception {
+    addReviewerToWipChangeNotifyAll(batch());
   }
 
-  private void addReviewerToWipChangeInReviewDbNotifyAll(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageWipChange();
-    TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
-    addReviewer(adder, sc.changeId, sc.owner, reviewer.email, NotifyHandling.ALL);
-    assertThat(sender)
-        .sent("newchange", sc)
-        .to(reviewer)
-        .cc(sc.reviewer, sc.ccer)
-        .cc(sc.reviewerByEmail, sc.ccerByEmail)
-        .noOneElse();
-  }
-
-  @Test
-  public void addReviewerToWipChangeInReviewDbNotifyAllSingly() throws Exception {
-    addReviewerToWipChangeInReviewDbNotifyAll(singly());
-  }
-
-  @Test
-  public void addReviewerToWipChangeInReviewDbNotifyAllBatch() throws Exception {
-    addReviewerToWipChangeInReviewDbNotifyAll(batch());
-  }
-
-  private void addReviewerToReviewableChangeInNoteDbNotifyOwnerReviewers(Adder adder)
-      throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  private void addReviewerToReviewableChangeNotifyOwnerReviewers(Adder adder) throws Exception {
     StagedChange sc = stageReviewableChange();
     TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
     addReviewer(adder, sc.changeId, sc.owner, reviewer.email, OWNER_REVIEWERS);
@@ -515,18 +442,17 @@
   }
 
   @Test
-  public void addReviewerToReviewableChangeInNoteDbNotifyOwnerReviewersSingly() throws Exception {
-    addReviewerToReviewableChangeInNoteDbNotifyOwnerReviewers(singly());
+  public void addReviewerToReviewableChangeNotifyOwnerReviewersSingly() throws Exception {
+    addReviewerToReviewableChangeNotifyOwnerReviewers(singly());
   }
 
   @Test
-  public void addReviewerToReviewableChangeInNoteDbNotifyOwnerReviewersBatch() throws Exception {
-    addReviewerToReviewableChangeInNoteDbNotifyOwnerReviewers(batch());
+  public void addReviewerToReviewableChangeNotifyOwnerReviewersBatch() throws Exception {
+    addReviewerToReviewableChangeNotifyOwnerReviewers(batch());
   }
 
-  private void addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyOwner(Adder adder)
+  private void addReviewerToReviewableChangeByOwnerCcingSelfNotifyOwner(Adder adder)
       throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     StagedChange sc = stageReviewableChange();
     TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
     addReviewer(adder, sc.changeId, sc.owner, reviewer.email, CC_ON_OWN_COMMENTS, OWNER);
@@ -534,20 +460,17 @@
   }
 
   @Test
-  public void addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyOwnerSingly()
-      throws Exception {
-    addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyOwner(singly());
+  public void addReviewerToReviewableChangeByOwnerCcingSelfNotifyOwnerSingly() throws Exception {
+    addReviewerToReviewableChangeByOwnerCcingSelfNotifyOwner(singly());
   }
 
   @Test
-  public void addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyOwnerBatch()
-      throws Exception {
-    addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyOwner(batch());
+  public void addReviewerToReviewableChangeByOwnerCcingSelfNotifyOwnerBatch() throws Exception {
+    addReviewerToReviewableChangeByOwnerCcingSelfNotifyOwner(batch());
   }
 
-  private void addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyNone(Adder adder)
+  private void addReviewerToReviewableChangeByOwnerCcingSelfNotifyNone(Adder adder)
       throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     StagedChange sc = stageReviewableChange();
     TestAccount reviewer = accountCreator.create("added", "added@example.com", "added");
     addReviewer(adder, sc.changeId, sc.owner, reviewer.email, CC_ON_OWN_COMMENTS, NONE);
@@ -555,19 +478,16 @@
   }
 
   @Test
-  public void addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyNoneSingly()
-      throws Exception {
-    addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyNone(singly());
+  public void addReviewerToReviewableChangeByOwnerCcingSelfNotifyNoneSingly() throws Exception {
+    addReviewerToReviewableChangeByOwnerCcingSelfNotifyNone(singly());
   }
 
   @Test
-  public void addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyNoneBatch()
-      throws Exception {
-    addReviewerToReviewableChangeInNoteDbByOwnerCcingSelfNotifyNone(batch());
+  public void addReviewerToReviewableChangeByOwnerCcingSelfNotifyNoneBatch() throws Exception {
+    addReviewerToReviewableChangeByOwnerCcingSelfNotifyNone(batch());
   }
 
-  private void addNonUserReviewerByEmailInNoteDb(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  private void addNonUserReviewerByEmail(Adder adder) throws Exception {
     StagedChange sc = stageReviewableChange();
     addReviewer(adder, sc.changeId, sc.owner, "nonexistent@example.com");
     assertThat(sender)
@@ -579,17 +499,16 @@
   }
 
   @Test
-  public void addNonUserReviewerByEmailInNoteDbSingly() throws Exception {
-    addNonUserReviewerByEmailInNoteDb(singly(ReviewerState.REVIEWER));
+  public void addNonUserReviewerByEmailSingly() throws Exception {
+    addNonUserReviewerByEmail(singly(ReviewerState.REVIEWER));
   }
 
   @Test
-  public void addNonUserReviewerByEmailInNoteDbBatch() throws Exception {
-    addNonUserReviewerByEmailInNoteDb(batch(ReviewerState.REVIEWER));
+  public void addNonUserReviewerByEmailBatch() throws Exception {
+    addNonUserReviewerByEmail(batch(ReviewerState.REVIEWER));
   }
 
-  private void addNonUserCcByEmailInNoteDb(Adder adder) throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  private void addNonUserCcByEmail(Adder adder) throws Exception {
     StagedChange sc = stageReviewableChange();
     addReviewer(adder, sc.changeId, sc.owner, "nonexistent@example.com");
     assertThat(sender)
@@ -601,13 +520,13 @@
   }
 
   @Test
-  public void addNonUserCcByEmailInNoteDbSingly() throws Exception {
-    addNonUserCcByEmailInNoteDb(singly(ReviewerState.CC));
+  public void addNonUserCcByEmailSingly() throws Exception {
+    addNonUserCcByEmail(singly(ReviewerState.CC));
   }
 
   @Test
-  public void addNonUserCcByEmailInNoteDbBatch() throws Exception {
-    addNonUserCcByEmailInNoteDb(batch(ReviewerState.CC));
+  public void addNonUserCcByEmailBatch() throws Exception {
+    addNonUserCcByEmail(batch(ReviewerState.CC));
   }
 
   private interface Adder {
@@ -923,8 +842,7 @@
   }
 
   @Test
-  public void addReviewerOnWipChangeAndStartReviewInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void addReviewerOnWipChangeAndStartReview() throws Exception {
     StagedChange sc = stageWipChange();
     ReviewInput in = ReviewInput.noScore().reviewer(other.email).setWorkInProgress(false);
     gApi.changes().id(sc.changeId).revision("current").review(in);
@@ -946,28 +864,6 @@
   }
 
   @Test
-  public void addReviewerOnWipChangeAndStartReviewInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageWipChange();
-    ReviewInput in = ReviewInput.noScore().reviewer(other.email).setWorkInProgress(false);
-    gApi.changes().id(sc.changeId).revision("current").review(in);
-    assertThat(sender)
-        .sent("comment", sc)
-        .cc(sc.reviewer, sc.ccer, other)
-        .cc(sc.reviewerByEmail, sc.ccerByEmail)
-        .bcc(sc.starrer)
-        .bcc(ALL_COMMENTS)
-        .noOneElse();
-    assertThat(sender)
-        .sent("newchange", sc)
-        .to(other)
-        .cc(sc.reviewer, sc.ccer)
-        .cc(sc.reviewerByEmail, sc.ccerByEmail)
-        .noOneElse();
-    assertThat(sender).notSent();
-  }
-
-  @Test
   public void startReviewMessageNotRepeated() throws Exception {
     // TODO(logan): Remove this test check once PolyGerrit workaround is rolled back.
     StagedChange sc = stageWipChange();
@@ -1094,18 +990,12 @@
             users -> ImmutableList.of("r=" + users.reviewer.username, "cc=" + users.ccer.username));
     FakeEmailSenderSubject subject =
         assertThat(sender).sent("newchange", spc).to(spc.reviewer, spc.watchingProjectOwner);
-    if (notesMigration.readChanges()) {
-      subject.cc(spc.ccer);
-    } else {
-      // CCs are considered reviewers in the storage layer.
-      subject.to(spc.ccer);
-    }
+    subject.cc(spc.ccer);
     subject.bcc(NEW_CHANGES, NEW_PATCHSETS).noOneElse();
   }
 
   @Test
-  public void createReviewableChangeWithReviewersAndCcsByEmailInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void createReviewableChangeWithReviewersAndCcsByEmail() throws Exception {
     StagedPreChange spc =
         stagePreChange(
             "refs/for/master",
@@ -1290,8 +1180,7 @@
   }
 
   @Test
-  public void deleteReviewerByEmailFromWipChangeInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void deleteReviewerByEmailFromWipChange() throws Exception {
     StagedChange sc = stageWipChangeWithExtraReviewer();
     gApi.changes().id(sc.changeId).reviewer(sc.reviewerByEmail).remove();
     assertThat(sender).notSent();
@@ -1650,8 +1539,7 @@
    */
 
   @Test
-  public void newPatchSetByOwnerOnReviewableChangeInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void newPatchSetByOwnerOnReviewableChange() throws Exception {
     StagedChange sc = stageReviewableChange();
     pushTo(sc, "refs/for/master", sc.owner);
     assertThat(sender)
@@ -1665,21 +1553,7 @@
   }
 
   @Test
-  public void newPatchSetByOwnerOnReviewableChangeInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    pushTo(sc, "refs/for/master", sc.owner);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.reviewer, sc.ccer)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-  }
-
-  @Test
-  public void newPatchSetByOtherOnReviewableChangeInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void newPatchSetByOtherOnReviewableChange() throws Exception {
     StagedChange sc = stageReviewableChange();
     pushTo(sc, "refs/for/master", other);
     assertThat(sender)
@@ -1694,22 +1568,7 @@
   }
 
   @Test
-  public void newPatchSetByOtherOnReviewableChangeInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    pushTo(sc, "refs/for/master", other);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .notTo(sc.owner) // TODO(logan): This email shouldn't come from the owner.
-        .to(sc.reviewer, sc.ccer, other)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-  }
-
-  @Test
-  public void newPatchSetByOtherOnReviewableChangeOwnerSelfCcInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void newPatchSetByOtherOnReviewableChangeOwnerSelfCc() throws Exception {
     StagedChange sc = stageReviewableChange();
     pushTo(sc, "refs/for/master", other, EmailStrategy.CC_ON_OWN_COMMENTS);
     assertThat(sender)
@@ -1724,22 +1583,7 @@
   }
 
   @Test
-  public void newPatchSetByOtherOnReviewableChangeOwnerSelfCcInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    pushTo(sc, "refs/for/master", other, EmailStrategy.CC_ON_OWN_COMMENTS);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .notTo(sc.owner) // TODO(logan): This shouldn't be sent *from* the owner.
-        .to(sc.reviewer, sc.ccer, other)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-  }
-
-  @Test
-  public void newPatchSetByOtherOnReviewableChangeNotifyOwnerReviewersInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void newPatchSetByOtherOnReviewableChangeNotifyOwnerReviewers() throws Exception {
     StagedChange sc = stageReviewableChange();
     pushTo(sc, "refs/for/master%notify=OWNER_REVIEWERS", other);
     assertThat(sender)
@@ -1753,23 +1597,8 @@
   }
 
   @Test
-  public void newPatchSetByOtherOnReviewableChangeNotifyOwnerReviewersInReviewDb()
+  public void newPatchSetByOtherOnReviewableChangeOwnerSelfCcNotifyOwnerReviewers()
       throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    pushTo(sc, "refs/for/master%notify=OWNER_REVIEWERS", other);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .notTo(sc.owner) // TODO(logan): This shouldn't be sent *from* the owner.
-        .to(sc.reviewer, sc.ccer)
-        .to(other)
-        .noOneElse();
-  }
-
-  @Test
-  public void newPatchSetByOtherOnReviewableChangeOwnerSelfCcNotifyOwnerReviewersInNoteDb()
-      throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     StagedChange sc = stageReviewableChange();
     pushTo(sc, "refs/for/master%notify=OWNER_REVIEWERS", other, EmailStrategy.CC_ON_OWN_COMMENTS);
     assertThat(sender)
@@ -1783,20 +1612,6 @@
   }
 
   @Test
-  public void newPatchSetByOtherOnReviewableChangeOwnerSelfCcNotifyOwnerReviewersInReviewDb()
-      throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    pushTo(sc, "refs/for/master%notify=OWNER_REVIEWERS", other, EmailStrategy.CC_ON_OWN_COMMENTS);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.reviewer, sc.ccer)
-        .to(other)
-        .notTo(sc.owner) // TODO(logan): This shouldn't be sent *from* the owner.
-        .noOneElse();
-  }
-
-  @Test
   public void newPatchSetByOtherOnReviewableChangeNotifyOwner() throws Exception {
     StagedChange sc = stageReviewableChange();
     pushTo(sc, "refs/for/master%notify=OWNER", other);
@@ -1843,8 +1658,7 @@
   }
 
   @Test
-  public void newPatchSetOnWipChangeNotifyAllInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void newPatchSetOnWipChangeNotifyAll() throws Exception {
     StagedChange sc = stageWipChange();
     pushTo(sc, "refs/for/master%wip,notify=ALL", sc.owner);
     assertThat(sender)
@@ -1858,21 +1672,7 @@
   }
 
   @Test
-  public void newPatchSetOnWipChangeNotifyAllInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageWipChange();
-    pushTo(sc, "refs/for/master%wip,notify=ALL", sc.owner);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.reviewer, sc.ccer)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-  }
-
-  @Test
-  public void newPatchSetOnWipChangeToReadyInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void newPatchSetOnWipChangeToReady() throws Exception {
     StagedChange sc = stageWipChange();
     pushTo(sc, "refs/for/master%ready", sc.owner);
     assertThat(sender)
@@ -1886,19 +1686,6 @@
   }
 
   @Test
-  public void newPatchSetOnWipChangeToReadyInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageWipChange();
-    pushTo(sc, "refs/for/master%ready", sc.owner);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.reviewer, sc.ccer)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-  }
-
-  @Test
   public void newPatchSetOnReviewableWipChange() throws Exception {
     StagedChange sc = stageReviewableWipChange();
     pushTo(sc, "refs/for/master%wip", sc.owner);
@@ -1906,8 +1693,7 @@
   }
 
   @Test
-  public void newPatchSetOnReviewableChangeAddingReviewerInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void newPatchSetOnReviewableChangeAddingReviewer() throws Exception {
     StagedChange sc = stageReviewableChange();
     TestAccount newReviewer = sc.testAccount("newReviewer");
     pushTo(sc, "refs/for/master%r=" + newReviewer.username, sc.owner);
@@ -1923,22 +1709,6 @@
   }
 
   @Test
-  public void newPatchSetOnReviewableChangeAddingReviewerInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    TestAccount newReviewer = sc.testAccount("newReviewer");
-    pushTo(sc, "refs/for/master%r=" + newReviewer.username, sc.owner);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.reviewer, sc.ccer, newReviewer)
-        .cc(sc.reviewerByEmail, sc.ccerByEmail)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-    assertThat(sender).notSent();
-  }
-
-  @Test
   public void newPatchSetOnWipChangeAddingReviewer() throws Exception {
     StagedChange sc = stageWipChange();
     TestAccount newReviewer = sc.testAccount("newReviewer");
@@ -1947,8 +1717,7 @@
   }
 
   @Test
-  public void newPatchSetOnWipChangeAddingReviewerNotifyAllInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void newPatchSetOnWipChangeAddingReviewerNotifyAll() throws Exception {
     StagedChange sc = stageWipChange();
     TestAccount newReviewer = sc.testAccount("newReviewer");
     pushTo(sc, "refs/for/master%notify=ALL,r=" + newReviewer.username, sc.owner);
@@ -1964,24 +1733,7 @@
   }
 
   @Test
-  public void newPatchSetOnWipChangeAddingReviewerNotifyAllInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageWipChange();
-    TestAccount newReviewer = sc.testAccount("newReviewer");
-    pushTo(sc, "refs/for/master%notify=ALL,r=" + newReviewer.username, sc.owner);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.reviewer, sc.ccer, newReviewer)
-        .cc(sc.reviewerByEmail, sc.ccerByEmail)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-    assertThat(sender).notSent();
-  }
-
-  @Test
-  public void newPatchSetOnWipChangeSettingReadyInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void newPatchSetOnWipChangeSettingReady() throws Exception {
     StagedChange sc = stageWipChange();
     pushTo(sc, "refs/for/master%ready", sc.owner);
     assertThat(sender)
@@ -1995,21 +1747,6 @@
     assertThat(sender).notSent();
   }
 
-  @Test
-  public void newPatchSetOnWipChangeSettingReadyInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageWipChange();
-    pushTo(sc, "refs/for/master%ready", sc.owner);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.reviewer, sc.ccer)
-        .cc(sc.reviewerByEmail, sc.ccerByEmail)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-    assertThat(sender).notSent();
-  }
-
   private void pushTo(StagedChange sc, String ref, TestAccount by) throws Exception {
     pushTo(sc, ref, by, ENABLED);
   }
@@ -2021,8 +1758,7 @@
   }
 
   @Test
-  public void editCommitMessageEditByOwnerOnReviewableChangeInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void editCommitMessageEditByOwnerOnReviewableChange() throws Exception {
     StagedChange sc = stageReviewableChange();
     editCommitMessage(sc, sc.owner);
     assertThat(sender)
@@ -2036,21 +1772,7 @@
   }
 
   @Test
-  public void editCommitMessageEditByOwnerOnReviewableChangeInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    editCommitMessage(sc, sc.owner);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.reviewer, sc.ccer)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-  }
-
-  @Test
-  public void editCommitMessageEditByOtherOnReviewableChangeInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void editCommitMessageEditByOtherOnReviewableChange() throws Exception {
     StagedChange sc = stageReviewableChange();
     editCommitMessage(sc, other);
     assertThat(sender)
@@ -2064,21 +1786,7 @@
   }
 
   @Test
-  public void editCommitMessageEditByOtherOnReviewableChangeInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    editCommitMessage(sc, other);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.owner, sc.reviewer, sc.ccer)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-  }
-
-  @Test
-  public void editCommitMessageByOtherOnReviewableChangeOwnerSelfCcInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void editCommitMessageByOtherOnReviewableChangeOwnerSelfCc() throws Exception {
     StagedChange sc = stageReviewableChange();
     editCommitMessage(sc, other, CC_ON_OWN_COMMENTS);
     assertThat(sender)
@@ -2092,22 +1800,7 @@
   }
 
   @Test
-  public void editCommitMessageByOtherOnReviewableChangeOwnerSelfCcInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    editCommitMessage(sc, other, CC_ON_OWN_COMMENTS);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.owner, sc.reviewer, sc.ccer, other)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-  }
-
-  @Test
-  public void editCommitMessageByOtherOnReviewableChangeNotifyOwnerReviewersInNoteDb()
-      throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void editCommitMessageByOtherOnReviewableChangeNotifyOwnerReviewers() throws Exception {
     StagedChange sc = stageReviewableChange();
     editCommitMessage(sc, other, OWNER_REVIEWERS);
     assertThat(sender)
@@ -2119,18 +1812,8 @@
   }
 
   @Test
-  public void editCommitMessageByOtherOnReviewableChangeNotifyOwnerReviewersInReviewDb()
+  public void editCommitMessageByOtherOnReviewableChangeOwnerSelfCcNotifyOwnerReviewers()
       throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    editCommitMessage(sc, other, OWNER_REVIEWERS);
-    assertThat(sender).sent("newpatchset", sc).to(sc.owner, sc.reviewer, sc.ccer).noOneElse();
-  }
-
-  @Test
-  public void editCommitMessageByOtherOnReviewableChangeOwnerSelfCcNotifyOwnerReviewersInNoteDb()
-      throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
     StagedChange sc = stageReviewableChange();
     editCommitMessage(sc, other, OWNER_REVIEWERS, CC_ON_OWN_COMMENTS);
     assertThat(sender)
@@ -2142,19 +1825,6 @@
   }
 
   @Test
-  public void editCommitMessageByOtherOnReviewableChangeOwnerSelfCcNotifyOwnerReviewersInReviewDb()
-      throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    editCommitMessage(sc, other, OWNER_REVIEWERS, CC_ON_OWN_COMMENTS);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.owner, sc.reviewer, sc.ccer)
-        .cc(other)
-        .noOneElse();
-  }
-
-  @Test
   public void editCommitMessageByOtherOnReviewableChangeNotifyOwner() throws Exception {
     StagedChange sc = stageReviewableChange();
     editCommitMessage(sc, other, OWNER);
@@ -2204,8 +1874,7 @@
   }
 
   @Test
-  public void editCommitMessageOnWipChangeNotifyAllInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void editCommitMessageOnWipChangeNotifyAll() throws Exception {
     StagedChange sc = stageWipChange();
     editCommitMessage(sc, sc.owner, ALL);
     assertThat(sender)
@@ -2218,19 +1887,6 @@
         .noOneElse();
   }
 
-  @Test
-  public void editCommitMessageOnWipChangeNotifyAllInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageWipChange();
-    editCommitMessage(sc, sc.owner, ALL);
-    assertThat(sender)
-        .sent("newpatchset", sc)
-        .to(sc.reviewer, sc.ccer)
-        .bcc(sc.starrer)
-        .bcc(NEW_PATCHSETS)
-        .noOneElse();
-  }
-
   private void editCommitMessage(StagedChange sc, TestAccount by) throws Exception {
     editCommitMessage(sc, by, null, ENABLED);
   }
@@ -2357,31 +2013,7 @@
    */
 
   @Test
-  public void revertChangeByOwnerInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageChange();
-    revert(sc, sc.owner);
-
-    // email for the newly created revert change
-    assertThat(sender)
-        .sent("newchange", sc)
-        .to(sc.reviewer, sc.ccer, sc.watchingProjectOwner, admin)
-        .bcc(NEW_CHANGES, NEW_PATCHSETS)
-        .noOneElse();
-
-    // email for the change that is reverted
-    assertThat(sender)
-        .sent("revert", sc)
-        .cc(sc.reviewer, sc.ccer, admin)
-        .cc(sc.reviewerByEmail)
-        .bcc(sc.starrer)
-        .bcc(ALL_COMMENTS)
-        .noOneElse();
-  }
-
-  @Test
-  public void revertChangeByOwnerInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void revertChangeByOwner() throws Exception {
     StagedChange sc = stageChange();
     revert(sc, sc.owner);
 
@@ -2404,33 +2036,7 @@
   }
 
   @Test
-  public void revertChangeByOwnerCcingSelfInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageChange();
-    revert(sc, sc.owner, CC_ON_OWN_COMMENTS);
-
-    // email for the newly created revert change
-    assertThat(sender)
-        .sent("newchange", sc)
-        .to(sc.reviewer, sc.ccer, sc.watchingProjectOwner, admin)
-        .cc(sc.owner)
-        .bcc(NEW_CHANGES, NEW_PATCHSETS)
-        .noOneElse();
-
-    // email for the change that is reverted
-    assertThat(sender)
-        .sent("revert", sc)
-        .to(sc.owner)
-        .cc(sc.reviewer, sc.ccer, admin)
-        .cc(sc.reviewerByEmail)
-        .bcc(sc.starrer)
-        .bcc(ALL_COMMENTS)
-        .noOneElse();
-  }
-
-  @Test
-  public void revertChangeByOwnerCcingSelfInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void revertChangeByOwnerCcingSelf() throws Exception {
     StagedChange sc = stageChange();
     revert(sc, sc.owner, CC_ON_OWN_COMMENTS);
 
@@ -2454,32 +2060,7 @@
   }
 
   @Test
-  public void revertChangeByOtherInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageChange();
-    revert(sc, other);
-
-    // email for the newly created revert change
-    assertThat(sender)
-        .sent("newchange", sc)
-        .to(sc.owner, sc.reviewer, sc.ccer, sc.watchingProjectOwner, admin)
-        .bcc(NEW_CHANGES, NEW_PATCHSETS)
-        .noOneElse();
-
-    // email for the change that is reverted
-    assertThat(sender)
-        .sent("revert", sc)
-        .to(sc.owner)
-        .cc(sc.reviewer, sc.ccer, admin)
-        .cc(sc.reviewerByEmail)
-        .bcc(sc.starrer)
-        .bcc(ALL_COMMENTS)
-        .noOneElse();
-  }
-
-  @Test
-  public void revertChangeByOtherInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void revertChangeByOther() throws Exception {
     StagedChange sc = stageChange();
     revert(sc, other);
 
@@ -2503,33 +2084,7 @@
   }
 
   @Test
-  public void revertChangeByOtherCcingSelfInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageChange();
-    revert(sc, other, CC_ON_OWN_COMMENTS);
-
-    // email for the newly created revert change
-    assertThat(sender)
-        .sent("newchange", sc)
-        .to(sc.owner, sc.reviewer, sc.ccer, sc.watchingProjectOwner, admin)
-        .cc(other)
-        .bcc(NEW_CHANGES, NEW_PATCHSETS)
-        .noOneElse();
-
-    // email for the change that is reverted
-    assertThat(sender)
-        .sent("revert", sc)
-        .to(sc.owner)
-        .cc(other, sc.reviewer, sc.ccer, admin)
-        .cc(sc.reviewerByEmail)
-        .bcc(sc.starrer)
-        .bcc(ALL_COMMENTS)
-        .noOneElse();
-  }
-
-  @Test
-  public void revertChangeByOtherCcingSelfInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void revertChangeByOtherCcingSelf() throws Exception {
     StagedChange sc = stageChange();
     revert(sc, other, CC_ON_OWN_COMMENTS);
 
@@ -2623,8 +2178,7 @@
   }
 
   @Test
-  public void setAssigneeToSelfOnReviewableChangeInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void setAssigneeToSelfOnReviewableChange() throws Exception {
     StagedChange sc = stageReviewableChange();
     assign(sc, sc.owner, sc.owner);
     assertThat(sender)
@@ -2634,14 +2188,6 @@
   }
 
   @Test
-  public void setAssigneeToSelfOnReviewableChangeInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    assign(sc, sc.owner, sc.owner);
-    assertThat(sender).notSent();
-  }
-
-  @Test
   public void changeAssigneeOnReviewableChange() throws Exception {
     StagedChange sc = stageReviewableChange();
     TestAccount other = accountCreator.create("other", "other@example.com", "other");
@@ -2656,8 +2202,7 @@
   }
 
   @Test
-  public void changeAssigneeToSelfOnReviewableChangeInNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void changeAssigneeToSelfOnReviewableChange() throws Exception {
     StagedChange sc = stageReviewableChange();
     assign(sc, sc.owner, sc.assignee);
     sender.clear();
@@ -2669,16 +2214,6 @@
   }
 
   @Test
-  public void changeAssigneeToSelfOnReviewableChangeInReviewDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    StagedChange sc = stageReviewableChange();
-    assign(sc, sc.owner, sc.assignee);
-    sender.clear();
-    assign(sc, sc.owner, sc.owner);
-    assertThat(sender).notSent();
-  }
-
-  @Test
   public void setAssigneeOnReviewableWipChange() throws Exception {
     StagedChange sc = stageReviewableWipChange();
     assign(sc, sc.owner, sc.assignee);
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
deleted file mode 100644
index 29f1b7d..0000000
--- a/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ /dev/null
@@ -1,1598 +0,0 @@
-// Copyright (C) 2016 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.server.notedb;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
-import static com.google.common.truth.TruthJUnit.assume;
-import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
-import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.concurrent.TimeUnit.DAYS;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static java.util.stream.Collectors.joining;
-import static java.util.stream.Collectors.toList;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Ordering;
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
-import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.api.changes.DraftInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
-import com.google.gerrit.extensions.client.Side;
-import com.google.gerrit.extensions.common.CommentInfo;
-import com.google.gerrit.extensions.common.Input;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.git.RepoRefCache;
-import com.google.gerrit.server.notedb.ChangeBundle;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.NoteDbChangeState;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.NoteDbUpdateManager;
-import com.google.gerrit.server.notedb.TestChangeRebuilderWrapper;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListNotAvailableException;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.testing.Util;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.restapi.change.PostReview;
-import com.google.gerrit.server.restapi.change.Rebuild;
-import com.google.gerrit.server.update.BatchUpdate;
-import com.google.gerrit.server.update.BatchUpdateOp;
-import com.google.gerrit.server.update.ChangeContext;
-import com.google.gerrit.server.update.UpdateException;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.NoteDbChecker;
-import com.google.gerrit.testing.NoteDbMode;
-import com.google.gerrit.testing.TestChanges;
-import com.google.gerrit.testing.TestTimeUtil;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.Timestamp;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
-import org.apache.http.Header;
-import org.apache.http.message.BasicHeader;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class ChangeRebuilderIT extends AbstractDaemonTest {
-  @ConfigSuite.Default
-  public static Config defaultConfig() {
-    Config cfg = new Config();
-    cfg.setBoolean("noteDb", null, "testRebuilderWrapper", true);
-
-    // Disable async reindex-if-stale check after index update. This avoids
-    // unintentional auto-rebuilding of the change in NoteDb during the read
-    // path of the reindex-if-stale check. For the purposes of this test, we
-    // want precise control over when auto-rebuilding happens.
-    cfg.setBoolean("index", null, "autoReindexIfStale", false);
-
-    // setNotesMigration tries to keep IDs in sync between ReviewDb and NoteDb, which is behavior
-    // unique to this test. This gets prohibitively slow if we use the default sequence gap.
-    cfg.setInt("noteDb", "changes", "initialSequenceGap", 0);
-
-    return cfg;
-  }
-
-  @Inject private NoteDbChecker checker;
-
-  @Inject private Rebuild rebuildHandler;
-
-  @Inject private Provider<ReviewDb> dbProvider;
-
-  @Inject private CommentsUtil commentsUtil;
-
-  @Inject private Provider<PostReview> postReview;
-
-  @Inject private TestChangeRebuilderWrapper rebuilderWrapper;
-
-  @Inject private Sequences seq;
-
-  @Inject private ChangeBundleReader bundleReader;
-
-  @Inject private PatchSetInfoFactory patchSetInfoFactory;
-
-  @Inject private PatchListCache patchListCache;
-
-  @Before
-  public void setUp() throws Exception {
-    assume().that(NoteDbMode.get()).isEqualTo(NoteDbMode.OFF);
-    TestTimeUtil.resetWithClockStep(1, SECONDS);
-    setNotesMigration(false, false);
-  }
-
-  @After
-  public void tearDown() {
-    TestTimeUtil.useSystemTime();
-  }
-
-  @SuppressWarnings("deprecation")
-  private void setNotesMigration(boolean writeChanges, boolean readChanges) throws Exception {
-    notesMigration.setWriteChanges(writeChanges);
-    notesMigration.setReadChanges(readChanges);
-    db = atrScope.reopenDb().getReviewDbProvider().get();
-
-    if (notesMigration.readChangeSequence()) {
-      // Copy next ReviewDb ID to NoteDb.
-      seq.getChangeIdRepoSequence().set(db.nextChangeId());
-    } else {
-      // Copy next NoteDb ID to ReviewDb.
-      while (db.nextChangeId() < seq.getChangeIdRepoSequence().next()) {}
-    }
-  }
-
-  @Test
-  public void changeFields() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    gApi.changes().id(id.get()).topic(name("a-topic"));
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void patchSets() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    amendChange(r.getChangeId());
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void publishedComment() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putComment(user, id, 1, "comment", null);
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void publishedCommentAndReply() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putComment(user, id, 1, "comment", null);
-    Map<String, List<CommentInfo>> comments = getPublishedComments(id);
-    String parentUuid = comments.get("a.txt").get(0).id;
-    putComment(user, id, 1, "comment", parentUuid);
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void patchSetWithNullGroups() throws Exception {
-    Timestamp ts = TimeUtil.nowTs();
-    Change c = TestChanges.newChange(project, user.getId(), seq.nextChangeId());
-    c.setCreatedOn(ts);
-    c.setLastUpdatedOn(ts);
-    c.setReviewStarted(true);
-    PatchSet ps =
-        TestChanges.newPatchSet(
-            c.currentPatchSetId(), "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", user.getId());
-    ps.setCreatedOn(ts);
-    db.changes().insert(Collections.singleton(c));
-    db.patchSets().insert(Collections.singleton(ps));
-
-    assertThat(ps.getGroups()).isEmpty();
-    checker.rebuildAndCheckChanges(c.getId());
-  }
-
-  @Test
-  public void draftComment() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putDraft(user, id, 1, "comment", null);
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void draftAndPublishedComment() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putDraft(user, id, 1, "draft comment", null);
-    putComment(user, id, 1, "published comment", null);
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void publishDraftComment() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putDraft(user, id, 1, "draft comment", null);
-    publishDrafts(user, id);
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void nullAccountId() throws Exception {
-    PushOneCommit.Result r = createChange();
-    PatchSet.Id psId = r.getPatchSetId();
-    Change.Id id = psId.getParentKey();
-
-    // Events need to be otherwise identical for the account ID to be compared.
-    ChangeMessage msg1 = insertMessage(id, psId, user.getId(), TimeUtil.nowTs(), "message 1");
-    insertMessage(id, psId, null, msg1.getWrittenOn(), "message 2");
-
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void nullPatchSetId() throws Exception {
-    PushOneCommit.Result r = createChange();
-    PatchSet.Id psId1 = r.getPatchSetId();
-    Change.Id id = psId1.getParentKey();
-
-    // Events need to be otherwise identical for the PatchSet.ID to be compared.
-    ChangeMessage msg1 = insertMessage(id, null, user.getId(), TimeUtil.nowTs(), "message 1");
-    insertMessage(id, null, user.getId(), msg1.getWrittenOn(), "message 2");
-
-    PatchSet.Id psId2 = amendChange(r.getChangeId()).getPatchSetId();
-
-    ChangeMessage msg3 = insertMessage(id, null, user.getId(), TimeUtil.nowTs(), "message 3");
-    insertMessage(id, null, user.getId(), msg3.getWrittenOn(), "message 4");
-
-    checker.rebuildAndCheckChanges(id);
-
-    setNotesMigration(true, true);
-
-    ChangeNotes notes = notesFactory.create(db, project, id);
-    Map<String, PatchSet.Id> psIds = new HashMap<>();
-    for (ChangeMessage msg : notes.getChangeMessages()) {
-      PatchSet.Id psId = msg.getPatchSetId();
-      assertThat(psId).named("patchset for " + msg).isNotNull();
-      psIds.put(msg.getMessage(), psId);
-    }
-    // Patch set IDs were replaced during conversion process.
-    assertThat(psIds).containsEntry("message 1", psId1);
-    assertThat(psIds).containsEntry("message 2", psId1);
-    assertThat(psIds).containsEntry("message 3", psId2);
-    assertThat(psIds).containsEntry("message 4", psId2);
-  }
-
-  @Test
-  public void noWriteToNewRef() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    checker.assertNoChangeRef(project, id);
-
-    setNotesMigration(true, false);
-    gApi.changes().id(id.get()).topic(name("a-topic"));
-
-    // First write doesn't create the ref, but rebuilding works.
-    checker.assertNoChangeRef(project, id);
-    assertThat(getUnwrappedDb().changes().get(id).getNoteDbState()).isNull();
-    checker.rebuildAndCheckChanges(id);
-
-    // Now that there is a ref, writes are "turned on" for this change, and
-    // NoteDb stays up to date without explicit rebuilding.
-    gApi.changes().id(id.get()).topic(name("new-topic"));
-    assertThat(getUnwrappedDb().changes().get(id).getNoteDbState()).isNotNull();
-    checker.checkChanges(id);
-  }
-
-  @Test
-  public void restApiNotFoundWhenNoteDbDisabled() throws Exception {
-    PushOneCommit.Result r = createChange();
-    exception.expect(ResourceNotFoundException.class);
-    rebuildHandler.apply(parseChangeResource(r.getChangeId()), new Input());
-  }
-
-  @Test
-  public void rebuildViaRestApi() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    setNotesMigration(true, false);
-
-    checker.assertNoChangeRef(project, id);
-    rebuildHandler.apply(parseChangeResource(r.getChangeId()), new Input());
-    checker.checkChanges(id);
-  }
-
-  @Test
-  public void writeToNewRefForNewChange() throws Exception {
-    PushOneCommit.Result r1 = createChange();
-    Change.Id id1 = r1.getPatchSetId().getParentKey();
-
-    setNotesMigration(true, false);
-    gApi.changes().id(id1.get()).topic(name("a-topic"));
-    PushOneCommit.Result r2 = createChange();
-    Change.Id id2 = r2.getPatchSetId().getParentKey();
-
-    // Second change was created after NoteDb writes were turned on, so it was
-    // allowed to write to a new ref.
-    checker.checkChanges(id2);
-
-    // First change was created before NoteDb writes were turned on, so its meta
-    // ref doesn't exist until a manual rebuild.
-    checker.assertNoChangeRef(project, id1);
-    checker.rebuildAndCheckChanges(id1);
-  }
-
-  @Test
-  public void noteDbChangeState() throws Exception {
-    setNotesMigration(true, true);
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-
-    ObjectId changeMetaId = getMetaRef(project, changeMetaRef(id));
-    assertThat(getUnwrappedDb().changes().get(id).getNoteDbState()).isEqualTo(changeMetaId.name());
-
-    putDraft(user, id, 1, "comment by user", null);
-    ObjectId userDraftsId = getMetaRef(allUsers, refsDraftComments(id, user.getId()));
-    assertThat(getUnwrappedDb().changes().get(id).getNoteDbState())
-        .isEqualTo(changeMetaId.name() + "," + user.getId() + "=" + userDraftsId.name());
-
-    putDraft(admin, id, 2, "comment by admin", null);
-    ObjectId adminDraftsId = getMetaRef(allUsers, refsDraftComments(id, admin.getId()));
-    assertThat(admin.getId().get()).isLessThan(user.getId().get());
-    assertThat(getUnwrappedDb().changes().get(id).getNoteDbState())
-        .isEqualTo(
-            changeMetaId.name()
-                + ","
-                + admin.getId()
-                + "="
-                + adminDraftsId.name()
-                + ","
-                + user.getId()
-                + "="
-                + userDraftsId.name());
-
-    putDraft(admin, id, 2, "revised comment by admin", null);
-    adminDraftsId = getMetaRef(allUsers, refsDraftComments(id, admin.getId()));
-    assertThat(getUnwrappedDb().changes().get(id).getNoteDbState())
-        .isEqualTo(
-            changeMetaId.name()
-                + ","
-                + admin.getId()
-                + "="
-                + adminDraftsId.name()
-                + ","
-                + user.getId()
-                + "="
-                + userDraftsId.name());
-  }
-
-  @Test
-  public void rebuildAutomaticallyWhenChangeOutOfDate() throws Exception {
-    setNotesMigration(true, true);
-
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    assertChangeUpToDate(true, id);
-
-    // Make a ReviewDb change behind NoteDb's back and ensure it's detected.
-    setNotesMigration(false, false);
-    gApi.changes().id(id.get()).topic(name("a-topic"));
-    setInvalidNoteDbState(id);
-    assertChangeUpToDate(false, id);
-
-    // On next NoteDb read, the change is transparently rebuilt.
-    setNotesMigration(true, true);
-    assertThat(gApi.changes().id(id.get()).info().topic).isEqualTo(name("a-topic"));
-    assertChangeUpToDate(true, id);
-
-    // Check that the bundles are equal.
-    ChangeBundle actual =
-        ChangeBundle.fromNotes(commentsUtil, notesFactory.create(dbProvider.get(), project, id));
-    ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
-    assertThat(actual.differencesFrom(expected)).isEmpty();
-  }
-
-  @Test
-  public void rebuildAutomaticallyWithinBatchUpdate() throws Exception {
-    setNotesMigration(true, true);
-
-    PushOneCommit.Result r = createChange();
-    final Change.Id id = r.getPatchSetId().getParentKey();
-    assertChangeUpToDate(true, id);
-
-    // Update ReviewDb and NoteDb, then revert the corresponding NoteDb change
-    // to simulate it failing.
-    NoteDbChangeState oldState = NoteDbChangeState.parse(getUnwrappedDb().changes().get(id));
-    String topic = name("a-topic");
-    gApi.changes().id(id.get()).topic(topic);
-    try (Repository repo = repoManager.openRepository(project)) {
-      new TestRepository<>(repo).update(RefNames.changeMetaRef(id), oldState.getChangeMetaId());
-    }
-    assertChangeUpToDate(false, id);
-
-    // Next NoteDb read comes inside the transaction started by BatchUpdate. In
-    // reality this could be caused by a failed update happening between when
-    // the change is parsed by ChangesCollection and when the BatchUpdate
-    // executes. We simulate it here by using BatchUpdate directly and not going
-    // through an API handler.
-    final String msg = "message from BatchUpdate";
-    try (BatchUpdate bu =
-        batchUpdateFactory.create(
-            db, project, identifiedUserFactory.create(user.getId()), TimeUtil.nowTs())) {
-      bu.addOp(
-          id,
-          new BatchUpdateOp() {
-            @Override
-            public boolean updateChange(ChangeContext ctx) throws OrmException {
-              PatchSet.Id psId = ctx.getChange().currentPatchSetId();
-              ChangeMessage cm =
-                  new ChangeMessage(
-                      new ChangeMessage.Key(id, ChangeUtil.messageUuid()),
-                      ctx.getAccountId(),
-                      ctx.getWhen(),
-                      psId);
-              cm.setMessage(msg);
-              ctx.getDb().changeMessages().insert(Collections.singleton(cm));
-              ctx.getUpdate(psId).setChangeMessage(msg);
-              return true;
-            }
-          });
-      try {
-        bu.execute();
-        fail("expected update to fail");
-      } catch (UpdateException e) {
-        assertThat(e.getMessage()).contains("cannot copy ChangeNotesState");
-      }
-    }
-
-    // TODO(dborowitz): Re-enable these assertions once we fix auto-rebuilding
-    // in the BatchUpdate path.
-    // As an implementation detail, change wasn't actually rebuilt inside the
-    // BatchUpdate transaction, but it was rebuilt during read for the
-    // subsequent reindex. Thus it's impossible to actually observe an
-    // out-of-date state in the caller.
-    // assertChangeUpToDate(true, id);
-
-    // Check that the bundles are equal.
-    // ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
-    // ChangeBundle actual = ChangeBundle.fromNotes(commentsUtil, notes);
-    // ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
-    // assertThat(actual.differencesFrom(expected)).isEmpty();
-    // assertThat(
-    //        Iterables.transform(
-    //            notes.getChangeMessages(),
-    //            ChangeMessage::getMessage))
-    //    .contains(msg);
-    // assertThat(actual.getChange().getTopic()).isEqualTo(topic);
-  }
-
-  @Test
-  public void rebuildIgnoresErrorIfChangeIsUpToDateAfter() throws Exception {
-    setNotesMigration(true, true);
-
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    assertChangeUpToDate(true, id);
-
-    // Make a ReviewDb change behind NoteDb's back and ensure it's detected.
-    setNotesMigration(false, false);
-    gApi.changes().id(id.get()).topic(name("a-topic"));
-    setInvalidNoteDbState(id);
-    assertChangeUpToDate(false, id);
-
-    // Force the next rebuild attempt to fail but also rebuild the change in the
-    // background.
-    rebuilderWrapper.stealNextUpdate();
-    setNotesMigration(true, true);
-    assertThat(gApi.changes().id(id.get()).info().topic).isEqualTo(name("a-topic"));
-    assertChangeUpToDate(true, id);
-
-    // Check that the bundles are equal.
-    ChangeBundle actual =
-        ChangeBundle.fromNotes(commentsUtil, notesFactory.create(dbProvider.get(), project, id));
-    ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
-    assertThat(actual.differencesFrom(expected)).isEmpty();
-  }
-
-  @Test
-  public void rebuildReturnsCorrectResultEvenIfSavingToNoteDbFailed() throws Exception {
-    setNotesMigration(true, true);
-
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    assertChangeUpToDate(true, id);
-    ObjectId oldMetaId = getMetaRef(project, changeMetaRef(id));
-
-    // Make a ReviewDb change behind NoteDb's back.
-    setNotesMigration(false, false);
-    gApi.changes().id(id.get()).topic(name("a-topic"));
-    setInvalidNoteDbState(id);
-    assertChangeUpToDate(false, id);
-    assertThat(getMetaRef(project, changeMetaRef(id))).isEqualTo(oldMetaId);
-
-    // Force the next rebuild attempt to fail.
-    rebuilderWrapper.failNextUpdate();
-    setNotesMigration(true, true);
-    ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
-
-    // Not up to date, but the actual returned state matches anyway.
-    assertChangeUpToDate(false, id);
-    assertThat(getMetaRef(project, changeMetaRef(id))).isEqualTo(oldMetaId);
-    ChangeBundle actual = ChangeBundle.fromNotes(commentsUtil, notes);
-    ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
-    assertThat(actual.differencesFrom(expected)).isEmpty();
-    assertChangeUpToDate(false, id);
-
-    // Another rebuild attempt succeeds
-    notesFactory.create(dbProvider.get(), project, id);
-    assertThat(getMetaRef(project, changeMetaRef(id))).isNotEqualTo(oldMetaId);
-    assertChangeUpToDate(true, id);
-  }
-
-  @Test
-  public void rebuildReturnsDraftResultWhenRebuildingInChangeNotesFails() throws Exception {
-    setNotesMigration(true, true);
-
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putDraft(user, id, 1, "comment by user", null);
-    assertChangeUpToDate(true, id);
-
-    ObjectId oldMetaId = getMetaRef(allUsers, refsDraftComments(id, user.getId()));
-
-    // Add a draft behind NoteDb's back.
-    setNotesMigration(false, false);
-    putDraft(user, id, 1, "second comment by user", null);
-    setInvalidNoteDbState(id);
-    assertDraftsUpToDate(false, id, user);
-    assertThat(getMetaRef(allUsers, refsDraftComments(id, user.getId()))).isEqualTo(oldMetaId);
-
-    // Force the next rebuild attempt to fail (in ChangeNotes).
-    rebuilderWrapper.failNextUpdate();
-    setNotesMigration(true, true);
-    ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
-    notes.getDraftComments(user.getId());
-    assertThat(getMetaRef(allUsers, refsDraftComments(id, user.getId()))).isEqualTo(oldMetaId);
-
-    // Not up to date, but the actual returned state matches anyway.
-    assertDraftsUpToDate(false, id, user);
-    ChangeBundle actual = ChangeBundle.fromNotes(commentsUtil, notes);
-    ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
-    assertThat(actual.differencesFrom(expected)).isEmpty();
-
-    // Another rebuild attempt succeeds
-    notesFactory.create(dbProvider.get(), project, id);
-    assertChangeUpToDate(true, id);
-    assertDraftsUpToDate(true, id, user);
-    assertThat(getMetaRef(allUsers, refsDraftComments(id, user.getId()))).isNotEqualTo(oldMetaId);
-  }
-
-  @Test
-  public void rebuildReturnsDraftResultWhenRebuildingInDraftCommentNotesFails() throws Exception {
-    setNotesMigration(true, true);
-
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putDraft(user, id, 1, "comment by user", null);
-    assertChangeUpToDate(true, id);
-
-    ObjectId oldMetaId = getMetaRef(allUsers, refsDraftComments(id, user.getId()));
-
-    // Add a draft behind NoteDb's back.
-    setNotesMigration(false, false);
-    putDraft(user, id, 1, "second comment by user", null);
-
-    ReviewDb db = getUnwrappedDb();
-    Change c = db.changes().get(id);
-    // Leave change meta ID alone so DraftCommentNotes does the rebuild.
-    ObjectId badSha = ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
-    NoteDbChangeState bogusState =
-        new NoteDbChangeState(
-            id,
-            PrimaryStorage.REVIEW_DB,
-            Optional.of(
-                NoteDbChangeState.RefState.create(
-                    NoteDbChangeState.parse(c).getChangeMetaId(),
-                    ImmutableMap.of(user.getId(), badSha))),
-            Optional.empty());
-    c.setNoteDbState(bogusState.toString());
-    db.changes().update(Collections.singleton(c));
-
-    assertDraftsUpToDate(false, id, user);
-    assertThat(getMetaRef(allUsers, refsDraftComments(id, user.getId()))).isEqualTo(oldMetaId);
-
-    // Force the next rebuild attempt to fail (in DraftCommentNotes).
-    rebuilderWrapper.failNextUpdate();
-    setNotesMigration(true, true);
-    ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
-    notes.getDraftComments(user.getId());
-    assertThat(getMetaRef(allUsers, refsDraftComments(id, user.getId()))).isEqualTo(oldMetaId);
-
-    // Not up to date, but the actual returned state matches anyway.
-    assertChangeUpToDate(true, id);
-    assertDraftsUpToDate(false, id, user);
-    ChangeBundle actual = ChangeBundle.fromNotes(commentsUtil, notes);
-    ChangeBundle expected = bundleReader.fromReviewDb(getUnwrappedDb(), id);
-    assertThat(actual.differencesFrom(expected)).isEmpty();
-
-    // Another rebuild attempt succeeds
-    notesFactory.create(dbProvider.get(), project, id).getDraftComments(user.getId());
-    assertChangeUpToDate(true, id);
-    assertDraftsUpToDate(true, id, user);
-    assertThat(getMetaRef(allUsers, refsDraftComments(id, user.getId()))).isNotEqualTo(oldMetaId);
-  }
-
-  @Test
-  public void rebuildAutomaticallyWhenDraftsOutOfDate() throws Exception {
-    setNotesMigration(true, true);
-    setApiUser(user);
-
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putDraft(user, id, 1, "comment", null);
-    assertDraftsUpToDate(true, id, user);
-
-    // Make a ReviewDb change behind NoteDb's back and ensure it's detected.
-    setNotesMigration(false, false);
-    putDraft(user, id, 1, "comment", null);
-    setInvalidNoteDbState(id);
-    assertDraftsUpToDate(false, id, user);
-
-    // On next NoteDb read, the drafts are transparently rebuilt.
-    setNotesMigration(true, true);
-    assertThat(gApi.changes().id(id.get()).current().drafts()).containsKey(PushOneCommit.FILE_NAME);
-    assertDraftsUpToDate(true, id, user);
-  }
-
-  @Test
-  public void pushCert() throws Exception {
-    // We don't have the code in our test harness to do signed pushes, so just
-    // use a hard-coded cert. This cert was actually generated by C git 2.2.0
-    // (albeit not for sending to Gerrit).
-    String cert =
-        "certificate version 0.1\n"
-            + "pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700\n"
-            + "pushee git://localhost/repo.git\n"
-            + "nonce 1433954361-bde756572d665bba81d8\n"
-            + "\n"
-            + "0000000000000000000000000000000000000000"
-            + "b981a177396fb47345b7df3e4d3f854c6bea7"
-            + "s/heads/master\n"
-            + "-----BEGIN PGP SIGNATURE-----\n"
-            + "Version: GnuPG v1\n"
-            + "\n"
-            + "iQEcBAABAgAGBQJVeGg5AAoJEPfTicJkUdPkUggH/RKAeI9/i/LduuiqrL/SSdIa\n"
-            + "9tYaSqJKLbXz63M/AW4Sp+4u+dVCQvnAt/a35CVEnpZz6hN4Kn/tiswOWVJf4CO7\n"
-            + "htNubGs5ZMwvD6sLYqKAnrM3WxV/2TbbjzjZW6Jkidz3jz/WRT4SmjGYiEO7aA+V\n"
-            + "4ZdIS9f7sW5VsHHYlNThCA7vH8Uu48bUovFXyQlPTX0pToSgrWV3JnTxDNxfn3iG\n"
-            + "IL0zTY/qwVCdXgFownLcs6J050xrrBWIKqfcWr3u4D2aCLyR0v+S/KArr7ulZygY\n"
-            + "+SOklImn8TAZiNxhWtA6ens66IiammUkZYFv7SSzoPLFZT4dC84SmGPWgf94NoQ=\n"
-            + "=XFeC\n"
-            + "-----END PGP SIGNATURE-----\n";
-
-    PushOneCommit.Result r = createChange();
-    PatchSet.Id psId = r.getPatchSetId();
-    Change.Id id = psId.getParentKey();
-
-    PatchSet ps = db.patchSets().get(psId);
-    ps.setPushCertificate(cert);
-    db.patchSets().update(Collections.singleton(ps));
-    indexer.index(db, project, id);
-
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void emptyTopic() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    Change c = db.changes().get(id);
-    assertThat(c.getTopic()).isNull();
-    c.setTopic("");
-    db.changes().update(Collections.singleton(c));
-
-    checker.rebuildAndCheckChanges(id);
-
-    setNotesMigration(true, true);
-
-    // Rebuild and check was successful, but NoteDb doesn't support storing an
-    // empty topic, so it comes out as null.
-    ChangeNotes notes = notesFactory.create(db, project, id);
-    assertThat(notes.getChange().getTopic()).isNull();
-  }
-
-  @Test
-  public void commentBeforeFirstPatchSet() throws Exception {
-    PushOneCommit.Result r = createChange();
-    PatchSet.Id psId = r.getPatchSetId();
-    Change.Id id = psId.getParentKey();
-
-    Change c = db.changes().get(id);
-    c.setCreatedOn(new Timestamp(c.getCreatedOn().getTime() - 5000));
-    db.changes().update(Collections.singleton(c));
-    indexer.index(db, project, id);
-
-    ReviewInput rin = new ReviewInput();
-    rin.message = "comment";
-
-    Timestamp ts = new Timestamp(c.getCreatedOn().getTime() + 2000);
-    assertThat(ts).isGreaterThan(c.getCreatedOn());
-    assertThat(ts).isLessThan(db.patchSets().get(psId).getCreatedOn());
-    RevisionResource revRsrc = parseCurrentRevisionResource(r.getChangeId());
-    postReview.get().apply(batchUpdateFactory, revRsrc, rin, ts);
-
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void commentPredatingChangeBySomeoneOtherThanOwner() throws Exception {
-    PushOneCommit.Result r = createChange();
-    PatchSet.Id psId = r.getPatchSetId();
-    Change.Id id = psId.getParentKey();
-    Change c = db.changes().get(id);
-
-    ReviewInput rin = new ReviewInput();
-    rin.message = "comment";
-
-    Timestamp ts = new Timestamp(c.getCreatedOn().getTime() - 10000);
-    RevisionResource revRsrc = parseCurrentRevisionResource(r.getChangeId());
-    setApiUser(user);
-    postReview.get().apply(batchUpdateFactory, revRsrc, rin, ts);
-
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void noteDbUsesOriginalSubjectFromPatchSetAndIgnoresChangeField() throws Exception {
-    PushOneCommit.Result r = createChange();
-    String orig = r.getChange().change().getSubject();
-    r =
-        pushFactory
-            .create(
-                db,
-                admin.getIdent(),
-                testRepo,
-                orig + " v2",
-                PushOneCommit.FILE_NAME,
-                "new contents",
-                r.getChangeId())
-            .to("refs/for/master");
-    r.assertOkStatus();
-
-    PatchSet.Id psId = r.getPatchSetId();
-    Change.Id id = psId.getParentKey();
-    Change c = db.changes().get(id);
-
-    c.setCurrentPatchSet(psId, c.getSubject(), "Bogus original subject");
-    db.changes().update(Collections.singleton(c));
-
-    checker.rebuildAndCheckChanges(id);
-
-    setNotesMigration(true, true);
-    ChangeNotes notes = notesFactory.create(db, project, id);
-    Change nc = notes.getChange();
-    assertThat(nc.getSubject()).isEqualTo(c.getSubject());
-    assertThat(nc.getSubject()).isEqualTo(orig + " v2");
-    assertThat(nc.getOriginalSubject()).isNotEqualTo(c.getOriginalSubject());
-    assertThat(nc.getOriginalSubject()).isEqualTo(orig);
-  }
-
-  @Test
-  public void ignorePatchLineCommentsOnPatchSet0() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change change = r.getChange().change();
-    Change.Id id = change.getId();
-
-    PatchLineComment comment =
-        new PatchLineComment(
-            new PatchLineComment.Key(
-                new Patch.Key(new PatchSet.Id(id, 0), PushOneCommit.FILE_NAME), "uuid"),
-            0,
-            user.getId(),
-            null,
-            TimeUtil.nowTs());
-    comment.setSide((short) 1);
-    comment.setMessage("message");
-    comment.setStatus(PatchLineComment.Status.PUBLISHED);
-    db.patchComments().insert(Collections.singleton(comment));
-    indexer.index(db, change.getProject(), id);
-
-    checker.rebuildAndCheckChanges(id);
-
-    setNotesMigration(true, true);
-    ChangeNotes notes = notesFactory.create(db, project, id);
-    assertThat(notes.getComments()).isEmpty();
-  }
-
-  @Test
-  public void leadingSpacesInSubject() throws Exception {
-    String subj = "   " + PushOneCommit.SUBJECT;
-    PushOneCommit push =
-        pushFactory.create(
-            db,
-            admin.getIdent(),
-            testRepo,
-            subj,
-            PushOneCommit.FILE_NAME,
-            PushOneCommit.FILE_CONTENT);
-    PushOneCommit.Result r = push.to("refs/for/master");
-    r.assertOkStatus();
-    Change change = r.getChange().change();
-    assertThat(change.getSubject()).isEqualTo(subj);
-    Change.Id id = r.getPatchSetId().getParentKey();
-
-    checker.rebuildAndCheckChanges(id);
-
-    setNotesMigration(true, true);
-    ChangeNotes notes = notesFactory.create(db, project, id);
-    assertThat(notes.getChange().getSubject()).isNotEqualTo(subj);
-    assertThat(notes.getChange().getSubject()).isEqualTo(PushOneCommit.SUBJECT);
-  }
-
-  @Test
-  public void allTimestampsExceptUpdatedAreEqualDueToBadMigration() throws Exception {
-    // https://bugs.chromium.org/p/gerrit/issues/detail?id=7397
-    PushOneCommit.Result r = createChange();
-    Change c = r.getChange().change();
-    Change.Id id = c.getId();
-    Timestamp ts = TimeUtil.nowTs();
-    Timestamp origUpdated = c.getLastUpdatedOn();
-
-    c.setCreatedOn(ts);
-    assertThat(c.getCreatedOn()).isGreaterThan(c.getLastUpdatedOn());
-    db.changes().update(Collections.singleton(c));
-
-    List<ChangeMessage> cm = db.changeMessages().byChange(id).toList();
-    cm.forEach(m -> m.setWrittenOn(ts));
-    db.changeMessages().update(cm);
-
-    List<PatchSet> ps = db.patchSets().byChange(id).toList();
-    ps.forEach(p -> p.setCreatedOn(ts));
-    db.patchSets().update(ps);
-
-    List<PatchSetApproval> psa = db.patchSetApprovals().byChange(id).toList();
-    psa.forEach(p -> p.setGranted(ts));
-    db.patchSetApprovals().update(psa);
-
-    List<PatchLineComment> plc = db.patchComments().byChange(id).toList();
-    plc.forEach(p -> p.setWrittenOn(ts));
-    db.patchComments().update(plc);
-
-    checker.rebuildAndCheckChanges(id);
-
-    setNotesMigration(true, true);
-    ChangeNotes notes = notesFactory.create(db, project, id);
-    assertThat(notes.getChange().getCreatedOn()).isEqualTo(origUpdated);
-    assertThat(notes.getChange().getLastUpdatedOn()).isAtLeast(origUpdated);
-    assertThat(notes.getPatchSets().get(new PatchSet.Id(id, 1)).getCreatedOn())
-        .isEqualTo(origUpdated);
-  }
-
-  @Test
-  public void createWithAutoRebuildingDisabled() throws Exception {
-    ReviewDb oldDb = db;
-    setNotesMigration(true, true);
-
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    ChangeNotes oldNotes = notesFactory.create(db, project, id);
-
-    // Make a ReviewDb change behind NoteDb's back.
-    Change c = oldDb.changes().get(id);
-    assertThat(c.getTopic()).isNull();
-    String topic = name("a-topic");
-    c.setTopic(topic);
-    oldDb.changes().update(Collections.singleton(c));
-
-    c = oldDb.changes().get(c.getId());
-    ChangeNotes newNotes = notesFactory.createWithAutoRebuildingDisabled(c, null);
-    assertThat(newNotes.getChange().getTopic()).isNotEqualTo(topic);
-    assertThat(newNotes.getChange().getTopic()).isEqualTo(oldNotes.getChange().getTopic());
-  }
-
-  @Test
-  public void rebuildDeletesOldDraftRefs() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putDraft(user, id, 1, "comment", null);
-
-    Account.Id otherAccountId = new Account.Id(user.getId().get() + 1234);
-    String otherDraftRef = refsDraftComments(id, otherAccountId);
-
-    try (Repository repo = repoManager.openRepository(allUsers);
-        ObjectInserter ins = repo.newObjectInserter()) {
-      ObjectId sha = ins.insert(OBJ_BLOB, "garbage data".getBytes(UTF_8));
-      ins.flush();
-      RefUpdate ru = repo.updateRef(otherDraftRef);
-      ru.setExpectedOldObjectId(ObjectId.zeroId());
-      ru.setNewObjectId(sha);
-      assertThat(ru.update()).isEqualTo(RefUpdate.Result.NEW);
-    }
-
-    checker.rebuildAndCheckChanges(id);
-
-    try (Repository repo = repoManager.openRepository(allUsers)) {
-      assertThat(repo.exactRef(otherDraftRef)).isNull();
-    }
-  }
-
-  @Test
-  public void failWhenWritesDisabled() throws Exception {
-    setNotesMigration(true, true);
-
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    assertChangeUpToDate(true, id);
-    assertThat(gApi.changes().id(id.get()).info().topic).isNull();
-
-    // Turning off writes causes failure.
-    setNotesMigration(false, true);
-    try {
-      gApi.changes().id(id.get()).topic(name("a-topic"));
-      fail("Expected write to fail");
-    } catch (RestApiException e) {
-      assertChangesReadOnly(e);
-    }
-
-    // Update was not written.
-    assertThat(gApi.changes().id(id.get()).info().topic).isNull();
-    assertChangeUpToDate(true, id);
-  }
-
-  @Test
-  public void rebuildWhenWritesDisabledWorksButDoesNotWrite() throws Exception {
-    setNotesMigration(true, true);
-
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    assertChangeUpToDate(true, id);
-
-    // Make a ReviewDb change behind NoteDb's back and ensure it's detected.
-    setNotesMigration(false, false);
-    gApi.changes().id(id.get()).topic(name("a-topic"));
-    setInvalidNoteDbState(id);
-    assertChangeUpToDate(false, id);
-
-    // On next NoteDb read, change is rebuilt in-memory but not stored.
-    setNotesMigration(false, true);
-    assertThat(gApi.changes().id(id.get()).info().topic).isEqualTo(name("a-topic"));
-    assertChangeUpToDate(false, id);
-
-    // Attempting to write directly causes failure.
-    try {
-      gApi.changes().id(id.get()).topic(name("other-topic"));
-      fail("Expected write to fail");
-    } catch (RestApiException e) {
-      assertChangesReadOnly(e);
-    }
-
-    // Update was not written.
-    assertThat(gApi.changes().id(id.get()).info().topic).isEqualTo(name("a-topic"));
-    assertChangeUpToDate(false, id);
-  }
-
-  @Test
-  public void rebuildChangeWithNoPatchSets() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    db.changes().beginTransaction(id);
-    try {
-      db.patchSets().delete(db.patchSets().byChange(id));
-      db.commit();
-    } finally {
-      db.rollback();
-    }
-
-    try {
-      checker.rebuildAndCheckChanges(id);
-      assert_().fail("expected NoPatchSetsException");
-    } catch (NoPatchSetsException e) {
-      // Expected.
-    }
-
-    Change c = db.changes().get(id);
-    assertThat(c.getNoteDbState()).isNull();
-    checker.assertNoChangeRef(project, id);
-  }
-
-  @Test
-  public void rebuildChangeWithNoEntitiesOtherThanChange() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    db.changes().beginTransaction(id);
-    try {
-      db.changeMessages().delete(db.changeMessages().byChange(id));
-      db.patchSets().delete(db.patchSets().byChange(id));
-      db.patchSetApprovals().delete(db.patchSetApprovals().byChange(id));
-      db.patchComments().delete(db.patchComments().byChange(id));
-      db.commit();
-    } finally {
-      db.rollback();
-    }
-
-    try {
-      checker.rebuildAndCheckChanges(id);
-      assert_().fail("expected NoPatchSetsException");
-    } catch (NoPatchSetsException e) {
-      // Expected.
-    }
-
-    Change c = db.changes().get(id);
-    assertThat(c.getNoteDbState()).isNull();
-    checker.assertNoChangeRef(project, id);
-  }
-
-  @Test
-  public void rebuildEntitiesCreatedByImpersonation() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    PatchSet.Id psId = new PatchSet.Id(id, 1);
-    String prefix = "/changes/" + id + "/revisions/current/";
-
-    // For each of the entities that have a real user field, create one entity
-    // without impersonation and one with.
-    CommentInput ci = new CommentInput();
-    ci.path = Patch.COMMIT_MSG;
-    ci.side = Side.REVISION;
-    ci.line = 1;
-    ci.message = "comment without impersonation";
-    ReviewInput ri = new ReviewInput();
-    ri.label("Code-Review", -1);
-    ri.message = "message without impersonation";
-    ri.drafts = DraftHandling.KEEP;
-    ri.comments = ImmutableMap.of(ci.path, ImmutableList.of(ci));
-    userRestSession.post(prefix + "review", ri).assertOK();
-
-    DraftInput di = new DraftInput();
-    di.path = Patch.COMMIT_MSG;
-    di.side = Side.REVISION;
-    di.line = 1;
-    di.message = "draft without impersonation";
-    userRestSession.put(prefix + "drafts", di).assertCreated();
-
-    allowRunAs();
-    try {
-      Header runAs = new BasicHeader("X-Gerrit-RunAs", user.id.toString());
-      ci.message = "comment with impersonation";
-      ri.message = "message with impersonation";
-      ri.label("Code-Review", 1);
-      adminRestSession.postWithHeader(prefix + "review", runAs, ri).assertOK();
-
-      di.message = "draft with impersonation";
-      adminRestSession.putWithHeader(prefix + "drafts", runAs, di).assertCreated();
-    } finally {
-      removeRunAs();
-    }
-
-    List<ChangeMessage> msgs =
-        Ordering.natural()
-            .onResultOf(ChangeMessage::getWrittenOn)
-            .sortedCopy(db.changeMessages().byChange(id));
-    assertThat(msgs).hasSize(3);
-    assertThat(msgs.get(1).getMessage()).endsWith("message without impersonation");
-    assertThat(msgs.get(1).getAuthor()).isEqualTo(user.id);
-    assertThat(msgs.get(1).getRealAuthor()).isEqualTo(user.id);
-    assertThat(msgs.get(2).getMessage()).endsWith("message with impersonation");
-    assertThat(msgs.get(2).getAuthor()).isEqualTo(user.id);
-    assertThat(msgs.get(2).getRealAuthor()).isEqualTo(admin.id);
-
-    List<PatchSetApproval> psas = db.patchSetApprovals().byChange(id).toList();
-    assertThat(psas).hasSize(1);
-    assertThat(psas.get(0).getLabel()).isEqualTo("Code-Review");
-    assertThat(psas.get(0).getValue()).isEqualTo(1);
-    assertThat(psas.get(0).getAccountId()).isEqualTo(user.id);
-    assertThat(psas.get(0).getRealAccountId()).isEqualTo(admin.id);
-
-    Ordering<PatchLineComment> commentOrder =
-        Ordering.natural().onResultOf(PatchLineComment::getWrittenOn);
-    List<PatchLineComment> drafts =
-        commentOrder.sortedCopy(db.patchComments().draftByPatchSetAuthor(psId, user.id));
-    assertThat(drafts).hasSize(2);
-    assertThat(drafts.get(0).getMessage()).isEqualTo("draft without impersonation");
-    assertThat(drafts.get(0).getAuthor()).isEqualTo(user.id);
-    assertThat(drafts.get(0).getRealAuthor()).isEqualTo(user.id);
-    assertThat(drafts.get(1).getMessage()).isEqualTo("draft with impersonation");
-    assertThat(drafts.get(1).getAuthor()).isEqualTo(user.id);
-    assertThat(drafts.get(1).getRealAuthor()).isEqualTo(admin.id);
-
-    List<PatchLineComment> pub =
-        commentOrder.sortedCopy(db.patchComments().publishedByPatchSet(psId));
-    assertThat(pub).hasSize(2);
-    assertThat(pub.get(0).getMessage()).isEqualTo("comment without impersonation");
-    assertThat(pub.get(0).getAuthor()).isEqualTo(user.id);
-    assertThat(pub.get(0).getRealAuthor()).isEqualTo(user.id);
-    assertThat(pub.get(1).getMessage()).isEqualTo("comment with impersonation");
-    assertThat(pub.get(1).getAuthor()).isEqualTo(user.id);
-    assertThat(pub.get(1).getRealAuthor()).isEqualTo(admin.id);
-  }
-
-  @Test
-  public void laterEventsDependingOnEarlierPatchSetDontIntefereWithOtherPatchSets()
-      throws Exception {
-    PushOneCommit.Result r1 = createChange();
-    ChangeData cd = r1.getChange();
-    Change.Id id = cd.getId();
-    amendChange(cd.change().getKey().get());
-    TestTimeUtil.incrementClock(90, TimeUnit.DAYS);
-
-    ReviewInput rin = ReviewInput.approve();
-    rin.message = "Some very late message on PS1";
-    gApi.changes().id(id.get()).revision(1).review(rin);
-
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void ignoreChangeMessageBeyondCurrentPatchSet() throws Exception {
-    PushOneCommit.Result r = createChange();
-    PatchSet.Id psId1 = r.getPatchSetId();
-    Change.Id id = psId1.getParentKey();
-    gApi.changes().id(id.get()).current().review(ReviewInput.recommend());
-
-    r = amendChange(r.getChangeId());
-    PatchSet.Id psId2 = r.getPatchSetId();
-
-    assertThat(db.patchSets().byChange(id)).hasSize(2);
-    assertThat(db.changeMessages().byPatchSet(psId2)).hasSize(1);
-    db.patchSets().deleteKeys(Collections.singleton(psId2));
-
-    checker.rebuildAndCheckChanges(psId2.getParentKey());
-    setNotesMigration(true, true);
-
-    ChangeData cd = changeDataFactory.create(db, project, id);
-    assertThat(cd.change().currentPatchSetId()).isEqualTo(psId1);
-    assertThat(cd.patchSets().stream().map(PatchSet::getId).collect(toList()))
-        .containsExactly(psId1);
-    PatchSet ps = cd.currentPatchSet();
-    assertThat(ps).isNotNull();
-    assertThat(ps.getId()).isEqualTo(psId1);
-  }
-
-  @Test
-  public void highestNumberedPatchSetIsNotCurrent() throws Exception {
-    PushOneCommit.Result r1 = createChange();
-    PatchSet.Id psId1 = r1.getPatchSetId();
-    Change.Id id = psId1.getParentKey();
-    PushOneCommit.Result r2 = amendChange(r1.getChangeId());
-    PatchSet.Id psId2 = r2.getPatchSetId();
-
-    try (BatchUpdate bu =
-        batchUpdateFactory.create(
-            db, project, identifiedUserFactory.create(user.getId()), TimeUtil.nowTs())) {
-      bu.addOp(
-          id,
-          new BatchUpdateOp() {
-            @Override
-            public boolean updateChange(ChangeContext ctx)
-                throws PatchSetInfoNotAvailableException {
-              ctx.getChange()
-                  .setCurrentPatchSet(patchSetInfoFactory.get(ctx.getDb(), ctx.getNotes(), psId1));
-              return true;
-            }
-          });
-      bu.execute();
-    }
-    ChangeNotes notes = notesFactory.create(db, project, id);
-    assertThat(psUtil.byChangeAsMap(db, notes).keySet()).containsExactly(psId1, psId2);
-    assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId1);
-
-    assertThat(db.changes().get(id).currentPatchSetId()).isEqualTo(psId1);
-
-    checker.rebuildAndCheckChanges(id);
-    setNotesMigration(true, true);
-
-    notes = notesFactory.create(db, project, id);
-    assertThat(psUtil.byChangeAsMap(db, notes).keySet()).containsExactly(psId1, psId2);
-    assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId1);
-  }
-
-  @Test
-  public void resolveCommentsInheritsValueFromParentWhenUnspecified() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getPatchSetId().getParentKey();
-    putDraft(user, id, 1, "comment", true);
-    putDraft(user, id, 1, "newComment", null);
-
-    Map<String, List<CommentInfo>> comments = gApi.changes().id(id.get()).current().drafts();
-    for (List<CommentInfo> cList : comments.values()) {
-      for (CommentInfo ci : cList) {
-        assertThat(ci.unresolved).isTrue();
-      }
-    }
-  }
-
-  @Test
-  public void rebuilderRespectsReadOnlyInNoteDbChangeState() throws Exception {
-    TestTimeUtil.resetWithClockStep(1, SECONDS);
-    PushOneCommit.Result r = createChange();
-    PatchSet.Id psId1 = r.getPatchSetId();
-    Change.Id id = psId1.getParentKey();
-
-    checker.rebuildAndCheckChanges(id);
-    setNotesMigration(true, true);
-
-    ReviewDb db = getUnwrappedDb();
-    Change c = db.changes().get(id);
-    NoteDbChangeState state = NoteDbChangeState.parse(c);
-    Timestamp until = new Timestamp(TimeUtil.nowMs() + MILLISECONDS.convert(1, DAYS));
-    state = state.withReadOnlyUntil(until);
-    c.setNoteDbState(state.toString());
-    db.changes().update(Collections.singleton(c));
-
-    try {
-      rebuilderWrapper.rebuild(db, id);
-      fail("expected rebuild to fail");
-    } catch (OrmRuntimeException e) {
-      assertThat(e.getMessage()).contains("read-only until");
-    }
-
-    TestTimeUtil.setClock(new Timestamp(until.getTime() + MILLISECONDS.convert(1, SECONDS)));
-    rebuilderWrapper.rebuild(db, id);
-  }
-
-  @Test
-  public void commitWithCrLineEndings() throws Exception {
-    PushOneCommit.Result r =
-        createChange("Subject\r\rBody\r", PushOneCommit.FILE_NAME, PushOneCommit.FILE_CONTENT);
-    Change c = r.getChange().change();
-
-    // This assertion demonstrates an arguable bug in JGit's commit subject
-    // parsing, and shows how this kind of data might have gotten into
-    // ReviewDb. If that bug ever gets fixed upstream, this assert may start
-    // failing. If that happens, this test can be rewritten to directly set the
-    // subject field in ReviewDb.
-    assertThat(c.getSubject()).isEqualTo("Subject\r\rBody");
-
-    checker.rebuildAndCheckChanges(c.getId());
-  }
-
-  @Test
-  public void patchSetsOutOfOrder() throws Exception {
-    String id = createChange().getChangeId();
-    amendChange(id);
-    PushOneCommit.Result r = amendChange(id);
-
-    ChangeData cd = r.getChange();
-    PatchSet.Id psId3 = cd.change().currentPatchSetId();
-    assertThat(psId3.get()).isEqualTo(3);
-
-    PatchSet ps1 = db.patchSets().get(new PatchSet.Id(cd.getId(), 1));
-    PatchSet ps3 = db.patchSets().get(psId3);
-    assertThat(ps1.getCreatedOn()).isLessThan(ps3.getCreatedOn());
-
-    // Simulate an old Gerrit bug by setting the created timestamp of the latest
-    // patch set ID to the timestamp of PS1.
-    ps3.setCreatedOn(ps1.getCreatedOn());
-    db.patchSets().update(Collections.singleton(ps3));
-
-    checker.rebuildAndCheckChanges(cd.getId());
-
-    setNotesMigration(true, true);
-    cd = changeDataFactory.create(db, project, cd.getId());
-    assertThat(cd.change().currentPatchSetId()).isEqualTo(psId3);
-
-    List<PatchSet> patchSets = ImmutableList.copyOf(cd.patchSets());
-    assertThat(patchSets).hasSize(3);
-
-    PatchSet newPs1 = patchSets.get(0);
-    assertThat(newPs1.getId()).isEqualTo(ps1.getId());
-    assertThat(newPs1.getCreatedOn()).isEqualTo(ps1.getCreatedOn());
-
-    PatchSet newPs2 = patchSets.get(1);
-    assertThat(newPs2.getCreatedOn()).isGreaterThan(newPs1.getCreatedOn());
-
-    PatchSet newPs3 = patchSets.get(2);
-    assertThat(newPs3.getId()).isEqualTo(ps3.getId());
-    // Migrated with a newer timestamp than the original, to preserve ordering.
-    assertThat(newPs3.getCreatedOn()).isAtLeast(newPs2.getCreatedOn());
-    assertThat(newPs3.getCreatedOn()).isGreaterThan(ps1.getCreatedOn());
-  }
-
-  @Test
-  public void ignoreNoteDbStateWithNoCorrespondingRefWhenWritesAndReadsDisabled() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-    ReviewDb db = getUnwrappedDb();
-    Change c = db.changes().get(id);
-    c.setNoteDbState("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
-    db.changes().update(Collections.singleton(c));
-    c = db.changes().get(id);
-
-    String refName = RefNames.changeMetaRef(id);
-    assertThat(getMetaRef(project, refName)).isNull();
-
-    ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
-    assertThat(notes.getChange().getRowVersion()).isEqualTo(c.getRowVersion());
-
-    notes = notesFactory.createChecked(dbProvider.get(), project, id);
-    assertThat(notes.getChange().getRowVersion()).isEqualTo(c.getRowVersion());
-
-    assertThat(getMetaRef(project, refName)).isNull();
-  }
-
-  @Test
-  public void autoRebuildMissingRefWriteOnly() throws Exception {
-    setNotesMigration(true, false);
-    testAutoRebuildMissingRef();
-  }
-
-  @Test
-  public void autoRebuildMissingRefReadWrite() throws Exception {
-    setNotesMigration(true, true);
-    testAutoRebuildMissingRef();
-  }
-
-  private void testAutoRebuildMissingRef() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    assertChangeUpToDate(true, id);
-    notesFactory.createChecked(db, project, id);
-
-    try (Repository repo = repoManager.openRepository(project)) {
-      RefUpdate ru = repo.updateRef(RefNames.changeMetaRef(id));
-      ru.setForceUpdate(true);
-      assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
-    }
-    assertChangeUpToDate(false, id);
-
-    notesFactory.createChecked(db, project, id);
-    assertChangeUpToDate(true, id);
-  }
-
-  @Test
-  public void missingPatchSetCommitOkForCommentsNotOnParentSide() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    putDraft(user, id, 1, "draft comment", null, Side.REVISION);
-    putComment(user, id, 1, "published comment", null, Side.REVISION);
-
-    ReviewDb db = getUnwrappedDb();
-    PatchSet ps = db.patchSets().get(new PatchSet.Id(id, 1));
-    ps.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    db.patchSets().update(Collections.singleton(ps));
-
-    try {
-      patchListCache.getOldId(db.changes().get(id), ps, null);
-      assert_().fail("Expected PatchListNotAvailableException");
-    } catch (PatchListNotAvailableException e) {
-      // Expected.
-    }
-
-    checker.rebuildAndCheckChanges(id);
-  }
-
-  @Test
-  public void missingPatchSetCommitOmitsCommentsOnParentSide() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    CommentInfo draftInfo = putDraft(user, id, 1, "draft comment", null, Side.PARENT);
-    putComment(user, id, 1, "published comment", null, Side.PARENT);
-    CommentInfo commentInfo =
-        gApi.changes()
-            .id(id.get())
-            .comments()
-            .values()
-            .stream()
-            .flatMap(List::stream)
-            .findFirst()
-            .get();
-
-    ReviewDb db = getUnwrappedDb();
-    PatchSet ps = db.patchSets().get(new PatchSet.Id(id, 1));
-    ps.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    db.patchSets().update(Collections.singleton(ps));
-
-    try {
-      patchListCache.getOldId(db.changes().get(id), ps, null);
-      assert_().fail("Expected PatchListNotAvailableException");
-    } catch (PatchListNotAvailableException e) {
-      // Expected.
-    }
-
-    checker.rebuildAndCheckChange(
-        id,
-        Stream.of(draftInfo.id, commentInfo.id)
-            .sorted()
-            .map(c -> id + ",1," + PushOneCommit.FILE_NAME + "," + c)
-            .collect(
-                joining(", ", "PatchLineComment.Key sets differ: [", "] only in A; [] only in B")));
-  }
-
-  private void assertChangesReadOnly(RestApiException e) throws Exception {
-    Throwable cause = e.getCause();
-    assertThat(cause).isInstanceOf(UpdateException.class);
-    assertThat(cause.getCause()).isInstanceOf(OrmException.class);
-    assertThat(cause.getCause()).hasMessageThat().isEqualTo(NoteDbUpdateManager.CHANGES_READ_ONLY);
-  }
-
-  private void setInvalidNoteDbState(Change.Id id) throws Exception {
-    ReviewDb db = getUnwrappedDb();
-    Change c = db.changes().get(id);
-    // In reality we would have NoteDb writes enabled, which would write a real
-    // state into this field. For tests however, we turn NoteDb writes off, so
-    // just use a dummy state to force ChangeNotes to view the notes as
-    // out-of-date.
-    c.setNoteDbState("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
-    db.changes().update(Collections.singleton(c));
-  }
-
-  private void assertChangeUpToDate(boolean expected, Change.Id id) throws Exception {
-    try (Repository repo = repoManager.openRepository(project)) {
-      Change c = getUnwrappedDb().changes().get(id);
-      assertThat(c).isNotNull();
-      assertThat(c.getNoteDbState()).isNotNull();
-      NoteDbChangeState state = NoteDbChangeState.parse(c);
-      assertThat(state).isNotNull();
-      assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
-      assertThat(state.isChangeUpToDate(new RepoRefCache(repo))).isEqualTo(expected);
-    }
-  }
-
-  private void assertDraftsUpToDate(boolean expected, Change.Id changeId, TestAccount account)
-      throws Exception {
-    try (Repository repo = repoManager.openRepository(allUsers)) {
-      Change c = getUnwrappedDb().changes().get(changeId);
-      assertThat(c).isNotNull();
-      assertThat(c.getNoteDbState()).isNotNull();
-      NoteDbChangeState state = NoteDbChangeState.parse(c);
-      assertThat(state.areDraftsUpToDate(new RepoRefCache(repo), account.getId()))
-          .isEqualTo(expected);
-    }
-  }
-
-  private ObjectId getMetaRef(Project.NameKey p, String name) throws Exception {
-    try (Repository repo = repoManager.openRepository(p)) {
-      Ref ref = repo.exactRef(name);
-      return ref != null ? ref.getObjectId() : null;
-    }
-  }
-
-  private CommentInfo putDraft(
-      TestAccount account, Change.Id id, int line, String msg, Boolean unresolved)
-      throws Exception {
-    return putDraft(account, id, line, msg, unresolved, Side.REVISION);
-  }
-
-  private CommentInfo putDraft(
-      TestAccount account, Change.Id id, int line, String msg, Boolean unresolved, Side side)
-      throws Exception {
-    DraftInput in = new DraftInput();
-    in.side = side;
-    in.line = line;
-    in.message = msg;
-    in.path = PushOneCommit.FILE_NAME;
-    in.unresolved = unresolved;
-    AcceptanceTestRequestScope.Context old = setApiUser(account);
-    try {
-      return gApi.changes().id(id.get()).current().createDraft(in).get();
-    } finally {
-      atrScope.set(old);
-    }
-  }
-
-  private void putComment(TestAccount account, Change.Id id, int line, String msg, String inReplyTo)
-      throws Exception {
-    putComment(account, id, line, msg, inReplyTo, Side.REVISION);
-  }
-
-  private void putComment(
-      TestAccount account, Change.Id id, int line, String msg, String inReplyTo, Side side)
-      throws Exception {
-    CommentInput in = new CommentInput();
-    in.side = side;
-    in.line = line;
-    in.message = msg;
-    in.inReplyTo = inReplyTo;
-    ReviewInput rin = new ReviewInput();
-    rin.comments = new HashMap<>();
-    rin.comments.put(PushOneCommit.FILE_NAME, ImmutableList.of(in));
-    rin.drafts = ReviewInput.DraftHandling.KEEP;
-    AcceptanceTestRequestScope.Context old = setApiUser(account);
-    try {
-      gApi.changes().id(id.get()).current().review(rin);
-    } finally {
-      atrScope.set(old);
-    }
-  }
-
-  private void publishDrafts(TestAccount account, Change.Id id) throws Exception {
-    ReviewInput rin = new ReviewInput();
-    rin.drafts = ReviewInput.DraftHandling.PUBLISH_ALL_REVISIONS;
-    AcceptanceTestRequestScope.Context old = setApiUser(account);
-    try {
-      gApi.changes().id(id.get()).current().review(rin);
-    } finally {
-      atrScope.set(old);
-    }
-  }
-
-  private ChangeMessage insertMessage(
-      Change.Id id, PatchSet.Id psId, Account.Id author, Timestamp ts, String message)
-      throws Exception {
-    ChangeMessage msg =
-        new ChangeMessage(new ChangeMessage.Key(id, ChangeUtil.messageUuid()), author, ts, psId);
-    msg.setMessage(message);
-    db.changeMessages().insert(Collections.singleton(msg));
-
-    Change c = db.changes().get(id);
-    if (ts.compareTo(c.getLastUpdatedOn()) > 0) {
-      c.setLastUpdatedOn(ts);
-      db.changes().update(Collections.singleton(c));
-    }
-
-    return msg;
-  }
-
-  private ReviewDb getUnwrappedDb() {
-    ReviewDb db = dbProvider.get();
-    return ReviewDbUtil.unwrapDb(db);
-  }
-
-  private void allowRunAs() throws Exception {
-    try (ProjectConfigUpdate u = updateProject(allProjects)) {
-      Util.allow(
-          u.getConfig(),
-          GlobalCapability.RUN_AS,
-          systemGroupBackend.getGroup(REGISTERED_USERS).getUUID());
-      u.save();
-    }
-  }
-
-  private void removeRunAs() throws Exception {
-    try (ProjectConfigUpdate u = updateProject(allProjects)) {
-      Util.remove(
-          u.getConfig(),
-          GlobalCapability.RUN_AS,
-          systemGroupBackend.getGroup(REGISTERED_USERS).getUUID());
-      u.save();
-    }
-  }
-
-  private Map<String, List<CommentInfo>> getPublishedComments(Change.Id id) throws Exception {
-    return gApi.changes().id(id.get()).current().comments();
-  }
-}
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
index 8d6fecd..5047b73 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
 import static java.util.stream.Collectors.toList;
 
@@ -56,7 +55,6 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevSort;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.junit.Before;
 import org.junit.Test;
 
 public class NoteDbOnlyIT extends AbstractDaemonTest {
@@ -70,14 +68,8 @@
 
   @Inject private RetryHelper retryHelper;
 
-  @Before
-  public void setUp() throws Exception {
-    assume().that(notesMigration.disableChangeReviewDb()).isTrue();
-  }
-
   @Test
   public void updateChangeFailureRollsBackRefUpdate() throws Exception {
-    assume().that(notesMigration.disableChangeReviewDb()).isTrue();
     PushOneCommit.Result r = createChange();
     Change.Id id = r.getChange().getId();
 
@@ -149,7 +141,6 @@
 
   @Test
   public void retryOnLockFailureWithAtomicUpdates() throws Exception {
-    assume().that(notesMigration.disableChangeReviewDb()).isTrue();
     PushOneCommit.Result r = createChange();
     Change.Id id = r.getChange().getId();
     String master = "refs/heads/master";
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbPrimaryIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbPrimaryIT.java
deleted file mode 100644
index 26d5461..0000000
--- a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbPrimaryIT.java
+++ /dev/null
@@ -1,524 +0,0 @@
-// Copyright (C) 2016 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.server.notedb;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
-import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.REVIEW_DB;
-import static com.google.gerrit.server.notedb.NoteDbUtil.formatTime;
-import static java.util.concurrent.TimeUnit.DAYS;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static java.util.stream.Collectors.toList;
-
-import com.github.rholder.retry.Retryer;
-import com.github.rholder.retry.RetryerBuilder;
-import com.github.rholder.retry.StopStrategies;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.extensions.api.changes.DraftInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
-import com.google.gerrit.extensions.client.ChangeStatus;
-import com.google.gerrit.extensions.common.ApprovalInfo;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.CommentInfo;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.InternalUser;
-import com.google.gerrit.server.git.RepoRefCache;
-import com.google.gerrit.server.notedb.ChangeBundle;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.NoteDbChangeState;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.PrimaryStorageMigrator;
-import com.google.gerrit.server.notedb.TestChangeRebuilderWrapper;
-import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.NoteDbMode;
-import com.google.gerrit.testing.TestTimeUtil;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
-import com.google.inject.Inject;
-import com.google.inject.util.Providers;
-import java.sql.Timestamp;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@NoHttpd
-public class NoteDbPrimaryIT extends AbstractDaemonTest {
-  @ConfigSuite.Default
-  public static Config defaultConfig() {
-    Config cfg = new Config();
-    cfg.setString("notedb", null, "concurrentWriterTimeout", "0s");
-    cfg.setString("notedb", null, "primaryStorageMigrationTimeout", "1d");
-    cfg.setBoolean("noteDb", null, "testRebuilderWrapper", true);
-    return cfg;
-  }
-
-  @Inject private ChangeBundleReader bundleReader;
-  @Inject private CommentsUtil commentsUtil;
-  @Inject private TestChangeRebuilderWrapper rebuilderWrapper;
-  @Inject private ChangeNotes.Factory changeNotesFactory;
-  @Inject private ChangeUpdate.Factory updateFactory;
-  @Inject private InternalUser.Factory internalUserFactory;
-  @Inject private RetryHelper retryHelper;
-
-  private PrimaryStorageMigrator migrator;
-
-  @Before
-  public void setUp() throws Exception {
-    assume().that(NoteDbMode.get()).isEqualTo(NoteDbMode.READ_WRITE);
-    db = ReviewDbUtil.unwrapDb(db);
-    TestTimeUtil.resetWithClockStep(1, SECONDS);
-    migrator = newMigrator(null);
-  }
-
-  private PrimaryStorageMigrator newMigrator(
-      @Nullable Retryer<NoteDbChangeState> ensureRebuiltRetryer) {
-    return new PrimaryStorageMigrator(
-        cfg,
-        Providers.of(db),
-        repoManager,
-        allUsers,
-        rebuilderWrapper,
-        ensureRebuiltRetryer,
-        changeNotesFactory,
-        queryProvider,
-        updateFactory,
-        internalUserFactory,
-        retryHelper);
-  }
-
-  @After
-  public void tearDown() {
-    TestTimeUtil.useSystemTime();
-  }
-
-  @Test
-  public void updateChange() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-    setNoteDbPrimary(id);
-
-    gApi.changes().id(id.get()).current().review(ReviewInput.approve());
-    gApi.changes().id(id.get()).current().submit();
-
-    ChangeInfo info = gApi.changes().id(id.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
-    ApprovalInfo approval = Iterables.getOnlyElement(info.labels.get("Code-Review").all);
-    assertThat(approval._accountId).isEqualTo(admin.id.get());
-    assertThat(approval.value).isEqualTo(2);
-    assertThat(info.messages).hasSize(3);
-    assertThat(Iterables.getLast(info.messages).message)
-        .isEqualTo("Change has been successfully merged by " + admin.fullName);
-
-    ChangeNotes notes = notesFactory.create(db, project, id);
-    assertThat(notes.getChange().getStatus()).isEqualTo(Change.Status.MERGED);
-    assertThat(notes.getChange().getNoteDbState())
-        .isEqualTo(NoteDbChangeState.NOTE_DB_PRIMARY_STATE);
-
-    // Writes weren't reflected in ReviewDb.
-    assertThat(db.changes().get(id).getStatus()).isEqualTo(Change.Status.NEW);
-    assertThat(db.patchSetApprovals().byChange(id)).isEmpty();
-    assertThat(db.changeMessages().byChange(id)).hasSize(1);
-  }
-
-  @Test
-  public void deleteDraftComment() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-    setNoteDbPrimary(id);
-
-    DraftInput din = new DraftInput();
-    din.path = PushOneCommit.FILE_NAME;
-    din.line = 1;
-    din.message = "A comment";
-    gApi.changes().id(id.get()).current().createDraft(din);
-
-    CommentInfo di =
-        Iterables.getOnlyElement(
-            gApi.changes().id(id.get()).current().drafts().get(PushOneCommit.FILE_NAME));
-    assertThat(di.message).isEqualTo(din.message);
-
-    assertThat(db.patchComments().draftByChangeFileAuthor(id, din.path, admin.id)).isEmpty();
-
-    gApi.changes().id(id.get()).current().draft(di.id).delete();
-    assertThat(gApi.changes().id(id.get()).current().drafts()).isEmpty();
-  }
-
-  @Test
-  public void deleteVote() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-    setNoteDbPrimary(id);
-
-    gApi.changes().id(id.get()).current().review(ReviewInput.approve());
-    List<ApprovalInfo> approvals = gApi.changes().id(id.get()).get().labels.get("Code-Review").all;
-    assertThat(approvals).hasSize(1);
-    assertThat(approvals.get(0).value).isEqualTo(2);
-
-    gApi.changes().id(id.get()).reviewer(admin.id.toString()).deleteVote("Code-Review");
-
-    approvals = gApi.changes().id(id.get()).get().labels.get("Code-Review").all;
-    assertThat(approvals).hasSize(1);
-    assertThat(approvals.get(0).value).isEqualTo(0);
-  }
-
-  @Test
-  public void deleteVoteViaReview() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-    setNoteDbPrimary(id);
-
-    gApi.changes().id(id.get()).current().review(ReviewInput.approve());
-    List<ApprovalInfo> approvals = gApi.changes().id(id.get()).get().labels.get("Code-Review").all;
-    assertThat(approvals).hasSize(1);
-    assertThat(approvals.get(0).value).isEqualTo(2);
-
-    gApi.changes().id(id.get()).current().review(ReviewInput.noScore());
-
-    approvals = gApi.changes().id(id.get()).get().labels.get("Code-Review").all;
-    assertThat(approvals).hasSize(1);
-    assertThat(approvals.get(0).value).isEqualTo(0);
-  }
-
-  @Test
-  public void deleteReviewer() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-    setNoteDbPrimary(id);
-
-    gApi.changes().id(id.get()).addReviewer(user.id.toString());
-    assertThat(getReviewers(id)).containsExactly(user.id);
-    gApi.changes().id(id.get()).reviewer(user.id.toString()).remove();
-    assertThat(getReviewers(id)).isEmpty();
-  }
-
-  @Test
-  public void readOnlyReviewDb() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-    testReadOnly(id);
-  }
-
-  @Test
-  public void readOnlyNoteDb() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-    setNoteDbPrimary(id);
-    testReadOnly(id);
-  }
-
-  private void testReadOnly(Change.Id id) throws Exception {
-    Timestamp before = TimeUtil.nowTs();
-    Timestamp until = new Timestamp(before.getTime() + 1000 * 3600);
-
-    // Set read-only.
-    Change c = db.changes().get(id);
-    assertThat(c).named("change " + id).isNotNull();
-    NoteDbChangeState state = NoteDbChangeState.parse(c);
-    state = state.withReadOnlyUntil(until);
-    c.setNoteDbState(state.toString());
-    db.changes().update(Collections.singleton(c));
-
-    assertThat(gApi.changes().id(id.get()).get().subject).isEqualTo(PushOneCommit.SUBJECT);
-    assertThat(gApi.changes().id(id.get()).get().topic).isNull();
-    try {
-      gApi.changes().id(id.get()).topic("a-topic");
-      fail("expected read-only exception");
-    } catch (RestApiException e) {
-      Optional<Throwable> oe =
-          Throwables.getCausalChain(e)
-              .stream()
-              .filter(x -> x instanceof OrmRuntimeException)
-              .findFirst();
-      assertThat(oe).named("OrmRuntimeException in causal chain of " + e).isPresent();
-      assertThat(oe.get().getMessage()).contains("read-only");
-    }
-    assertThat(gApi.changes().id(id.get()).get().topic).isNull();
-
-    TestTimeUtil.setClock(new Timestamp(until.getTime() + 1000));
-    assertThat(gApi.changes().id(id.get()).get().subject).isEqualTo(PushOneCommit.SUBJECT);
-    gApi.changes().id(id.get()).topic("a-topic");
-    assertThat(gApi.changes().id(id.get()).get().topic).isEqualTo("a-topic");
-  }
-
-  @Test
-  public void migrateToNoteDb() throws Exception {
-    testMigrateToNoteDb(createChange().getChange().getId());
-  }
-
-  @Test
-  public void migrateToNoteDbWithRebuildingFirst() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    Change c = db.changes().get(id);
-    c.setNoteDbState("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
-    db.changes().update(Collections.singleton(c));
-    testMigrateToNoteDb(id);
-  }
-
-  private void testMigrateToNoteDb(Change.Id id) throws Exception {
-    assertThat(PrimaryStorage.of(db.changes().get(id))).isEqualTo(PrimaryStorage.REVIEW_DB);
-    migrator.migrateToNoteDbPrimary(id);
-    assertNoteDbPrimary(id);
-
-    gApi.changes().id(id.get()).topic("a-topic");
-    assertThat(gApi.changes().id(id.get()).get().topic).isEqualTo("a-topic");
-    assertThat(db.changes().get(id).getTopic()).isNull();
-  }
-
-  @Test
-  public void migrateToNoteDbFailsRebuildingOnceAndRetries() throws Exception {
-    Change.Id id = createChange().getChange().getId();
-
-    Change c = db.changes().get(id);
-    c.setNoteDbState("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
-    db.changes().update(Collections.singleton(c));
-    rebuilderWrapper.failNextUpdate();
-
-    migrator =
-        newMigrator(
-            RetryerBuilder.<NoteDbChangeState>newBuilder()
-                .retryIfException()
-                .withStopStrategy(StopStrategies.neverStop())
-                .build());
-    migrator.migrateToNoteDbPrimary(id);
-    assertNoteDbPrimary(id);
-  }
-
-  @Test
-  public void migrateToNoteDbFailsRebuildingAndStops() throws Exception {
-    Change.Id id = createChange().getChange().getId();
-
-    Change c = db.changes().get(id);
-    c.setNoteDbState("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
-    db.changes().update(Collections.singleton(c));
-    rebuilderWrapper.failNextUpdate();
-
-    migrator =
-        newMigrator(
-            RetryerBuilder.<NoteDbChangeState>newBuilder()
-                .retryIfException()
-                .withStopStrategy(StopStrategies.stopAfterAttempt(1))
-                .build());
-    exception.expect(OrmException.class);
-    exception.expectMessage("Retrying failed");
-    migrator.migrateToNoteDbPrimary(id);
-  }
-
-  @Test
-  public void migrateToNoteDbMissingOldState() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    Change c = db.changes().get(id);
-    c.setNoteDbState(null);
-    db.changes().update(Collections.singleton(c));
-
-    exception.expect(PrimaryStorageMigrator.NoNoteDbStateException.class);
-    exception.expectMessage("no note_db_state");
-    migrator.migrateToNoteDbPrimary(id);
-  }
-
-  @Test
-  public void migrateToNoteDbLeaseExpires() throws Exception {
-    TestTimeUtil.resetWithClockStep(2, DAYS);
-    exception.expect(OrmRuntimeException.class);
-    exception.expectMessage("read-only lease");
-    migrator.migrateToNoteDbPrimary(createChange().getChange().getId());
-  }
-
-  @Test
-  public void migrateToNoteDbAlreadyReadOnly() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    Change c = db.changes().get(id);
-    NoteDbChangeState state = NoteDbChangeState.parse(c);
-    Timestamp until = new Timestamp(TimeUtil.nowMs() + MILLISECONDS.convert(1, DAYS));
-    state = state.withReadOnlyUntil(until);
-    c.setNoteDbState(state.toString());
-    db.changes().update(Collections.singleton(c));
-
-    exception.expect(OrmRuntimeException.class);
-    exception.expectMessage("read-only until " + until);
-    migrator.migrateToNoteDbPrimary(id);
-  }
-
-  @Test
-  public void migrateToNoteDbAlreadyMigrated() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    assertThat(PrimaryStorage.of(db.changes().get(id))).isEqualTo(PrimaryStorage.REVIEW_DB);
-    migrator.migrateToNoteDbPrimary(id);
-    assertNoteDbPrimary(id);
-
-    migrator.migrateToNoteDbPrimary(id);
-    assertNoteDbPrimary(id);
-  }
-
-  @Test
-  public void rebuildReviewDb() throws Exception {
-    Change c = createChange().getChange().change();
-    Change.Id id = c.getId();
-
-    CommentInput cin = new CommentInput();
-    cin.line = 1;
-    cin.message = "Published comment";
-    ReviewInput rin = ReviewInput.approve();
-    rin.comments = ImmutableMap.of(PushOneCommit.FILE_NAME, ImmutableList.of(cin));
-    gApi.changes().id(id.get()).current().review(ReviewInput.approve());
-
-    DraftInput din = new DraftInput();
-    din.path = PushOneCommit.FILE_NAME;
-    din.line = 1;
-    din.message = "Draft comment";
-    gApi.changes().id(id.get()).current().createDraft(din);
-    gApi.changes().id(id.get()).current().review(ReviewInput.approve());
-    gApi.changes().id(id.get()).current().createDraft(din);
-
-    assertThat(db.changeMessages().byChange(id)).isNotEmpty();
-    assertThat(db.patchSets().byChange(id)).isNotEmpty();
-    assertThat(db.patchSetApprovals().byChange(id)).isNotEmpty();
-    assertThat(db.patchComments().byChange(id)).isNotEmpty();
-
-    ChangeBundle noteDbBundle =
-        ChangeBundle.fromNotes(commentsUtil, notesFactory.create(db, project, id));
-
-    setNoteDbPrimary(id);
-
-    db.changeMessages().delete(db.changeMessages().byChange(id));
-    db.patchSets().delete(db.patchSets().byChange(id));
-    db.patchSetApprovals().delete(db.patchSetApprovals().byChange(id));
-    db.patchComments().delete(db.patchComments().byChange(id));
-    ChangeMessage bogusMessage =
-        ChangeMessagesUtil.newMessage(
-            c.currentPatchSetId(),
-            identifiedUserFactory.create(admin.getId()),
-            TimeUtil.nowTs(),
-            "some message",
-            null);
-    db.changeMessages().insert(Collections.singleton(bogusMessage));
-
-    rebuilderWrapper.rebuildReviewDb(db, project, id);
-
-    assertThat(db.changeMessages().byChange(id)).isNotEmpty();
-    assertThat(db.patchSets().byChange(id)).isNotEmpty();
-    assertThat(db.patchSetApprovals().byChange(id)).isNotEmpty();
-    assertThat(db.patchComments().byChange(id)).isNotEmpty();
-
-    ChangeBundle reviewDbBundle = bundleReader.fromReviewDb(ReviewDbUtil.unwrapDb(db), id);
-    assertThat(reviewDbBundle.differencesFrom(noteDbBundle)).isEmpty();
-  }
-
-  @Test
-  public void migrateBackToReviewDbPrimary() throws Exception {
-    Change c = createChange().getChange().change();
-    Change.Id id = c.getId();
-
-    migrator.migrateToNoteDbPrimary(id);
-    assertNoteDbPrimary(id);
-
-    gApi.changes().id(id.get()).topic("new-topic");
-    assertThat(gApi.changes().id(id.get()).topic()).isEqualTo("new-topic");
-    assertThat(db.changes().get(id).getTopic()).isNotEqualTo("new-topic");
-
-    migrator.migrateToReviewDbPrimary(id, null);
-    ObjectId metaId;
-    try (Repository repo = repoManager.openRepository(c.getProject());
-        RevWalk rw = new RevWalk(repo)) {
-      metaId = repo.exactRef(RefNames.changeMetaRef(id)).getObjectId();
-      RevCommit commit = rw.parseCommit(metaId);
-      rw.parseBody(commit);
-      assertThat(commit.getFullMessage())
-          .contains("Read-only-until: " + formatTime(serverIdent.get(), new Timestamp(0)));
-    }
-    NoteDbChangeState state = NoteDbChangeState.parse(db.changes().get(id));
-    assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
-    assertThat(state.getChangeMetaId()).isEqualTo(metaId);
-    assertThat(gApi.changes().id(id.get()).topic()).isEqualTo("new-topic");
-    assertThat(db.changes().get(id).getTopic()).isEqualTo("new-topic");
-
-    ChangeNotes notes = notesFactory.create(db, project, id);
-    assertThat(notes.getRevision()).isEqualTo(metaId); // No rebuilding, change was up to date.
-    assertThat(notes.getReadOnlyUntil()).isNotNull();
-
-    gApi.changes().id(id.get()).topic("reviewdb-topic");
-    assertThat(db.changes().get(id).getTopic()).isEqualTo("reviewdb-topic");
-  }
-
-  private void setNoteDbPrimary(Change.Id id) throws Exception {
-    Change c = db.changes().get(id);
-    assertThat(c).named("change " + id).isNotNull();
-    NoteDbChangeState state = NoteDbChangeState.parse(c);
-    assertThat(state.getPrimaryStorage()).named("storage of " + id).isEqualTo(REVIEW_DB);
-
-    try (Repository changeRepo = repoManager.openRepository(c.getProject());
-        Repository allUsersRepo = repoManager.openRepository(allUsers)) {
-      assertThat(state.isUpToDate(new RepoRefCache(changeRepo), new RepoRefCache(allUsersRepo)))
-          .named("change " + id + " up to date")
-          .isTrue();
-    }
-
-    c.setNoteDbState(NoteDbChangeState.NOTE_DB_PRIMARY_STATE);
-    db.changes().update(Collections.singleton(c));
-  }
-
-  private void assertNoteDbPrimary(Change.Id id) throws Exception {
-    assertThat(PrimaryStorage.of(db.changes().get(id))).isEqualTo(PrimaryStorage.NOTE_DB);
-  }
-
-  private List<Account.Id> getReviewers(Change.Id id) throws Exception {
-    return gApi.changes()
-        .id(id.get())
-        .get()
-        .reviewers
-        .values()
-        .stream()
-        .flatMap(Collection::stream)
-        .map(a -> new Account.Id(a._accountId))
-        .collect(toList());
-  }
-}
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
deleted file mode 100644
index 87c5ace..0000000
--- a/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
+++ /dev/null
@@ -1,627 +0,0 @@
-// Copyright (C) 2017 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.server.notedb;
-
-import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
-import static com.google.gerrit.server.notedb.NoteDbChangeState.NOTE_DB_PRIMARY_STATE;
-import static com.google.gerrit.server.notedb.NotesMigrationState.NOTE_DB;
-import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_NO_SEQUENCE;
-import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY;
-import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY;
-import static com.google.gerrit.server.notedb.NotesMigrationState.REVIEW_DB;
-import static com.google.gerrit.server.notedb.NotesMigrationState.WRITE;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Comparator.naturalOrder;
-import static org.easymock.EasyMock.createStrictMock;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSortedSet;
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.GerritConfig;
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.acceptance.Sandboxed;
-import com.google.gerrit.acceptance.UseLocalDisk;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.registration.RegistrationHandle;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.notedb.ChangeBundle;
-import com.google.gerrit.server.notedb.ChangeBundleReader;
-import com.google.gerrit.server.notedb.NoteDbChangeState;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.notedb.NoteDbChangeState.RefState;
-import com.google.gerrit.server.notedb.NotesMigrationState;
-import com.google.gerrit.server.notedb.rebuild.MigrationException;
-import com.google.gerrit.server.notedb.rebuild.NoteDbMigrator;
-import com.google.gerrit.server.notedb.rebuild.NotesMigrationStateListener;
-import com.google.gerrit.server.schema.ReviewDbFactory;
-import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.NoteDbMode;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Stream;
-import org.eclipse.jgit.internal.storage.file.FileRepository;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@Sandboxed
-@UseLocalDisk
-@NoHttpd
-public class OnlineNoteDbMigrationIT extends AbstractDaemonTest {
-  private static final String INVALID_STATE = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
-
-  @ConfigSuite.Default
-  public static Config defaultConfig() {
-    Config cfg = new Config();
-    cfg.setInt("noteDb", "changes", "sequenceBatchSize", 10);
-    cfg.setInt("noteDb", "changes", "initialSequenceGap", 500);
-    return cfg;
-  }
-
-  // Tests in this class are generally interested in the actual ReviewDb contents, but the shifting
-  // migration state may result in various kinds of wrappers showing up unexpectedly.
-  @Inject @ReviewDbFactory private SchemaFactory<ReviewDb> schemaFactory;
-
-  @Inject private ChangeBundleReader changeBundleReader;
-  @Inject private CommentsUtil commentsUtil;
-  @Inject private DynamicSet<NotesMigrationStateListener> listeners;
-  @Inject private Provider<NoteDbMigrator.Builder> migratorBuilderProvider;
-  @Inject private Sequences sequences;
-  @Inject private SitePaths sitePaths;
-
-  private FileBasedConfig noteDbConfig;
-  private List<RegistrationHandle> addedListeners;
-
-  @Before
-  public void setUp() throws Exception {
-    assume().that(NoteDbMode.get()).isEqualTo(NoteDbMode.OFF);
-    // Unlike in the running server, for tests, we don't stack notedb.config on gerrit.config.
-    noteDbConfig = new FileBasedConfig(sitePaths.notedb_config.toFile(), FS.detect());
-    assertNotesMigrationState(REVIEW_DB, false, false);
-    addedListeners = new ArrayList<>();
-  }
-
-  @After
-  public void tearDown() throws Exception {
-    if (addedListeners != null) {
-      addedListeners.forEach(RegistrationHandle::remove);
-      addedListeners = null;
-    }
-  }
-
-  @Test
-  public void preconditionsFail() throws Exception {
-    List<Change.Id> cs = ImmutableList.of(new Change.Id(1));
-    List<Project.NameKey> ps = ImmutableList.of(new Project.NameKey("p"));
-    assertMigrationException(
-        "Cannot rebuild without noteDb.changes.write=true", b -> b, NoteDbMigrator::rebuild);
-    assertMigrationException(
-        "Cannot set both changes and projects", b -> b.setChanges(cs).setProjects(ps), m -> {});
-    assertMigrationException(
-        "Cannot set changes or projects during full migration",
-        b -> b.setChanges(cs),
-        NoteDbMigrator::migrate);
-    assertMigrationException(
-        "Cannot set changes or projects during full migration",
-        b -> b.setProjects(ps),
-        NoteDbMigrator::migrate);
-
-    setNotesMigrationState(READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY);
-    assertMigrationException(
-        "Migration has already progressed past the endpoint of the \"trial mode\" state",
-        b -> b.setTrialMode(true),
-        NoteDbMigrator::migrate);
-
-    setNotesMigrationState(READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY);
-    assertMigrationException(
-        "Cannot force rebuild changes; NoteDb is already the primary storage for some changes",
-        b -> b.setForceRebuild(true),
-        NoteDbMigrator::migrate);
-  }
-
-  @Test
-  @GerritConfig(name = "noteDb.changes.initialSequenceGap", value = "-7")
-  public void initialSequenceGapMustBeNonNegative() throws Exception {
-    setNotesMigrationState(READ_WRITE_NO_SEQUENCE);
-    assertMigrationException("Sequence gap must be non-negative: -7", b -> b, m -> {});
-  }
-
-  @Test
-  public void rebuildOneChangeTrialModeAndForceRebuild() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    migrate(b -> b.setTrialMode(true));
-    assertNotesMigrationState(READ_WRITE_NO_SEQUENCE, false, true);
-
-    ObjectId oldMetaId;
-    try (Repository repo = repoManager.openRepository(project);
-        ReviewDb db = schemaFactory.open()) {
-      Ref ref = repo.exactRef(RefNames.changeMetaRef(id));
-      assertThat(ref).isNotNull();
-      oldMetaId = ref.getObjectId();
-
-      Change c = db.changes().get(id);
-      assertThat(c).isNotNull();
-      NoteDbChangeState state = NoteDbChangeState.parse(c);
-      assertThat(state).isNotNull();
-      assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
-      assertThat(state.getRefState()).hasValue(RefState.create(oldMetaId, ImmutableMap.of()));
-
-      // Force change to be out of date, and change topic so it will get rebuilt as something other
-      // than oldMetaId.
-      c.setNoteDbState(INVALID_STATE);
-      c.setTopic(name("a-new-topic"));
-      db.changes().update(ImmutableList.of(c));
-    }
-
-    migrate(b -> b.setTrialMode(true));
-    assertNotesMigrationState(READ_WRITE_NO_SEQUENCE, false, true);
-
-    try (Repository repo = repoManager.openRepository(project);
-        ReviewDb db = schemaFactory.open()) {
-      // Change is out of date, but was not rebuilt without forceRebuild.
-      assertThat(repo.exactRef(RefNames.changeMetaRef(id)).getObjectId()).isEqualTo(oldMetaId);
-      Change c = db.changes().get(id);
-      assertThat(c.getNoteDbState()).isEqualTo(INVALID_STATE);
-    }
-
-    migrate(b -> b.setTrialMode(true).setForceRebuild(true));
-    assertNotesMigrationState(READ_WRITE_NO_SEQUENCE, false, true);
-
-    try (Repository repo = repoManager.openRepository(project);
-        ReviewDb db = schemaFactory.open()) {
-      Ref ref = repo.exactRef(RefNames.changeMetaRef(id));
-      assertThat(ref).isNotNull();
-      ObjectId newMetaId = ref.getObjectId();
-      assertThat(newMetaId).isNotEqualTo(oldMetaId);
-
-      NoteDbChangeState state = NoteDbChangeState.parse(db.changes().get(id));
-      assertThat(state).isNotNull();
-      assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
-      assertThat(state.getRefState()).hasValue(RefState.create(newMetaId, ImmutableMap.of()));
-    }
-  }
-
-  @Test
-  public void autoMigrateTrialMode() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    migrate(b -> b.setAutoMigrate(true).setTrialMode(true).setStopAtStateForTesting(WRITE));
-    assertNotesMigrationState(WRITE, true, true);
-
-    migrate(b -> b);
-    // autoMigrate is still enabled so that we can continue the migration by only unsetting trial.
-    assertNotesMigrationState(READ_WRITE_NO_SEQUENCE, true, true);
-
-    ObjectId metaId;
-    try (Repository repo = repoManager.openRepository(project);
-        ReviewDb db = schemaFactory.open()) {
-      Ref ref = repo.exactRef(RefNames.changeMetaRef(id));
-      assertThat(ref).isNotNull();
-      metaId = ref.getObjectId();
-      NoteDbChangeState state = NoteDbChangeState.parse(db.changes().get(id));
-      assertThat(state).isNotNull();
-      assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
-      assertThat(state.getRefState()).hasValue(RefState.create(metaId, ImmutableMap.of()));
-    }
-
-    // Unset trial mode and the next migration runs to completion.
-    noteDbConfig.load();
-    NoteDbMigrator.setTrialMode(noteDbConfig, false);
-    noteDbConfig.save();
-
-    migrate(b -> b);
-    assertNotesMigrationState(NOTE_DB, false, false);
-
-    try (Repository repo = repoManager.openRepository(project);
-        ReviewDb db = schemaFactory.open()) {
-      Ref ref = repo.exactRef(RefNames.changeMetaRef(id));
-      assertThat(ref).isNotNull();
-      assertThat(ref.getObjectId()).isEqualTo(metaId);
-      NoteDbChangeState state = NoteDbChangeState.parse(db.changes().get(id));
-      assertThat(state).isNotNull();
-      assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.NOTE_DB);
-    }
-  }
-
-  @Test
-  public void rebuildSubsetOfChanges() throws Exception {
-    setNotesMigrationState(WRITE);
-
-    PushOneCommit.Result r1 = createChange();
-    PushOneCommit.Result r2 = createChange();
-    Change.Id id1 = r1.getChange().getId();
-    Change.Id id2 = r2.getChange().getId();
-
-    try (ReviewDb db = schemaFactory.open()) {
-      Change c1 = db.changes().get(id1);
-      c1.setNoteDbState(INVALID_STATE);
-      Change c2 = db.changes().get(id2);
-      c2.setNoteDbState(INVALID_STATE);
-      db.changes().update(ImmutableList.of(c1, c2));
-    }
-
-    migrate(b -> b.setChanges(ImmutableList.of(id2)), NoteDbMigrator::rebuild);
-
-    try (ReviewDb db = schemaFactory.open()) {
-      NoteDbChangeState s1 = NoteDbChangeState.parse(db.changes().get(id1));
-      assertThat(s1.getChangeMetaId().name()).isEqualTo(INVALID_STATE);
-
-      NoteDbChangeState s2 = NoteDbChangeState.parse(db.changes().get(id2));
-      assertThat(s2.getChangeMetaId().name()).isNotEqualTo(INVALID_STATE);
-    }
-  }
-
-  @Test
-  public void rebuildSubsetOfProjects() throws Exception {
-    setNotesMigrationState(WRITE);
-
-    Project.NameKey p2 = createProject("project2");
-    TestRepository<?> tr2 = cloneProject(p2, admin);
-
-    PushOneCommit.Result r1 = createChange();
-    PushOneCommit.Result r2 = pushFactory.create(db, admin.getIdent(), tr2).to("refs/for/master");
-    Change.Id id1 = r1.getChange().getId();
-    Change.Id id2 = r2.getChange().getId();
-
-    String invalidState = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
-    try (ReviewDb db = schemaFactory.open()) {
-      Change c1 = db.changes().get(id1);
-      c1.setNoteDbState(invalidState);
-      Change c2 = db.changes().get(id2);
-      c2.setNoteDbState(invalidState);
-      db.changes().update(ImmutableList.of(c1, c2));
-    }
-
-    migrate(b -> b.setProjects(ImmutableList.of(p2)), NoteDbMigrator::rebuild);
-
-    try (ReviewDb db = schemaFactory.open()) {
-      NoteDbChangeState s1 = NoteDbChangeState.parse(db.changes().get(id1));
-      assertThat(s1.getChangeMetaId().name()).isEqualTo(invalidState);
-
-      NoteDbChangeState s2 = NoteDbChangeState.parse(db.changes().get(id2));
-      assertThat(s2.getChangeMetaId().name()).isNotEqualTo(invalidState);
-    }
-  }
-
-  @Test
-  public void enableSequencesNoGap() throws Exception {
-    testEnableSequences(0, 3, "13");
-  }
-
-  @Test
-  public void enableSequencesWithGap() throws Exception {
-    testEnableSequences(-1, 502, "512");
-  }
-
-  private void testEnableSequences(int builderOption, int expectedFirstId, String expectedRefValue)
-      throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-    assertThat(id.get()).isEqualTo(1);
-
-    migrate(
-        b ->
-            b.setSequenceGap(builderOption)
-                .setStopAtStateForTesting(READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY));
-
-    assertThat(sequences.nextChangeId()).isEqualTo(expectedFirstId);
-    assertThat(sequences.nextChangeId()).isEqualTo(expectedFirstId + 1);
-
-    try (Repository repo = repoManager.openRepository(allProjects);
-        ObjectReader reader = repo.newObjectReader()) {
-      Ref ref = repo.exactRef("refs/sequences/changes");
-      assertThat(ref).isNotNull();
-      ObjectLoader loader = reader.open(ref.getObjectId());
-      assertThat(loader.getType()).isEqualTo(Constants.OBJ_BLOB);
-      // Acquired a block of 10 to serve the first nextChangeId call after migration.
-      assertThat(new String(loader.getCachedBytes(), UTF_8)).isEqualTo(expectedRefValue);
-    }
-
-    try (ReviewDb db = schemaFactory.open()) {
-      // Underlying, unused ReviewDb is still on its own sequence.
-      @SuppressWarnings("deprecation")
-      int nextFromReviewDb = db.nextChangeId();
-      assertThat(nextFromReviewDb).isEqualTo(3);
-    }
-  }
-
-  @Test
-  public void fullMigrationSameThread() throws Exception {
-    testFullMigration(1);
-  }
-
-  @Test
-  public void fullMigrationMultipleThreads() throws Exception {
-    testFullMigration(2);
-  }
-
-  private void testFullMigration(int threads) throws Exception {
-    PushOneCommit.Result r1 = createChange();
-    PushOneCommit.Result r2 = createChange();
-    Change.Id id1 = r1.getChange().getId();
-    Change.Id id2 = r2.getChange().getId();
-
-    Set<String> objectFiles = getObjectFiles(project);
-    assertThat(objectFiles).isNotEmpty();
-
-    migrate(b -> b.setThreads(threads));
-
-    assertNotesMigrationState(NOTE_DB, false, false);
-    assertThat(sequences.nextChangeId()).isEqualTo(503);
-    assertThat(getObjectFiles(project)).containsExactlyElementsIn(objectFiles);
-
-    ObjectId oldMetaId = null;
-    int rowVersion = 0;
-    try (ReviewDb db = schemaFactory.open();
-        Repository repo = repoManager.openRepository(project)) {
-      for (Change.Id id : ImmutableList.of(id1, id2)) {
-        String refName = RefNames.changeMetaRef(id);
-        Ref ref = repo.exactRef(refName);
-        assertThat(ref).named(refName).isNotNull();
-
-        Change c = db.changes().get(id);
-        assertThat(c.getTopic()).named("topic of change %s", id).isNull();
-        NoteDbChangeState s = NoteDbChangeState.parse(c);
-        assertThat(s.getPrimaryStorage())
-            .named("primary storage of change %s", id)
-            .isEqualTo(PrimaryStorage.NOTE_DB);
-        assertThat(s.getRefState()).named("ref state of change %s").isEmpty();
-
-        if (id.equals(id1)) {
-          oldMetaId = ref.getObjectId();
-          rowVersion = c.getRowVersion();
-        }
-      }
-    }
-
-    // Do not open a new context, to simulate races with other threads that opened a context earlier
-    // in the migration process; this needs to work.
-    gApi.changes().id(id1.get()).topic(name("a-topic"));
-
-    // Of course, it should also work with a new context.
-    resetCurrentApiUser();
-    gApi.changes().id(id1.get()).topic(name("another-topic"));
-
-    try (ReviewDb db = schemaFactory.open();
-        Repository repo = repoManager.openRepository(project)) {
-      assertThat(repo.exactRef(RefNames.changeMetaRef(id1)).getObjectId()).isNotEqualTo(oldMetaId);
-
-      Change c = db.changes().get(id1);
-      assertThat(c.getTopic()).isNull();
-      assertThat(c.getRowVersion()).isEqualTo(rowVersion);
-    }
-  }
-
-  @Test
-  public void fullMigrationOneChangeWithNoPatchSets() throws Exception {
-    PushOneCommit.Result r1 = createChange();
-    PushOneCommit.Result r2 = createChange();
-    Change.Id id1 = r1.getChange().getId();
-    Change.Id id2 = r2.getChange().getId();
-
-    db.changes().beginTransaction(id2);
-    try {
-      db.patchSets().delete(db.patchSets().byChange(id2));
-      db.commit();
-    } finally {
-      db.rollback();
-    }
-
-    migrate(b -> b);
-    assertNotesMigrationState(NOTE_DB, false, false);
-
-    try (ReviewDb db = schemaFactory.open();
-        Repository repo = repoManager.openRepository(project)) {
-      assertThat(repo.exactRef(RefNames.changeMetaRef(id1))).isNotNull();
-      assertThat(db.changes().get(id1).getNoteDbState()).isEqualTo(NOTE_DB_PRIMARY_STATE);
-
-      // A change with no patch sets is so corrupt that it is completely skipped by the migration
-      // process.
-      assertThat(repo.exactRef(RefNames.changeMetaRef(id2))).isNull();
-      assertThat(db.changes().get(id2).getNoteDbState()).isNull();
-    }
-  }
-
-  @Test
-  public void fullMigrationMissingPatchSetRefs() throws Exception {
-    PushOneCommit.Result r = createChange();
-    Change.Id id = r.getChange().getId();
-
-    try (Repository repo = repoManager.openRepository(project)) {
-      RefUpdate u = repo.updateRef(new PatchSet.Id(id, 1).toRefName());
-      u.setForceUpdate(true);
-      assertThat(u.delete()).isEqualTo(RefUpdate.Result.FORCED);
-    }
-
-    ChangeBundle reviewDbBundle;
-    try (ReviewDb db = schemaFactory.open()) {
-      reviewDbBundle = changeBundleReader.fromReviewDb(db, id);
-    }
-
-    migrate(b -> b);
-    assertNotesMigrationState(NOTE_DB, false, false);
-
-    try (ReviewDb db = schemaFactory.open();
-        Repository repo = repoManager.openRepository(project)) {
-      // Change migrated successfully even though it was missing patch set refs.
-      assertThat(repo.exactRef(RefNames.changeMetaRef(id))).isNotNull();
-      assertThat(db.changes().get(id).getNoteDbState()).isEqualTo(NOTE_DB_PRIMARY_STATE);
-
-      ChangeBundle noteDbBundle =
-          ChangeBundle.fromNotes(commentsUtil, notesFactory.createChecked(db, project, id));
-      assertThat(noteDbBundle.differencesFrom(reviewDbBundle)).isEmpty();
-    }
-  }
-
-  @Test
-  public void autoMigrationConfig() throws Exception {
-    createChange();
-
-    migrate(b -> b.setStopAtStateForTesting(WRITE));
-    assertNotesMigrationState(WRITE, false, false);
-
-    migrate(b -> b.setAutoMigrate(true).setStopAtStateForTesting(READ_WRITE_NO_SEQUENCE));
-    assertNotesMigrationState(READ_WRITE_NO_SEQUENCE, true, false);
-
-    migrate(b -> b);
-    assertNotesMigrationState(NOTE_DB, false, false);
-  }
-
-  @Test
-  public void notesMigrationStateListener() throws Exception {
-    NotesMigrationStateListener listener = createStrictMock(NotesMigrationStateListener.class);
-    listener.preStateChange(REVIEW_DB, WRITE);
-    expectLastCall();
-    listener.preStateChange(WRITE, READ_WRITE_NO_SEQUENCE);
-    expectLastCall();
-    listener.preStateChange(READ_WRITE_NO_SEQUENCE, READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY);
-    expectLastCall();
-    listener.preStateChange(
-        READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY, READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY);
-    listener.preStateChange(READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY, NOTE_DB);
-    expectLastCall();
-    replay(listener);
-    addListener(listener);
-
-    createChange();
-    migrate(b -> b);
-    assertNotesMigrationState(NOTE_DB, false, false);
-    verify(listener);
-  }
-
-  @Test
-  public void notesMigrationStateListenerFails() throws Exception {
-    NotesMigrationStateListener listener = createStrictMock(NotesMigrationStateListener.class);
-    listener.preStateChange(REVIEW_DB, WRITE);
-    expectLastCall();
-    listener.preStateChange(WRITE, READ_WRITE_NO_SEQUENCE);
-    IOException listenerException = new IOException("Listener failed");
-    expectLastCall().andThrow(listenerException);
-    replay(listener);
-    addListener(listener);
-
-    createChange();
-    try {
-      migrate(b -> b);
-      fail("expected IOException");
-    } catch (IOException e) {
-      assertThat(e).isSameAs(listenerException);
-    }
-    assertNotesMigrationState(WRITE, false, false);
-    verify(listener);
-  }
-
-  private void assertNotesMigrationState(
-      NotesMigrationState expected, boolean autoMigrate, boolean trialMode) throws Exception {
-    assertThat(NotesMigrationState.forNotesMigration(notesMigration)).hasValue(expected);
-    noteDbConfig.load();
-    assertThat(NotesMigrationState.forConfig(noteDbConfig)).hasValue(expected);
-    assertThat(NoteDbMigrator.getAutoMigrate(noteDbConfig))
-        .named("noteDb.changes.autoMigrate")
-        .isEqualTo(autoMigrate);
-    assertThat(NoteDbMigrator.getTrialMode(noteDbConfig))
-        .named("noteDb.changes.trial")
-        .isEqualTo(trialMode);
-  }
-
-  private void setNotesMigrationState(NotesMigrationState state) throws Exception {
-    noteDbConfig.load();
-    state.setConfigValues(noteDbConfig);
-    noteDbConfig.save();
-    notesMigration.setFrom(state);
-  }
-
-  @FunctionalInterface
-  interface PrepareBuilder {
-    NoteDbMigrator.Builder prepare(NoteDbMigrator.Builder b) throws Exception;
-  }
-
-  @FunctionalInterface
-  interface RunMigration {
-    void run(NoteDbMigrator m) throws Exception;
-  }
-
-  private void migrate(PrepareBuilder b) throws Exception {
-    migrate(b, NoteDbMigrator::migrate);
-  }
-
-  private void migrate(PrepareBuilder b, RunMigration m) throws Exception {
-    try (NoteDbMigrator migrator = b.prepare(migratorBuilderProvider.get()).build()) {
-      m.run(migrator);
-    }
-  }
-
-  private void assertMigrationException(
-      String expectMessageContains, PrepareBuilder b, RunMigration m) throws Exception {
-    try {
-      migrate(b, m);
-      fail("expected MigrationException");
-    } catch (MigrationException e) {
-      assertThat(e).hasMessageThat().contains(expectMessageContains);
-    }
-  }
-
-  private void addListener(NotesMigrationStateListener listener) {
-    addedListeners.add(listeners.add("gerrit", listener));
-  }
-
-  private ImmutableSortedSet<String> getObjectFiles(Project.NameKey project) throws Exception {
-    try (Repository repo = repoManager.openRepository(project);
-        Stream<Path> paths =
-            Files.walk(((FileRepository) repo).getObjectDatabase().getDirectory().toPath())) {
-      return paths
-          .filter(path -> !Files.isDirectory(path))
-          .map(Path::toString)
-          .filter(name -> !name.endsWith(".pack") && !name.endsWith(".idx"))
-          .collect(toImmutableSortedSet(naturalOrder()));
-    }
-  }
-}
diff --git a/javatests/com/google/gerrit/acceptance/server/permissions/PermissionBackendConditionIT.java b/javatests/com/google/gerrit/acceptance/server/permissions/PermissionBackendConditionIT.java
index 720eeed..a72cd33 100644
--- a/javatests/com/google/gerrit/acceptance/server/permissions/PermissionBackendConditionIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/permissions/PermissionBackendConditionIT.java
@@ -18,6 +18,7 @@
 import static org.junit.Assert.assertNotEquals;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
@@ -34,6 +35,7 @@
 public class PermissionBackendConditionIT extends AbstractDaemonTest {
 
   @Inject PermissionBackend pb;
+  @Inject ProjectOperations projectOperations;
 
   @Test
   public void globalPermissions_sameUserAndPermissionEquals() throws Exception {
@@ -110,7 +112,7 @@
 
   @Test
   public void projectPermissions_differentResourceSameUserDoesNotEqual() throws Exception {
-    Project.NameKey project2 = createProject("p2");
+    Project.NameKey project2 = projectOperations.newProject().create();
     BooleanCondition cond1 = pb.user(user()).project(project).testCond(ProjectPermission.READ);
     BooleanCondition cond2 = pb.user(user()).project(project2).testCond(ProjectPermission.READ);
 
@@ -152,7 +154,7 @@
   @Test
   public void refPermissions_differentResourceAndSameUserDoesNotEqual2() throws Exception {
     Branch.NameKey branch1 = new Branch.NameKey(project, "branch");
-    Branch.NameKey branch2 = new Branch.NameKey(createProject("p2"), "branch");
+    Branch.NameKey branch2 = new Branch.NameKey(projectOperations.newProject().create(), "branch");
     BooleanCondition cond1 = pb.user(user()).ref(branch1).testCond(RefPermission.READ);
     BooleanCondition cond2 = pb.user(user()).ref(branch2).testCond(RefPermission.READ);
 
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index cfdd781..f9493fa 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.StarsInput;
@@ -32,6 +33,7 @@
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
 import com.google.gerrit.server.git.NotifyConfig;
 import com.google.gerrit.testing.FakeEmailSender.Message;
+import com.google.inject.Inject;
 import java.util.EnumSet;
 import java.util.List;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
@@ -40,6 +42,8 @@
 
 @NoHttpd
 public class ProjectWatchIT extends AbstractDaemonTest {
+  @Inject private ProjectOperations projectOperations;
+
   @Test
   public void newPatchSetsNotifyConfig() throws Exception {
     Address addr = new Address("Watcher", "watcher@example.com");
@@ -210,7 +214,7 @@
   @Test
   public void watchProject() throws Exception {
     // watch project
-    String watchedProject = createProject("watchedProject").get();
+    String watchedProject = projectOperations.newProject().create().get();
     setApiUser(user);
     watch(watchedProject);
 
@@ -226,7 +230,7 @@
 
     // push a change to non-watched project -> should not trigger email
     // notification
-    String notWatchedProject = createProject("otherProject").get();
+    String notWatchedProject = projectOperations.newProject().create().get();
     TestRepository<InMemoryRepository> notWatchedRepo =
         cloneProject(new Project.NameKey(notWatchedProject), admin);
     r =
@@ -246,8 +250,8 @@
 
   @Test
   public void watchFile() throws Exception {
-    String watchedProject = createProject("watchedProject").get();
-    String otherWatchedProject = createProject("otherWatchedProject").get();
+    String watchedProject = projectOperations.newProject().create().get();
+    String otherWatchedProject = projectOperations.newProject().create().get();
     setApiUser(user);
 
     // watch file in project as user
@@ -300,7 +304,7 @@
 
   @Test
   public void watchKeyword() throws Exception {
-    String watchedProject = createProject("watchedProject").get();
+    String watchedProject = projectOperations.newProject().create().get();
     setApiUser(user);
 
     // watch keyword in project as user
@@ -339,7 +343,7 @@
 
   @Test
   public void watchAllProjects() throws Exception {
-    String anyProject = createProject("anyProject").get();
+    String anyProject = projectOperations.newProject().create().get();
     setApiUser(user);
 
     // watch the All-Projects project to watch all projects
@@ -366,7 +370,7 @@
 
   @Test
   public void watchFileAllProjects() throws Exception {
-    String anyProject = createProject("anyProject").get();
+    String anyProject = projectOperations.newProject().create().get();
     setApiUser(user);
 
     // watch file in All-Projects project as user to watch the file in all
@@ -417,7 +421,7 @@
 
   @Test
   public void watchKeywordAllProjects() throws Exception {
-    String anyProject = createProject("anyProject").get();
+    String anyProject = projectOperations.newProject().create().get();
     setApiUser(user);
 
     // watch keyword in project as user
@@ -458,7 +462,7 @@
   @Test
   public void watchProjectNoNotificationForIgnoredChange() throws Exception {
     // watch project
-    String watchedProject = createProject("watchedProject").get();
+    String watchedProject = projectOperations.newProject().create().get();
     setApiUser(user);
     watch(watchedProject);
 
@@ -491,7 +495,7 @@
   @Test
   public void watchProjectNoNotificationForPrivateChange() throws Exception {
     // watch project
-    String watchedProject = createProject("watchedProject").get();
+    String watchedProject = projectOperations.newProject().create().get();
     setApiUser(user);
     watch(watchedProject);
 
@@ -511,7 +515,7 @@
 
   @Test
   public void watchProjectNotifyOnPrivateChange() throws Exception {
-    String watchedProject = createProject("watchedProject").get();
+    String watchedProject = projectOperations.newProject().create().get();
 
     // create group that can view all private changes
     GroupInfo groupThatCanViewPrivateChanges =
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java b/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java
index 8abb59d..04303ea 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/ReflogIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.server.project;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -40,7 +39,6 @@
 public class ReflogIT extends AbstractDaemonTest {
   @Test
   public void guessRestApiInReflog() throws Exception {
-    assume().that(notesMigration.disableChangeReviewDb()).isTrue();
     PushOneCommit.Result r = createChange();
     Change.Id id = r.getChange().getId();
 
diff --git a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
index 1e60071..4e88955 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
@@ -25,11 +25,6 @@
 public class ElasticIndexIT extends AbstractIndexTests {
 
   @ConfigSuite.Default
-  public static Config elasticsearchV2() {
-    return getConfig(ElasticVersion.V2_4);
-  }
-
-  @ConfigSuite.Config
   public static Config elasticsearchV5() {
     return getConfig(ElasticVersion.V5_6);
   }
@@ -39,6 +34,11 @@
     return getConfig(ElasticVersion.V6_5);
   }
 
+  @ConfigSuite.Config
+  public static Config elasticsearchV7() {
+    return getConfig(ElasticVersion.V7_0);
+  }
+
   @Override
   public void configureIndex(Injector injector) throws Exception {
     createAllIndexes(injector);
diff --git a/javatests/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java b/javatests/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
index 4384ab5..c23f889d 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.UseLocalDisk;
 import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.GarbageCollectionResult;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.GarbageCollection;
@@ -40,14 +41,15 @@
   @Inject private GarbageCollectionQueue gcQueue;
 
   @Inject private GcAssert gcAssert;
+  @Inject private ProjectOperations projectOperations;
 
   private Project.NameKey project2;
   private Project.NameKey project3;
 
   @Before
   public void setUp() throws Exception {
-    project2 = createProject("p2");
-    project3 = createProject("p3");
+    project2 = projectOperations.newProject().create();
+    project3 = projectOperations.newProject().create();
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
index e603413..4e9c4a4 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
@@ -17,7 +17,6 @@
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -27,8 +26,6 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.Sandboxed;
 import com.google.gerrit.acceptance.UseSsh;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.sshd.Commands;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -65,7 +62,6 @@
           "create-branch",
           "create-group",
           "create-project",
-          "gsql",
           "index",
           "query",
           "receive-pack",
@@ -80,35 +76,40 @@
           "stream-events",
           "test-submit");
 
+  private static final ImmutableList<String> EMPTY = ImmutableList.of();
   private static final ImmutableMap<String, List<String>> MASTER_COMMANDS =
-      ImmutableMap.of(
-          Commands.ROOT,
-          Streams.concat(COMMON_ROOT_COMMANDS.stream(), MASTER_ONLY_ROOT_COMMANDS.stream())
-              .sorted()
-              .collect(toImmutableList()),
-          "index",
-          ImmutableList.of(
-              "changes", "changes-in-project"), // "activate" and "start" are not included
-          "logging",
-          ImmutableList.of("ls", "set"),
-          "plugin",
-          ImmutableList.of("add", "enable", "install", "ls", "reload", "remove", "rm"),
-          "test-submit",
-          ImmutableList.of("rule", "type"));
+      ImmutableMap.<String, List<String>>builder()
+          .put("kill", EMPTY)
+          .put("ps", EMPTY)
+          // TODO(dpursehouse): Add "scp" and "suexec"
+          .put(
+              "gerrit",
+              Streams.concat(COMMON_ROOT_COMMANDS.stream(), MASTER_ONLY_ROOT_COMMANDS.stream())
+                  .sorted()
+                  .collect(toImmutableList()))
+          .put(
+              "gerrit index",
+              ImmutableList.of(
+                  "changes", "changes-in-project")) // "activate" and "start" are not included
+          .put("gerrit logging", ImmutableList.of("ls", "set"))
+          .put(
+              "gerrit plugin",
+              ImmutableList.of("add", "enable", "install", "ls", "reload", "remove", "rm"))
+          .put("gerrit test-submit", ImmutableList.of("rule", "type"))
+          .build();
 
   private static final ImmutableMap<String, List<String>> SLAVE_COMMANDS =
       ImmutableMap.of(
-          Commands.ROOT,
+          "kill",
+          EMPTY,
+          "gerrit",
           COMMON_ROOT_COMMANDS,
-          "plugin",
+          "gerrit plugin",
           ImmutableList.of("add", "enable", "install", "ls", "reload", "remove", "rm"));
 
   @Test
   @Sandboxed
   public void sshCommandCanBeExecuted() throws Exception {
-    // Access Database capability is required to run the "gerrit gsql" command
-    allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
-
     testCommandExecution(MASTER_COMMANDS);
 
     restartAsSlave();
@@ -117,22 +118,30 @@
 
   private void testCommandExecution(Map<String, List<String>> commands) throws Exception {
     for (String root : commands.keySet()) {
-      for (String command : commands.get(root)) {
-        // We can't assert that adminSshSession.hasError() is false, because using the --help
-        // option causes the usage info to be written to stderr. Instead, we assert on the
-        // content of the stderr, which will always start with "gerrit command" when the --help
-        // option is used.
-        String cmd = String.format("gerrit%s%s %s", root.isEmpty() ? "" : " ", root, command);
-        logger.atFine().log(cmd);
-        adminSshSession.exec(String.format("%s --help", cmd));
-        String response = adminSshSession.getError();
-        assertWithMessage(String.format("command %s failed: %s", command, response))
-            .that(response)
-            .startsWith(cmd);
+      List<String> cmds = commands.get(root);
+      if (cmds.isEmpty()) {
+        testCommandExecution(root);
+      } else {
+        for (String cmd : cmds) {
+          testCommandExecution(String.format("%s %s", root, cmd));
+        }
       }
     }
   }
 
+  private void testCommandExecution(String cmd) throws Exception {
+    // We can't assert that adminSshSession.hasError() is false, because using the --help
+    // option causes the usage info to be written to stderr. Instead, we assert on the
+    // content of the stderr, which will always start with "gerrit command" when the --help
+    // option is used.
+    logger.atFine().log(cmd);
+    adminSshSession.exec(String.format("%s --help", cmd));
+    String response = adminSshSession.getError();
+    assertWithMessage(String.format("command %s failed: %s", cmd, response))
+        .that(response)
+        .startsWith(cmd);
+  }
+
   @Test
   public void nonExistingCommandFails() throws Exception {
     adminSshSession.exec("gerrit non-existing-command --help");
@@ -145,12 +154,12 @@
   public void listCommands() throws Exception {
     adminSshSession.exec("gerrit --help");
     List<String> commands = parseCommandsFromGerritHelpText(adminSshSession.getError());
-    assertThat(commands).containsExactlyElementsIn(MASTER_COMMANDS.get(Commands.ROOT)).inOrder();
+    assertThat(commands).containsExactlyElementsIn(MASTER_COMMANDS.get("gerrit")).inOrder();
 
     restartAsSlave();
     adminSshSession.exec("gerrit --help");
     commands = parseCommandsFromGerritHelpText(adminSshSession.getError());
-    assertThat(commands).containsExactlyElementsIn(SLAVE_COMMANDS.get(Commands.ROOT)).inOrder();
+    assertThat(commands).containsExactlyElementsIn(SLAVE_COMMANDS.get("gerrit")).inOrder();
   }
 
   private List<String> parseCommandsFromGerritHelpText(String helpText) {
diff --git a/javatests/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java b/javatests/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java
index 93fc769..03b7143 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.acceptance.ssh;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 
 import com.google.common.base.Splitter;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -23,7 +22,6 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.UseSsh;
-import com.google.gerrit.testing.NoteDbMode;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
@@ -34,20 +32,12 @@
 import org.eclipse.jgit.transport.PacketLineIn;
 import org.eclipse.jgit.transport.PacketLineOut;
 import org.eclipse.jgit.util.IO;
-import org.junit.Before;
 import org.junit.Test;
 
 @NoHttpd
 @UseSsh
 public class UploadArchiveIT extends AbstractDaemonTest {
 
-  @Before
-  public void setUp() {
-    // There is some Guice request scoping problem preventing this test from
-    // passing in CHECK mode.
-    assume().that(NoteDbMode.get()).isNotEqualTo(NoteDbMode.CHECK);
-  }
-
   @Test
   @GerritConfig(name = "download.archive", value = "off")
   public void archiveFeatureOff() throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
index 6dc69be..3f537c0 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
@@ -47,7 +47,7 @@
 
   @Test
   public void emptyCommit() throws Exception {
-    Project.NameKey key = projectOperations.newProject().withEmptyCommit().create();
+    Project.NameKey key = projectOperations.newProject().create();
     List<BranchInfo> branches = gApi.projects().name(key.get()).branches().get();
     assertThat(branches).isNotEmpty();
     assertThat(branches.stream().map(x -> x.ref).collect(toList()))
diff --git a/javatests/com/google/gerrit/common/BUILD b/javatests/com/google/gerrit/common/BUILD
index 88ddcd5..29a23c3 100644
--- a/javatests/com/google/gerrit/common/BUILD
+++ b/javatests/com/google/gerrit/common/BUILD
@@ -2,10 +2,7 @@
 
 junit_tests(
     name = "server_tests",
-    srcs = [
-        "AutoValueTest.java",
-        "VersionTest.java",
-    ],
+    srcs = glob(["*.java"]),
     tags = ["no_windows"],
     deps = [
         "//java/com/google/gerrit/common:server",
diff --git a/javatests/com/google/gerrit/common/data/AccessSectionTest.java b/javatests/com/google/gerrit/common/data/AccessSectionTest.java
index b12a9c2..faf9d6c 100644
--- a/javatests/com/google/gerrit/common/data/AccessSectionTest.java
+++ b/javatests/com/google/gerrit/common/data/AccessSectionTest.java
@@ -152,21 +152,6 @@
   }
 
   @Test
-  public void cannotAddPermissionByModifyingListThatWasRetrievedFromAccessSection() {
-    Permission submitPermission = new Permission(Permission.SUBMIT);
-    accessSection.getPermissions().add(submitPermission);
-    assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
-
-    List<Permission> permissions = new ArrayList<>();
-    permissions.add(new Permission(Permission.ABANDON));
-    permissions.add(new Permission(Permission.REBASE));
-    accessSection.setPermissions(permissions);
-    assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
-    accessSection.getPermissions().add(submitPermission);
-    assertThat(accessSection.getPermission(Permission.SUBMIT)).isNull();
-  }
-
-  @Test
   public void removePermission() {
     Permission abandonPermission = new Permission(Permission.ABANDON);
     Permission rebasePermission = new Permission(Permission.REBASE);
diff --git a/javatests/com/google/gerrit/common/data/BUILD b/javatests/com/google/gerrit/common/data/BUILD
new file mode 100644
index 0000000..776a5e0
--- /dev/null
+++ b/javatests/com/google/gerrit/common/data/BUILD
@@ -0,0 +1,13 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+
+junit_tests(
+    name = "data_tests",
+    srcs = glob(["*.java"]),
+    deps = [
+        "//java/com/google/gerrit/common:server",
+        "//java/com/google/gerrit/reviewdb:server",
+        "//java/com/google/gerrit/testing:gerrit-test-util",
+        "//lib:guava",
+        "//lib/truth",
+    ],
+)
diff --git a/javatests/com/google/gerrit/common/data/PermissionTest.java b/javatests/com/google/gerrit/common/data/PermissionTest.java
index 84fb2f0..23380e7 100644
--- a/javatests/com/google/gerrit/common/data/PermissionTest.java
+++ b/javatests/com/google/gerrit/common/data/PermissionTest.java
@@ -187,22 +187,6 @@
   }
 
   @Test
-  public void cannotAddPermissionByModifyingListThatWasRetrievedFromAccessSection() {
-    GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid-1"), "group1");
-    PermissionRule permissionRule1 = new PermissionRule(groupReference1);
-    permission.getRules().add(permissionRule1);
-    assertThat(permission.getRule(groupReference1)).isNull();
-
-    List<PermissionRule> rules = new ArrayList<>();
-    rules.add(new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-2"), "group2")));
-    rules.add(new PermissionRule(new GroupReference(new AccountGroup.UUID("uuid-3"), "group3")));
-    permission.setRules(rules);
-    assertThat(permission.getRule(groupReference1)).isNull();
-    permission.getRules().add(permissionRule1);
-    assertThat(permission.getRule(groupReference1)).isNull();
-  }
-
-  @Test
   public void getNonExistingRule() {
     GroupReference groupReference = new GroupReference(new AccountGroup.UUID("uuid-1"), "group1");
     assertThat(permission.getRule(groupReference)).isNull();
diff --git a/javatests/com/google/gerrit/elasticsearch/BUILD b/javatests/com/google/gerrit/elasticsearch/BUILD
index 51d45f2..61bd263 100644
--- a/javatests/com/google/gerrit/elasticsearch/BUILD
+++ b/javatests/com/google/gerrit/elasticsearch/BUILD
@@ -18,6 +18,7 @@
         "//lib/httpcomponents:httpcore",
         "//lib/jgit/org.eclipse.jgit:jgit",
         "//lib/testcontainers",
+        "//lib/testcontainers:testcontainers-elasticsearch",
     ],
 )
 
@@ -40,12 +41,12 @@
 
 SUFFIX = "sTest.java"
 
-ELASTICSEARCH_TESTS = {i: "ElasticQuery" + i.capitalize() + SUFFIX for i in TYPES}
-
 ELASTICSEARCH_TESTS_V5 = {i: "ElasticV5Query" + i.capitalize() + SUFFIX for i in TYPES}
 
 ELASTICSEARCH_TESTS_V6 = {i: "ElasticV6Query" + i.capitalize() + SUFFIX for i in TYPES}
 
+ELASTICSEARCH_TESTS_V7 = {i: "ElasticV7Query" + i.capitalize() + SUFFIX for i in TYPES}
+
 ELASTICSEARCH_TAGS = [
     "docker",
     "elastic",
@@ -53,14 +54,6 @@
 ]
 
 [junit_tests(
-    name = "elasticsearch_query_%ss_test" % name,
-    size = "large",
-    srcs = [src],
-    tags = ELASTICSEARCH_TAGS,
-    deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name],
-) for name, src in ELASTICSEARCH_TESTS.items()]
-
-[junit_tests(
     name = "elasticsearch_query_%ss_test_V5" % name,
     size = "large",
     srcs = [src],
@@ -76,6 +69,17 @@
     deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name],
 ) for name, src in ELASTICSEARCH_TESTS_V6.items()]
 
+[junit_tests(
+    name = "elasticsearch_query_%ss_test_V7" % name,
+    size = "large",
+    srcs = [src],
+    tags = ELASTICSEARCH_TAGS + ["flaky"],
+    deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name] + [
+        "//lib/httpcomponents:httpasyncclient",
+        "//lib/httpcomponents:httpclient",
+    ],
+) for name, src in ELASTICSEARCH_TESTS_V7.items()]
+
 junit_tests(
     name = "elasticsearch_tests",
     size = "small",
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index c3150f1..19ea44b 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -14,21 +14,19 @@
 
 package com.google.gerrit.elasticsearch;
 
-import com.google.common.collect.ImmutableSet;
-import java.util.Set;
 import org.apache.http.HttpHost;
 import org.junit.AssumptionViolatedException;
-import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.elasticsearch.ElasticsearchContainer;
 
 /* Helper class for running ES integration tests in docker container */
-public class ElasticContainer<SELF extends ElasticContainer<SELF>> extends GenericContainer<SELF> {
+public class ElasticContainer extends ElasticsearchContainer {
   private static final int ELASTICSEARCH_DEFAULT_PORT = 9200;
 
-  public static ElasticContainer<?> createAndStart(ElasticVersion version) {
+  public static ElasticContainer createAndStart(ElasticVersion version) {
     // Assumption violation is not natively supported by Testcontainers.
     // See https://github.com/testcontainers/testcontainers-java/issues/343
     try {
-      ElasticContainer<?> container = new ElasticContainer<>(version);
+      ElasticContainer container = new ElasticContainer(version);
       container.start();
       return container;
     } catch (Throwable t) {
@@ -36,14 +34,8 @@
     }
   }
 
-  public static ElasticContainer<?> createAndStart() {
-    return createAndStart(ElasticVersion.V2_4);
-  }
-
   private static String getImageName(ElasticVersion version) {
     switch (version) {
-      case V2_4:
-        return "elasticsearch:2.4.6-alpine";
       case V5_6:
         return "docker.elastic.co/elasticsearch/elasticsearch:5.6.13";
       case V6_2:
@@ -53,7 +45,9 @@
       case V6_4:
         return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3";
       case V6_5:
-        return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.5.0";
+        return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.5.2";
+      case V7_0:
+        return "docker.elastic.co/elasticsearch/elasticsearch-oss:7.0.0-alpha1";
     }
     throw new IllegalStateException("No tests for version: " + version.name());
   }
@@ -62,19 +56,6 @@
     super(getImageName(version));
   }
 
-  @Override
-  protected void configure() {
-    addExposedPort(ELASTICSEARCH_DEFAULT_PORT);
-
-    // https://github.com/docker-library/elasticsearch/issues/58
-    addEnv("-Ees.network.host", "0.0.0.0");
-  }
-
-  @Override
-  public Set<Integer> getLivenessCheckPortNumbers() {
-    return ImmutableSet.of(getMappedPort(ELASTICSEARCH_DEFAULT_PORT));
-  }
-
   public HttpHost getHttpHost() {
     return new HttpHost(getContainerIpAddress(), getMappedPort(ELASTICSEARCH_DEFAULT_PORT));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
index 9f7b60c..020a158 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -58,7 +58,7 @@
 
   public static Config getConfig(ElasticVersion version) {
     ElasticNodeInfo elasticNodeInfo;
-    ElasticContainer<?> container = ElasticContainer.createAndStart(version);
+    ElasticContainer container = ElasticContainer.createAndStart(version);
     elasticNodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
     String indicesPrefix = UUID.randomUUID().toString();
     Config cfg = new Config();
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryAccountsTest.java
index 5d2f944..c8ce54a 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryAccountsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(
         elasticsearchConfig, nodeInfo.port, indicesPrefix, ElasticVersion.V5_6);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryChangesTest.java
index 5d76162..cfdfa98 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryChangesTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(
         elasticsearchConfig, nodeInfo.port, indicesPrefix, ElasticVersion.V5_6);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryGroupsTest.java
index 9ce2e93..832a7bd 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryGroupsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(
         elasticsearchConfig, nodeInfo.port, indicesPrefix, ElasticVersion.V5_6);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryProjectsTest.java
index 4184935..29d3fa4 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryProjectsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryProjectsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(
         elasticsearchConfig, nodeInfo.port, indicesPrefix, ElasticVersion.V5_6);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
index eeb4c09..8833907 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
index 7525b65..8ba753c 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
index e8d5683..cecb085 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
index eaaf0c8..47e9b10 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
similarity index 85%
rename from javatests/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
index 4f0f8b0..bddbbc9 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 The Android Open Source Project
+// Copyright (C) 2018 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.
@@ -25,14 +25,14 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-public class ElasticQueryAccountsTest extends AbstractQueryAccountsTest {
+public class ElasticV7QueryAccountsTest extends AbstractQueryAccountsTest {
   @ConfigSuite.Default
   public static Config defaultConfig() {
     return IndexConfig.createForElasticsearch();
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -41,7 +41,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart();
+    container = ElasticContainer.createAndStart(ElasticVersion.V7_0);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
similarity index 69%
rename from javatests/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
index a02d691..5dcf159 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 The Android Open Source Project
+// Copyright (C) 2018 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.
@@ -21,18 +21,24 @@
 import com.google.gerrit.testing.IndexConfig;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClients;
 import org.eclipse.jgit.lib.Config;
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-public class ElasticQueryChangesTest extends AbstractQueryChangesTest {
+public class ElasticV7QueryChangesTest extends AbstractQueryChangesTest {
   @ConfigSuite.Default
   public static Config defaultConfig() {
     return IndexConfig.createForElasticsearch();
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
+  private static CloseableHttpAsyncClient client;
 
   @BeforeClass
   public static void startIndexService() {
@@ -41,8 +47,10 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart();
+    container = ElasticContainer.createAndStart(ElasticVersion.V7_0);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
+    client = HttpAsyncClients.createDefault();
+    client.start();
   }
 
   @AfterClass
@@ -52,8 +60,14 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
+  @After
+  public void closeIndex() {
+    client.execute(
+        new HttpPost(
+            String.format(
+                "http://localhost:%d/%s*/_close", nodeInfo.port, getSanitizedMethodName())),
+        HttpClientContext.create(),
+        null);
   }
 
   @Override
@@ -66,7 +80,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
similarity index 85%
rename from javatests/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
index f13c491..54be7b9 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2018 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.
@@ -25,14 +25,14 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-public class ElasticQueryGroupsTest extends AbstractQueryGroupsTest {
+public class ElasticV7QueryGroupsTest extends AbstractQueryGroupsTest {
   @ConfigSuite.Default
   public static Config defaultConfig() {
     return IndexConfig.createForElasticsearch();
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -41,7 +41,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart();
+    container = ElasticContainer.createAndStart(ElasticVersion.V7_0);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
similarity index 85%
rename from javatests/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
index dd04010..e8b4a2c 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2018 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.
@@ -25,14 +25,14 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-public class ElasticQueryProjectsTest extends AbstractQueryProjectsTest {
+public class ElasticV7QueryProjectsTest extends AbstractQueryProjectsTest {
   @ConfigSuite.Default
   public static Config defaultConfig() {
     return IndexConfig.createForElasticsearch();
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -41,7 +41,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart();
+    container = ElasticContainer.createAndStart(ElasticVersion.V7_0);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index 3ab9d5a..baf6c2b 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -22,9 +22,6 @@
 public class ElasticVersionTest extends GerritBaseTests {
   @Test
   public void supportedVersion() throws Exception {
-    assertThat(ElasticVersion.forVersion("2.4.0")).isEqualTo(ElasticVersion.V2_4);
-    assertThat(ElasticVersion.forVersion("2.4.6")).isEqualTo(ElasticVersion.V2_4);
-
     assertThat(ElasticVersion.forVersion("5.6.0")).isEqualTo(ElasticVersion.V5_6);
     assertThat(ElasticVersion.forVersion("5.6.11")).isEqualTo(ElasticVersion.V5_6);
 
@@ -36,6 +33,12 @@
 
     assertThat(ElasticVersion.forVersion("6.4.0")).isEqualTo(ElasticVersion.V6_4);
     assertThat(ElasticVersion.forVersion("6.4.1")).isEqualTo(ElasticVersion.V6_4);
+
+    assertThat(ElasticVersion.forVersion("6.5.0")).isEqualTo(ElasticVersion.V6_5);
+    assertThat(ElasticVersion.forVersion("6.5.1")).isEqualTo(ElasticVersion.V6_5);
+
+    assertThat(ElasticVersion.forVersion("7.0.0")).isEqualTo(ElasticVersion.V7_0);
+    assertThat(ElasticVersion.forVersion("7.0.1")).isEqualTo(ElasticVersion.V7_0);
   }
 
   @Test
@@ -48,9 +51,19 @@
 
   @Test
   public void version6() throws Exception {
-    assertThat(ElasticVersion.V6_2.isV6()).isTrue();
-    assertThat(ElasticVersion.V6_3.isV6()).isTrue();
-    assertThat(ElasticVersion.V6_4.isV6()).isTrue();
-    assertThat(ElasticVersion.V5_6.isV6()).isFalse();
+    assertThat(ElasticVersion.V5_6.isV6OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_2.isV6OrLater()).isTrue();
+    assertThat(ElasticVersion.V6_3.isV6OrLater()).isTrue();
+    assertThat(ElasticVersion.V6_4.isV6OrLater()).isTrue();
+    assertThat(ElasticVersion.V7_0.isV6OrLater()).isTrue();
+  }
+
+  @Test
+  public void version7() throws Exception {
+    assertThat(ElasticVersion.V5_6.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_2.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_3.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_4.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V7_0.isV7OrLater()).isTrue();
   }
 }
diff --git a/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java b/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
index b90ccf7c..d5add7d 100644
--- a/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
+++ b/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
@@ -41,7 +41,7 @@
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.testing.GerritBaseTests;
@@ -81,7 +81,7 @@
 
   @Inject private InMemoryDatabase schemaFactory;
 
-  @Inject private ReviewDbSchemaCreator schemaCreator;
+  @Inject private SchemaCreator schemaCreator;
 
   @Inject private ThreadLocalRequestContext requestContext;
 
@@ -112,7 +112,7 @@
     lifecycle.start();
 
     db = schemaFactory.open();
-    schemaCreator.create(db);
+    schemaCreator.create();
     userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
     // Note: does not match any key in TestKeys.
     accountsUpdateProvider
@@ -162,7 +162,6 @@
     if (db != null) {
       db.close();
     }
-    InMemoryDatabase.drop(schemaFactory);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java b/javatests/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
deleted file mode 100644
index 159ff0d..0000000
--- a/javatests/com/google/gerrit/pgm/init/UpgradeFrom2_0_xTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (C) 2009 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.pgm.init;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.easymock.EasyMock.createStrictMock;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import com.google.common.io.ByteStreams;
-import com.google.gerrit.common.FileUtil;
-import com.google.gerrit.pgm.init.api.ConsoleUI;
-import com.google.gerrit.pgm.init.api.InitFlags;
-import com.google.gerrit.pgm.init.api.Section;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.securestore.testing.InMemorySecureStore;
-import com.google.gerrit.testing.GerritBaseTests;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Collections;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-public class UpgradeFrom2_0_xTest extends GerritBaseTests {
-  @Rule public TemporaryFolder folder = new TemporaryFolder();
-
-  @Test
-  public void upgrade() throws IOException, ConfigInvalidException {
-    final Path p = folder.newFolder().toPath();
-    final SitePaths site = new SitePaths(p);
-    assertTrue(site.isNew);
-    FileUtil.mkdirsOrDie(site.etc_dir, "Failed to create");
-
-    for (String n : UpgradeFrom2_0_x.etcFiles) {
-      Files.write(p.resolve(n), ("# " + n + "\n").getBytes(UTF_8));
-    }
-
-    FileBasedConfig old = new FileBasedConfig(p.resolve("gerrit.config").toFile(), FS.DETECTED);
-
-    old.setString("ldap", null, "username", "ldap.user");
-    old.setString("ldap", null, "password", "ldap.s3kr3t");
-
-    old.setString("sendemail", null, "smtpUser", "email.user");
-    old.setString("sendemail", null, "smtpPass", "email.s3kr3t");
-    old.save();
-
-    final InMemorySecureStore secureStore = new InMemorySecureStore();
-    final InitFlags flags =
-        new InitFlags(site, secureStore, Collections.<String>emptyList(), false);
-    final ConsoleUI ui = createStrictMock(ConsoleUI.class);
-    Section.Factory sections =
-        new Section.Factory() {
-          @Override
-          public Section get(String name, String subsection) {
-            return new Section(flags, site, secureStore, ui, name, subsection);
-          }
-        };
-
-    expect(ui.yesno(eq(true), eq("Upgrade '%s'"), eq(p.toAbsolutePath().normalize())))
-        .andReturn(true);
-    replay(ui);
-
-    UpgradeFrom2_0_x u = new UpgradeFrom2_0_x(site, flags, ui, sections);
-    assertTrue(u.isNeedUpgrade());
-    u.run();
-    assertFalse(u.isNeedUpgrade());
-    verify(ui);
-
-    for (String n : UpgradeFrom2_0_x.etcFiles) {
-      if ("gerrit.config".equals(n) || "secure.config".equals(n)) {
-        continue;
-      }
-      try (InputStream in = Files.newInputStream(site.etc_dir.resolve(n))) {
-        assertEquals("# " + n + "\n", new String(ByteStreams.toByteArray(in), UTF_8));
-      }
-    }
-
-    FileBasedConfig cfg = new FileBasedConfig(site.gerrit_config.toFile(), FS.DETECTED);
-    cfg.load();
-
-    assertEquals("email.user", cfg.getString("sendemail", null, "smtpUser"));
-    assertNull(cfg.getString("sendemail", null, "smtpPass"));
-    assertEquals("email.s3kr3t", secureStore.get("sendemail", null, "smtpPass"));
-
-    assertEquals("ldap.user", cfg.getString("ldap", null, "username"));
-    assertNull(cfg.getString("ldap", null, "password"));
-    assertEquals("ldap.s3kr3t", secureStore.get("ldap", null, "password"));
-
-    u.run();
-  }
-}
diff --git a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
index fe980f9..cc15b1b 100644
--- a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
+++ b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
@@ -44,7 +44,7 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.util.time.TimeUtil;
@@ -72,7 +72,7 @@
   @Inject private LabelNormalizer norm;
   @Inject private MetaDataUpdate.User metaDataUpdateFactory;
   @Inject private ProjectCache projectCache;
-  @Inject private ReviewDbSchemaCreator schemaCreator;
+  @Inject private SchemaCreator schemaCreator;
   @Inject protected ThreadLocalRequestContext requestContext;
   @Inject private ChangeNotes.Factory changeNotesFactory;
   @Inject private ProjectConfig.Factory projectConfigFactory;
@@ -94,7 +94,7 @@
     lifecycle.start();
 
     db = schemaFactory.open();
-    schemaCreator.create(db);
+    schemaCreator.create();
     userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
     user = userFactory.create(userId);
 
@@ -148,7 +148,6 @@
     if (db != null) {
       db.close();
     }
-    InMemoryDatabase.drop(schemaFactory);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
index d00f96b..ed4aacb 100644
--- a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -137,7 +137,7 @@
                 install(new GitModule());
 
                 install(new DefaultUrlFormatter.Module());
-                install(NoteDbModule.forTest(testConfig));
+                install(NoteDbModule.forTest());
                 bind(AllUsersName.class).toProvider(AllUsersNameProvider.class);
                 bind(String.class).annotatedWith(GerritServerId.class).toInstance("gerrit");
                 bind(GitRepositoryManager.class).toInstance(repoManager);
@@ -172,11 +172,6 @@
                         () -> {
                           throw new UnsupportedOperationException();
                         });
-                bind(ChangeBundleReader.class)
-                    .toInstance(
-                        (db, id) -> {
-                          throw new UnsupportedOperationException();
-                        });
               }
             });
 
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeBundleTest.java b/javatests/com/google/gerrit/server/notedb/ChangeBundleTest.java
deleted file mode 100644
index fc2a272..0000000
--- a/javatests/com/google/gerrit/server/notedb/ChangeBundleTest.java
+++ /dev/null
@@ -1,1976 +0,0 @@
-// Copyright (C) 2016 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.notedb;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.server.notedb.ChangeBundle.Source.NOTE_DB;
-import static com.google.gerrit.server.notedb.ChangeBundle.Source.REVIEW_DB;
-import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
-import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
-import static com.google.gerrit.server.util.time.TimeUtil.truncateToSecond;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Table;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-import com.google.gerrit.reviewdb.client.LabelId;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.server.ReviewerSet;
-import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
-import com.google.gerrit.testing.TestChanges;
-import com.google.gerrit.testing.TestTimeUtil;
-import com.google.gwtorm.protobuf.CodecFactory;
-import com.google.gwtorm.protobuf.ProtobufCodec;
-import java.sql.Timestamp;
-import java.time.LocalDate;
-import java.time.Month;
-import java.time.ZoneId;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.TimeZone;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class ChangeBundleTest extends GerritBaseTests {
-  private static final ProtobufCodec<Change> CHANGE_CODEC = CodecFactory.encoder(Change.class);
-  private static final ProtobufCodec<ChangeMessage> CHANGE_MESSAGE_CODEC =
-      CodecFactory.encoder(ChangeMessage.class);
-  private static final ProtobufCodec<PatchSet> PATCH_SET_CODEC =
-      CodecFactory.encoder(PatchSet.class);
-  private static final ProtobufCodec<PatchSetApproval> PATCH_SET_APPROVAL_CODEC =
-      CodecFactory.encoder(PatchSetApproval.class);
-  private static final ProtobufCodec<PatchLineComment> PATCH_LINE_COMMENT_CODEC =
-      CodecFactory.encoder(PatchLineComment.class);
-  private static final String TIMEZONE_ID = "US/Eastern";
-
-  private String systemTimeZoneProperty;
-  private TimeZone systemTimeZone;
-
-  private Project.NameKey project;
-  private Account.Id accountId;
-
-  @Before
-  public void setUp() {
-    systemTimeZoneProperty = System.setProperty("user.timezone", TIMEZONE_ID);
-    systemTimeZone = TimeZone.getDefault();
-    TimeZone.setDefault(TimeZone.getTimeZone(TIMEZONE_ID));
-    long maxMs = ChangeRebuilderImpl.MAX_WINDOW_MS;
-    assertThat(maxMs).isGreaterThan(1000L);
-    TestTimeUtil.resetWithClockStep(maxMs * 2, MILLISECONDS);
-    project = new Project.NameKey("project");
-    accountId = new Account.Id(100);
-  }
-
-  @After
-  public void tearDown() {
-    TestTimeUtil.useSystemTime();
-    System.setProperty("user.timezone", systemTimeZoneProperty);
-    TimeZone.setDefault(systemTimeZone);
-  }
-
-  private void superWindowResolution() {
-    TestTimeUtil.setClockStep(ChangeRebuilderImpl.MAX_WINDOW_MS * 2, MILLISECONDS);
-    TimeUtil.nowTs();
-  }
-
-  private void subWindowResolution() {
-    TestTimeUtil.setClockStep(1, SECONDS);
-    TimeUtil.nowTs();
-  }
-
-  @Test
-  public void diffChangesDifferentIds() throws Exception {
-    Change c1 = TestChanges.newChange(project, accountId);
-    int id1 = c1.getId().get();
-    Change c2 = TestChanges.newChange(project, accountId);
-    int id2 = c2.getId().get();
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-
-    assertDiffs(
-        b1,
-        b2,
-        "changeId differs for Changes: {" + id1 + "} != {" + id2 + "}",
-        "createdOn differs for Changes: {2009-09-30 17:00:00.0} != {2009-09-30 17:00:06.0}",
-        "effective last updated time differs for Changes:"
-            + " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:06.0}");
-  }
-
-  @Test
-  public void diffChangesSameId() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    Change c2 = clone(c1);
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-
-    assertNoDiffs(b1, b2);
-
-    c2.setTopic("topic");
-    assertDiffs(b1, b2, "topic differs for Change.Id " + c1.getId() + ": {null} != {topic}");
-  }
-
-  @Test
-  public void diffChangesMixedSourcesAllowsSlop() throws Exception {
-    subWindowResolution();
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    Change c2 = clone(c1);
-    c2.setCreatedOn(TimeUtil.nowTs());
-    c2.setLastUpdatedOn(TimeUtil.nowTs());
-
-    // Both are ReviewDb, exact timestamp match is required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "createdOn differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {2009-09-30 17:00:01.0} != {2009-09-30 17:00:02.0}",
-        "effective last updated time differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {2009-09-30 17:00:01.0} != {2009-09-30 17:00:03.0}");
-
-    // One NoteDb, slop is allowed.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-
-    // But not too much slop.
-    superWindowResolution();
-    Change c3 = clone(c1);
-    c3.setLastUpdatedOn(TimeUtil.nowTs());
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    ChangeBundle b3 =
-        new ChangeBundle(
-            c3, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    String msg =
-        "effective last updated time differs for Change.Id "
-            + c1.getId()
-            + " in NoteDb vs. ReviewDb:"
-            + " {2009-09-30 17:00:01.0} != {2009-09-30 17:00:10.0}";
-    assertDiffs(b1, b3, msg);
-    assertDiffs(b3, b1, msg);
-  }
-
-  @Test
-  public void diffChangesIgnoresOriginalSubjectInReviewDb() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    c1.setCurrentPatchSet(c1.currentPatchSetId(), "Subject", "Original A");
-    Change c2 = clone(c1);
-    c2.setCurrentPatchSet(c2.currentPatchSetId(), c1.getSubject(), "Original B");
-
-    // Both ReviewDb, exact match required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "originalSubject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Original A} != {Original B}");
-
-    // Both NoteDb, exact match required.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "originalSubject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Original A} != {Original B}");
-
-    // One ReviewDb, one NoteDb, original subject is ignored.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-  }
-
-  @Test
-  public void diffChangesSanitizesSubjectsBeforeComparison() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    c1.setCurrentPatchSet(c1.currentPatchSetId(), "Subject\r\rbody", "Original");
-    Change c2 = clone(c1);
-    c2.setCurrentPatchSet(c2.currentPatchSetId(), "Subject  body", "Original");
-
-    // Both ReviewDb, exact match required
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "subject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Subject\r\rbody} != {Subject  body}");
-
-    // Both NoteDb, exact match required (although it should be impossible to
-    // create a NoteDb change with '\r' in the subject).
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "subject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Subject\r\rbody} != {Subject  body}");
-
-    // One ReviewDb, one NoteDb, '\r' is normalized to ' '.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-  }
-
-  @Test
-  public void diffChangesConsidersEmptyReviewDbTopicEquivalentToNullInNoteDb() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    c1.setTopic("");
-    Change c2 = clone(c1);
-    c2.setTopic(null);
-
-    // Both ReviewDb, exact match required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(b1, b2, "topic differs for Change.Id " + c1.getId() + ": {} != {null}");
-
-    // Topic ignored if ReviewDb is empty and NoteDb is null.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-
-    // Exact match still required if NoteDb has empty value (not realistic).
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(b1, b2, "topic differs for Change.Id " + c1.getId() + ": {} != {null}");
-
-    // Null is not equal to a non-empty string.
-    Change c3 = clone(c1);
-    c3.setTopic("topic");
-    b1 =
-        new ChangeBundle(
-            c3, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertDiffs(b1, b2, "topic differs for Change.Id " + c1.getId() + ": {topic} != {null}");
-
-    // Null is equal to a string that is all whitespace.
-    Change c4 = clone(c1);
-    c4.setTopic("  ");
-    b1 =
-        new ChangeBundle(
-            c4, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-  }
-
-  @Test
-  public void diffChangesIgnoresLeadingAndTrailingWhitespaceInReviewDbTopics() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    c1.setTopic(" abc ");
-    Change c2 = clone(c1);
-    c2.setTopic("abc");
-
-    // Both ReviewDb, exact match required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(b1, b2, "topic differs for Change.Id " + c1.getId() + ": { abc } != {abc}");
-
-    // Leading whitespace in ReviewDb topic is ignored.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-
-    // Must match except for the leading/trailing whitespace.
-    Change c3 = clone(c1);
-    c3.setTopic("cba");
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c3, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertDiffs(b1, b2, "topic differs for Change.Id " + c1.getId() + ": { abc } != {cba}");
-  }
-
-  @Test
-  public void diffChangesTakesMaxEntityTimestampFromReviewDb() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    PatchSet ps = new PatchSet(c1.currentPatchSetId());
-    ps.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    ps.setUploader(accountId);
-    ps.setCreatedOn(TimeUtil.nowTs());
-    PatchSetApproval a =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(c1.currentPatchSetId(), accountId, new LabelId("Code-Review")),
-            (short) 1,
-            TimeUtil.nowTs());
-
-    Change c2 = clone(c1);
-    c2.setLastUpdatedOn(a.getGranted());
-
-    // Both ReviewDb, exact match required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(ps), approvals(a), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(ps), approvals(a), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "effective last updated time differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:12.0}");
-
-    // NoteDb allows latest timestamp from all entities in bundle.
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(ps), approvals(a), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-  }
-
-  @Test
-  public void diffChangesIgnoresChangeTimestampIfAnyOtherEntitiesExist() {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    PatchSet ps = new PatchSet(c1.currentPatchSetId());
-    ps.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    ps.setUploader(accountId);
-    ps.setCreatedOn(TimeUtil.nowTs());
-    PatchSetApproval a =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(c1.currentPatchSetId(), accountId, new LabelId("Code-Review")),
-            (short) 1,
-            TimeUtil.nowTs());
-    c1.setLastUpdatedOn(a.getGranted());
-
-    Change c2 = clone(c1);
-    c2.setLastUpdatedOn(TimeUtil.nowTs());
-
-    // ReviewDb has later lastUpdatedOn timestamp than NoteDb, allowed since
-    // NoteDb matches the latest timestamp of a non-Change entity.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c2, messages(), patchSets(ps), approvals(a), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c1, messages(), patchSets(ps), approvals(a), comments(), reviewers(), NOTE_DB);
-    assertThat(b1.getChange().getLastUpdatedOn()).isGreaterThan(b2.getChange().getLastUpdatedOn());
-    assertNoDiffs(b1, b2);
-
-    // Timestamps must actually match if Change is the only entity.
-    b1 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "effective last updated time differs for Change.Id "
-            + c1.getId()
-            + " in NoteDb vs. ReviewDb:"
-            + " {2009-09-30 17:00:12.0} != {2009-09-30 17:00:18.0}");
-  }
-
-  @Test
-  public void diffChangesAllowsReviewDbSubjectToBePrefixOfNoteDbSubject() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    Change c2 = clone(c1);
-    c2.setCurrentPatchSet(
-        c1.currentPatchSetId(), c1.getSubject().substring(0, 10), c1.getOriginalSubject());
-    assertThat(c2.getSubject()).isNotEqualTo(c1.getSubject());
-
-    // Both ReviewDb, exact match required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "subject differs for Change.Id " + c1.getId() + ": {Change subject} != {Change sub}");
-
-    // ReviewDb has shorter subject, allowed.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-
-    // NoteDb has shorter subject, not allowed.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), latest(c1), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(c2, messages(), latest(c2), approvals(), comments(), reviewers(), NOTE_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "subject differs for Change.Id " + c1.getId() + ": {Change subject} != {Change sub}");
-  }
-
-  @Test
-  public void diffChangesTrimsLeadingSpacesFromReviewDbComparingToNoteDb() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    Change c2 = clone(c1);
-    c2.setCurrentPatchSet(c1.currentPatchSetId(), "   " + c1.getSubject(), c1.getOriginalSubject());
-
-    // Both ReviewDb, exact match required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "subject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Change subject} != {   Change subject}");
-
-    // ReviewDb is missing leading spaces, allowed.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-  }
-
-  @Test
-  public void diffChangesDoesntTrimLeadingNonSpaceWhitespaceFromSubject() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    Change c2 = clone(c1);
-    c2.setCurrentPatchSet(c1.currentPatchSetId(), "\t" + c1.getSubject(), c1.getOriginalSubject());
-
-    // Both ReviewDb.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "subject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Change subject} != {\tChange subject}");
-
-    // One NoteDb.
-    b1 =
-        new ChangeBundle(c1, messages(), latest(c1), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), latest(c2), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "subject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Change subject} != {\tChange subject}");
-    assertDiffs(
-        b2,
-        b1,
-        "subject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {\tChange subject} != {Change subject}");
-  }
-
-  @Test
-  public void diffChangesHandlesBuggyJGitSubjectExtraction() throws Exception {
-    Change c1 = TestChanges.newChange(project, accountId);
-    String buggySubject = "Subject\r \r Rest of message.";
-    c1.setCurrentPatchSet(c1.currentPatchSetId(), buggySubject, buggySubject);
-    Change c2 = clone(c1);
-    c2.setCurrentPatchSet(c2.currentPatchSetId(), "Subject", "Subject");
-
-    // Both ReviewDb.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "originalSubject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Subject\r \r Rest of message.} != {Subject}",
-        "subject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Subject\r \r Rest of message.} != {Subject}");
-
-    // NoteDb has correct subject without "\r ".
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-  }
-
-  @Test
-  public void diffChangesIgnoresInvalidCurrentPatchSetIdInReviewDb() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    Change c2 = clone(c1);
-    c2.setCurrentPatchSet(
-        new PatchSet.Id(c2.getId(), 0), "Unrelated subject", c2.getOriginalSubject());
-
-    // Both ReviewDb.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "currentPatchSetId differs for Change.Id " + c1.getId() + ": {1} != {0}",
-        "subject differs for Change.Id "
-            + c1.getId()
-            + ":"
-            + " {Change subject} != {Unrelated subject}");
-
-    // One NoteDb.
-    //
-    // This is based on a real corrupt change where all patch sets were deleted
-    // but the Change entity stuck around, resulting in a currentPatchSetId of 0
-    // after converting to NoteDb.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-  }
-
-  @Test
-  public void diffChangesAllowsCreatedToMatchLastUpdated() throws Exception {
-    Change c1 = TestChanges.newChange(new Project.NameKey("project"), new Account.Id(100));
-    c1.setCreatedOn(TimeUtil.nowTs());
-    assertThat(c1.getCreatedOn()).isGreaterThan(c1.getLastUpdatedOn());
-    Change c2 = clone(c1);
-    c2.setCreatedOn(c2.getLastUpdatedOn());
-
-    // Both ReviewDb.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "createdOn differs for Change.Id "
-            + c1.getId()
-            + ": {2009-09-30 17:00:06.0} != {2009-09-30 17:00:00.0}");
-
-    // One NoteDb.
-    b1 =
-        new ChangeBundle(
-            c1, messages(), patchSets(), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c2, messages(), patchSets(), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-  }
-
-  @Test
-  public void diffChangeMessageKeySets() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    int id = c.getId().get();
-    ChangeMessage cm1 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid1"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    ChangeMessage cm2 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid2"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(cm2), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-
-    assertDiffs(
-        b1,
-        b2,
-        "ChangeMessage.Key sets differ:"
-            + " ["
-            + id
-            + ",uuid1] only in A; ["
-            + id
-            + ",uuid2] only in B");
-  }
-
-  @Test
-  public void diffChangeMessages() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    ChangeMessage cm1 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    cm1.setMessage("message 1");
-    ChangeMessage cm2 = clone(cm1);
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(cm2), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-
-    assertNoDiffs(b1, b2);
-
-    cm2.setMessage("message 2");
-    assertDiffs(
-        b1,
-        b2,
-        "message differs for ChangeMessage.Key "
-            + c.getId()
-            + ",uuid:"
-            + " {message 1} != {message 2}");
-  }
-
-  @Test
-  public void diffChangeMessagesIgnoresUuids() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    int id = c.getId().get();
-    ChangeMessage cm1 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid1"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    cm1.setMessage("message 1");
-    ChangeMessage cm2 = clone(cm1);
-    cm2.getKey().set("uuid2");
-
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(cm2), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    // Both are ReviewDb, exact UUID match is required.
-    assertDiffs(
-        b1,
-        b2,
-        "ChangeMessage.Key sets differ:"
-            + " ["
-            + id
-            + ",uuid1] only in A; ["
-            + id
-            + ",uuid2] only in B");
-
-    // One NoteDb, UUIDs are ignored.
-    b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(cm2), latest(c), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-  }
-
-  @Test
-  public void diffChangeMessagesWithDifferentCounts() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    int id = c.getId().get();
-    ChangeMessage cm1 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid1"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    cm1.setMessage("message 1");
-    ChangeMessage cm2 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid2"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    cm1.setMessage("message 2");
-
-    // Both ReviewDb: Uses same keySet diff as other types.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(cm1, cm2), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1, b2, "ChangeMessage.Key sets differ: [" + id + ",uuid2] only in A; [] only in B");
-
-    // One NoteDb: UUIDs in keys can't be used for comparison, just diff counts.
-    b1 =
-        new ChangeBundle(
-            c, messages(cm1, cm2), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), NOTE_DB);
-    assertDiffs(b1, b2, "ChangeMessages differ for Change.Id " + id + "\nOnly in A:\n  " + cm2);
-    assertDiffs(b2, b1, "ChangeMessages differ for Change.Id " + id + "\nOnly in B:\n  " + cm2);
-  }
-
-  @Test
-  public void diffChangeMessagesMixedSourcesWithDifferences() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    int id = c.getId().get();
-    ChangeMessage cm1 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid1"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    cm1.setMessage("message 1");
-    ChangeMessage cm2 = clone(cm1);
-    cm2.setMessage("message 2");
-    ChangeMessage cm3 = clone(cm1);
-    cm3.getKey().set("uuid2"); // Differs only in UUID.
-
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(cm1, cm3), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(cm2, cm3), latest(c), approvals(), comments(), reviewers(), NOTE_DB);
-    // Implementation happens to pair up cm1 in b1 with cm3 in b2 because it
-    // depends on iteration order and doesn't care about UUIDs. The important
-    // thing is that there's some diff.
-    assertDiffs(
-        b1,
-        b2,
-        "ChangeMessages differ for Change.Id "
-            + id
-            + "\n"
-            + "Only in A:\n  "
-            + cm3
-            + "\n"
-            + "Only in B:\n  "
-            + cm2);
-    assertDiffs(
-        b2,
-        b1,
-        "ChangeMessages differ for Change.Id "
-            + id
-            + "\n"
-            + "Only in A:\n  "
-            + cm2
-            + "\n"
-            + "Only in B:\n  "
-            + cm3);
-  }
-
-  @Test
-  public void diffChangeMessagesMixedSourcesAllowsSlop() throws Exception {
-    subWindowResolution();
-    Change c = TestChanges.newChange(project, accountId);
-    ChangeMessage cm1 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid1"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    ChangeMessage cm2 = clone(cm1);
-    cm2.setWrittenOn(TimeUtil.nowTs());
-
-    // Both are ReviewDb, exact timestamp match is required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(cm2), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "writtenOn differs for ChangeMessage.Key "
-            + c.getId()
-            + ",uuid1:"
-            + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:03.0}");
-
-    // One NoteDb, slop is allowed.
-    b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(cm2), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-
-    // But not too much slop.
-    superWindowResolution();
-    ChangeMessage cm3 = clone(cm1);
-    cm3.setWrittenOn(TimeUtil.nowTs());
-    b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), NOTE_DB);
-    ChangeBundle b3 =
-        new ChangeBundle(
-            c, messages(cm3), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    int id = c.getId().get();
-    assertDiffs(
-        b1,
-        b3,
-        "ChangeMessages differ for Change.Id "
-            + id
-            + "\n"
-            + "Only in A:\n  "
-            + cm1
-            + "\n"
-            + "Only in B:\n  "
-            + cm3);
-    assertDiffs(
-        b3,
-        b1,
-        "ChangeMessages differ for Change.Id "
-            + id
-            + "\n"
-            + "Only in A:\n  "
-            + cm3
-            + "\n"
-            + "Only in B:\n  "
-            + cm1);
-  }
-
-  @Test
-  public void diffChangeMessagesAllowsNullPatchSetIdFromReviewDb() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    int id = c.getId().get();
-    ChangeMessage cm1 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    cm1.setMessage("message 1");
-    ChangeMessage cm2 = clone(cm1);
-    cm2.setPatchSetId(null);
-
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(cm2), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-
-    // Both are ReviewDb, exact patch set ID match is required.
-    assertDiffs(
-        b1,
-        b2,
-        "patchset differs for ChangeMessage.Key "
-            + c.getId()
-            + ",uuid:"
-            + " {"
-            + id
-            + ",1} != {null}");
-
-    // Null patch set ID on ReviewDb is ignored.
-    b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(cm2), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-
-    // Null patch set ID on NoteDb is not ignored (but is not realistic).
-    b1 =
-        new ChangeBundle(
-            c, messages(cm1), latest(c), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(cm2), latest(c), approvals(), comments(), reviewers(), NOTE_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "ChangeMessages differ for Change.Id "
-            + id
-            + "\n"
-            + "Only in A:\n  "
-            + cm1
-            + "\n"
-            + "Only in B:\n  "
-            + cm2);
-    assertDiffs(
-        b2,
-        b1,
-        "ChangeMessages differ for Change.Id "
-            + id
-            + "\n"
-            + "Only in A:\n  "
-            + cm2
-            + "\n"
-            + "Only in B:\n  "
-            + cm1);
-  }
-
-  @Test
-  public void diffPatchSetIdSets() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    TestChanges.incrementPatchSet(c);
-
-    PatchSet ps1 = new PatchSet(new PatchSet.Id(c.getId(), 1));
-    ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    ps1.setUploader(accountId);
-    ps1.setCreatedOn(TimeUtil.nowTs());
-    PatchSet ps2 = new PatchSet(new PatchSet.Id(c.getId(), 2));
-    ps2.setRevision(new RevId("badc0feebadc0feebadc0feebadc0feebadc0fee"));
-    ps2.setUploader(accountId);
-    ps2.setCreatedOn(TimeUtil.nowTs());
-
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps2), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1, ps2), approvals(), comments(), reviewers(), REVIEW_DB);
-
-    assertDiffs(b1, b2, "PatchSet.Id sets differ: [] only in A; [" + c.getId() + ",1] only in B");
-  }
-
-  @Test
-  public void diffPatchSets() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    PatchSet ps1 = new PatchSet(c.currentPatchSetId());
-    ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    ps1.setUploader(accountId);
-    ps1.setCreatedOn(TimeUtil.nowTs());
-    PatchSet ps2 = clone(ps1);
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps2), approvals(), comments(), reviewers(), REVIEW_DB);
-
-    assertNoDiffs(b1, b2);
-
-    ps2.setRevision(new RevId("badc0feebadc0feebadc0feebadc0feebadc0fee"));
-    assertDiffs(
-        b1,
-        b2,
-        "revision differs for PatchSet.Id "
-            + c.getId()
-            + ",1:"
-            + " {RevId{deadbeefdeadbeefdeadbeefdeadbeefdeadbeef}}"
-            + " != {RevId{badc0feebadc0feebadc0feebadc0feebadc0fee}}");
-  }
-
-  @Test
-  public void diffPatchSetsMixedSourcesAllowsSlop() throws Exception {
-    subWindowResolution();
-    Change c = TestChanges.newChange(project, accountId);
-    PatchSet ps1 = new PatchSet(c.currentPatchSetId());
-    ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    ps1.setUploader(accountId);
-    ps1.setCreatedOn(truncateToSecond(TimeUtil.nowTs()));
-    PatchSet ps2 = clone(ps1);
-    ps2.setCreatedOn(TimeUtil.nowTs());
-
-    // Both are ReviewDb, exact timestamp match is required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps2), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "createdOn differs for PatchSet.Id "
-            + c.getId()
-            + ",1:"
-            + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:03.0}");
-
-    // One NoteDb, slop is allowed.
-    b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1), approvals(), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps2), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-
-    // But not too much slop.
-    superWindowResolution();
-    PatchSet ps3 = clone(ps1);
-    ps3.setCreatedOn(TimeUtil.nowTs());
-    b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1), approvals(), comments(), reviewers(), NOTE_DB);
-    ChangeBundle b3 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps3), approvals(), comments(), reviewers(), REVIEW_DB);
-    String msg =
-        "createdOn differs for PatchSet.Id "
-            + c.getId()
-            + ",1 in NoteDb vs. ReviewDb:"
-            + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:10.0}";
-    assertDiffs(b1, b3, msg);
-    assertDiffs(b3, b1, msg);
-  }
-
-  @Test
-  public void diffPatchSetsIgnoresTrailingNewlinesInPushCertificate() throws Exception {
-    subWindowResolution();
-    Change c = TestChanges.newChange(project, accountId);
-    PatchSet ps1 = new PatchSet(c.currentPatchSetId());
-    ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    ps1.setUploader(accountId);
-    ps1.setCreatedOn(truncateToSecond(TimeUtil.nowTs()));
-    ps1.setPushCertificate("some cert");
-    PatchSet ps2 = clone(ps1);
-    ps2.setPushCertificate(ps2.getPushCertificate() + "\n\n");
-
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1), approvals(), comments(), reviewers(), NOTE_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps2), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-
-    b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps2), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-  }
-
-  @Test
-  public void diffPatchSetsGreaterThanCurrent() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-
-    PatchSet ps1 = new PatchSet(new PatchSet.Id(c.getId(), 1));
-    ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    ps1.setUploader(accountId);
-    ps1.setCreatedOn(TimeUtil.nowTs());
-    PatchSet ps2 = new PatchSet(new PatchSet.Id(c.getId(), 2));
-    ps2.setRevision(new RevId("badc0feebadc0feebadc0feebadc0feebadc0fee"));
-    ps2.setUploader(accountId);
-    ps2.setCreatedOn(TimeUtil.nowTs());
-    assertThat(ps2.getId().get()).isGreaterThan(c.currentPatchSetId().get());
-
-    ChangeMessage cm1 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid1"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-    ChangeMessage cm2 =
-        new ChangeMessage(
-            new ChangeMessage.Key(c.getId(), "uuid2"),
-            accountId,
-            TimeUtil.nowTs(),
-            c.currentPatchSetId());
-
-    PatchSetApproval a1 =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(ps1.getId(), accountId, new LabelId("Code-Review")),
-            (short) 1,
-            TimeUtil.nowTs());
-    PatchSetApproval a2 =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(ps2.getId(), accountId, new LabelId("Code-Review")),
-            (short) 1,
-            TimeUtil.nowTs());
-
-    // Both ReviewDb.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(cm1), patchSets(ps1), approvals(a1), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c,
-            messages(cm1, cm2),
-            patchSets(ps1, ps2),
-            approvals(a1, a2),
-            comments(),
-            reviewers(),
-            REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "ChangeMessage.Key sets differ: [] only in A; [" + cm2.getKey() + "] only in B",
-        "PatchSet.Id sets differ: [] only in A; [" + ps2.getId() + "] only in B",
-        "PatchSetApproval.Key sets differ: [] only in A; [" + a2.getKey() + "] only in B");
-
-    // One NoteDb.
-    b1 =
-        new ChangeBundle(
-            c, messages(cm1), patchSets(ps1), approvals(a1), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c,
-            messages(cm1, cm2),
-            patchSets(ps1, ps2),
-            approvals(a1, a2),
-            comments(),
-            reviewers(),
-            REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "ChangeMessages differ for Change.Id " + c.getId() + "\nOnly in B:\n  " + cm2,
-        "PatchSet.Id sets differ: [] only in A; [" + ps2.getId() + "] only in B",
-        "PatchSetApproval.Key sets differ: [] only in A; [" + a2.getKey() + "] only in B");
-
-    // Both NoteDb.
-    b1 =
-        new ChangeBundle(
-            c, messages(cm1), patchSets(ps1), approvals(a1), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c,
-            messages(cm1, cm2),
-            patchSets(ps1, ps2),
-            approvals(a1, a2),
-            comments(),
-            reviewers(),
-            NOTE_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "ChangeMessages differ for Change.Id " + c.getId() + "\nOnly in B:\n  " + cm2,
-        "PatchSet.Id sets differ: [] only in A; [" + ps2.getId() + "] only in B",
-        "PatchSetApproval.Key sets differ: [] only in A; [" + a2.getKey() + "] only in B");
-  }
-
-  @Test
-  public void diffPatchSetsIgnoresLeadingAndTrailingWhitespaceInReviewDbDescriptions()
-      throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-
-    PatchSet ps1 = new PatchSet(new PatchSet.Id(c.getId(), 1));
-    ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    ps1.setUploader(accountId);
-    ps1.setCreatedOn(TimeUtil.nowTs());
-    ps1.setDescription(" abc ");
-    PatchSet ps2 = clone(ps1);
-    ps2.setDescription("abc");
-
-    // Both ReviewDb, exact match required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1), approvals(), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps2), approvals(), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1, b2, "description differs for PatchSet.Id " + ps1.getId() + ": { abc } != {abc}");
-
-    // Whitespace in ReviewDb description is ignored.
-    b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps2), approvals(), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-
-    // Must match except for the leading/trailing whitespace.
-    PatchSet ps3 = clone(ps1);
-    ps3.setDescription("cba");
-    b1 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps1), approvals(), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(), patchSets(ps3), approvals(), comments(), reviewers(), NOTE_DB);
-    assertDiffs(
-        b1, b2, "description differs for PatchSet.Id " + ps1.getId() + ": { abc } != {cba}");
-  }
-
-  @Test
-  public void diffPatchSetsIgnoresCreatedOnWhenReviewDbIsNonMonotonic() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-
-    Timestamp beforePs1 = TimeUtil.nowTs();
-
-    PatchSet goodPs1 = new PatchSet(new PatchSet.Id(c.getId(), 1));
-    goodPs1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    goodPs1.setUploader(accountId);
-    goodPs1.setCreatedOn(TimeUtil.nowTs());
-    PatchSet goodPs2 = new PatchSet(new PatchSet.Id(c.getId(), 2));
-    goodPs2.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    goodPs2.setUploader(accountId);
-    goodPs2.setCreatedOn(TimeUtil.nowTs());
-    assertThat(goodPs2.getCreatedOn()).isGreaterThan(goodPs1.getCreatedOn());
-
-    PatchSet badPs2 = clone(goodPs2);
-    badPs2.setCreatedOn(beforePs1);
-    assertThat(badPs2.getCreatedOn()).isLessThan(goodPs1.getCreatedOn());
-
-    // Both ReviewDb, exact match required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(goodPs1, goodPs2),
-            approvals(),
-            comments(),
-            reviewers(),
-            REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(goodPs1, badPs2),
-            approvals(),
-            comments(),
-            reviewers(),
-            REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "createdOn differs for PatchSet.Id "
-            + badPs2.getId()
-            + ":"
-            + " {2009-09-30 17:00:18.0} != {2009-09-30 17:00:06.0}");
-
-    // Non-monotonic in ReviewDb but monotonic in NoteDb, timestamps are
-    // ignored, including for ps1.
-    PatchSet badPs1 = clone(goodPs1);
-    badPs1.setCreatedOn(TimeUtil.nowTs());
-    b1 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(badPs1, badPs2),
-            approvals(),
-            comments(),
-            reviewers(),
-            REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(goodPs1, goodPs2),
-            approvals(),
-            comments(),
-            reviewers(),
-            NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-
-    // Non-monotonic in NoteDb but monotonic in ReviewDb, timestamps are not
-    // ignored.
-    b1 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(goodPs1, goodPs2),
-            approvals(),
-            comments(),
-            reviewers(),
-            REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(badPs1, badPs2),
-            approvals(),
-            comments(),
-            reviewers(),
-            NOTE_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "createdOn differs for PatchSet.Id "
-            + badPs1.getId()
-            + " in NoteDb vs. ReviewDb:"
-            + " {2009-09-30 17:00:24.0} != {2009-09-30 17:00:12.0}",
-        "createdOn differs for PatchSet.Id "
-            + badPs2.getId()
-            + " in NoteDb vs. ReviewDb:"
-            + " {2009-09-30 17:00:06.0} != {2009-09-30 17:00:18.0}");
-  }
-
-  @Test
-  public void diffPatchSetsAllowsFirstPatchSetCreatedOnToMatchChangeCreatedOn() {
-    Change c = TestChanges.newChange(project, accountId);
-    c.setLastUpdatedOn(TimeUtil.nowTs());
-
-    PatchSet goodPs1 = new PatchSet(new PatchSet.Id(c.getId(), 1));
-    goodPs1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    goodPs1.setUploader(accountId);
-    goodPs1.setCreatedOn(TimeUtil.nowTs());
-    assertThat(goodPs1.getCreatedOn()).isGreaterThan(c.getCreatedOn());
-
-    PatchSet ps1AtCreatedOn = clone(goodPs1);
-    ps1AtCreatedOn.setCreatedOn(c.getCreatedOn());
-
-    PatchSet goodPs2 = new PatchSet(new PatchSet.Id(c.getId(), 2));
-    goodPs2.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
-    goodPs2.setUploader(accountId);
-    goodPs2.setCreatedOn(TimeUtil.nowTs());
-
-    PatchSet ps2AtCreatedOn = clone(goodPs2);
-    ps2AtCreatedOn.setCreatedOn(c.getCreatedOn());
-
-    // Both ReviewDb, exact match required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(goodPs1, goodPs2),
-            approvals(),
-            comments(),
-            reviewers(),
-            REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(ps1AtCreatedOn, ps2AtCreatedOn),
-            approvals(),
-            comments(),
-            reviewers(),
-            REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "createdOn differs for PatchSet.Id "
-            + c.getId()
-            + ",1: {2009-09-30 17:00:12.0} != {2009-09-30 17:00:00.0}",
-        "createdOn differs for PatchSet.Id "
-            + c.getId()
-            + ",2: {2009-09-30 17:00:18.0} != {2009-09-30 17:00:00.0}");
-
-    // One ReviewDb, PS1 is allowed to match change createdOn, but PS2 isn't.
-    b1 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(goodPs1, goodPs2),
-            approvals(),
-            comments(),
-            reviewers(),
-            REVIEW_DB);
-    b2 =
-        new ChangeBundle(
-            c,
-            messages(),
-            patchSets(ps1AtCreatedOn, ps2AtCreatedOn),
-            approvals(),
-            comments(),
-            reviewers(),
-            NOTE_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "createdOn differs for PatchSet.Id "
-            + c.getId()
-            + ",2 in NoteDb vs. ReviewDb: {2009-09-30 17:00:00.0} != {2009-09-30 17:00:18.0}");
-    assertDiffs(
-        b2,
-        b1,
-        "createdOn differs for PatchSet.Id "
-            + c.getId()
-            + ",2 in NoteDb vs. ReviewDb: {2009-09-30 17:00:00.0} != {2009-09-30 17:00:18.0}");
-  }
-
-  @Test
-  public void diffPatchSetApprovalKeySets() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    int id = c.getId().get();
-    PatchSetApproval a1 =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(c.currentPatchSetId(), accountId, new LabelId("Code-Review")),
-            (short) 1,
-            TimeUtil.nowTs());
-    PatchSetApproval a2 =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(c.currentPatchSetId(), accountId, new LabelId("Verified")),
-            (short) 1,
-            TimeUtil.nowTs());
-
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a1), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a2), comments(), reviewers(), REVIEW_DB);
-
-    assertDiffs(
-        b1,
-        b2,
-        "PatchSetApproval.Key sets differ:"
-            + " ["
-            + id
-            + "%2C1,100,Code-Review] only in A;"
-            + " ["
-            + id
-            + "%2C1,100,Verified] only in B");
-  }
-
-  @Test
-  public void diffPatchSetApprovals() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    PatchSetApproval a1 =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(c.currentPatchSetId(), accountId, new LabelId("Code-Review")),
-            (short) 1,
-            TimeUtil.nowTs());
-    PatchSetApproval a2 = clone(a1);
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a1), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a2), comments(), reviewers(), REVIEW_DB);
-
-    assertNoDiffs(b1, b2);
-
-    a2.setValue((short) -1);
-    assertDiffs(
-        b1,
-        b2,
-        "value differs for PatchSetApproval.Key "
-            + c.getId()
-            + "%2C1,100,Code-Review: {1} != {-1}");
-  }
-
-  @Test
-  public void diffPatchSetApprovalsMixedSourcesAllowsSlop() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    subWindowResolution();
-    PatchSetApproval a1 =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(c.currentPatchSetId(), accountId, new LabelId("Code-Review")),
-            (short) 1,
-            truncateToSecond(TimeUtil.nowTs()));
-    PatchSetApproval a2 = clone(a1);
-    a2.setGranted(TimeUtil.nowTs());
-
-    // Both are ReviewDb, exact timestamp match is required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a1), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a2), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "granted differs for PatchSetApproval.Key "
-            + c.getId()
-            + "%2C1,100,Code-Review:"
-            + " {2009-09-30 17:00:07.0} != {2009-09-30 17:00:08.0}");
-
-    // One NoteDb, slop is allowed.
-    b1 =
-        new ChangeBundle(c, messages(), latest(c), approvals(a1), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a2), comments(), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-
-    // But not too much slop.
-    superWindowResolution();
-    PatchSetApproval a3 = clone(a1);
-    a3.setGranted(TimeUtil.nowTs());
-    b1 =
-        new ChangeBundle(c, messages(), latest(c), approvals(a1), comments(), reviewers(), NOTE_DB);
-    ChangeBundle b3 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a3), comments(), reviewers(), REVIEW_DB);
-    String msg =
-        "granted differs for PatchSetApproval.Key "
-            + c.getId()
-            + "%2C1,100,Code-Review in NoteDb vs. ReviewDb:"
-            + " {2009-09-30 17:00:07.0} != {2009-09-30 17:00:15.0}";
-    assertDiffs(b1, b3, msg);
-    assertDiffs(b3, b1, msg);
-  }
-
-  @Test
-  public void diffPatchSetApprovalsAllowsTruncatedTimestampInNoteDb() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    PatchSetApproval a1 =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(c.currentPatchSetId(), accountId, new LabelId("Code-Review")),
-            (short) 1,
-            c.getCreatedOn());
-    PatchSetApproval a2 = clone(a1);
-    a2.setGranted(
-        new Timestamp(
-            LocalDate.of(1900, Month.JANUARY, 1)
-                .atStartOfDay()
-                .atZone(ZoneId.of(TIMEZONE_ID))
-                .toInstant()
-                .toEpochMilli()));
-
-    // Both are ReviewDb, exact match is required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a1), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a2), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "granted differs for PatchSetApproval.Key "
-            + c.getId()
-            + "%2C1,100,Code-Review:"
-            + " {2009-09-30 17:00:00.0} != {1900-01-01 00:00:00.0}");
-
-    // Truncating NoteDb timestamp is allowed.
-    b1 =
-        new ChangeBundle(c, messages(), latest(c), approvals(a1), comments(), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a2), comments(), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-  }
-
-  @Test
-  public void diffPatchSetApprovalsIgnoresPostSubmitBitOnZeroVote() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    c.setStatus(Change.Status.MERGED);
-    PatchSetApproval a1 =
-        new PatchSetApproval(
-            new PatchSetApproval.Key(c.currentPatchSetId(), accountId, new LabelId("Code-Review")),
-            (short) 0,
-            TimeUtil.nowTs());
-    a1.setPostSubmit(false);
-    PatchSetApproval a2 = clone(a1);
-    a2.setPostSubmit(true);
-
-    // Both are ReviewDb, exact match is required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a1), comments(), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a2), comments(), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "postSubmit differs for PatchSetApproval.Key "
-            + c.getId()
-            + "%2C1,100,Code-Review:"
-            + " {false} != {true}");
-
-    // One NoteDb, postSubmit is ignored.
-    b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(a1), comments(), reviewers(), REVIEW_DB);
-    b2 =
-        new ChangeBundle(c, messages(), latest(c), approvals(a2), comments(), reviewers(), NOTE_DB);
-    assertNoDiffs(b1, b2);
-    assertNoDiffs(b2, b1);
-
-    // postSubmit is not ignored if vote isn't 0.
-    a1.setValue((short) 1);
-    a2.setValue((short) 1);
-    assertDiffs(
-        b1,
-        b2,
-        "postSubmit differs for PatchSetApproval.Key "
-            + c.getId()
-            + "%2C1,100,Code-Review:"
-            + " {false} != {true}");
-    assertDiffs(
-        b2,
-        b1,
-        "postSubmit differs for PatchSetApproval.Key "
-            + c.getId()
-            + "%2C1,100,Code-Review:"
-            + " {true} != {false}");
-  }
-
-  @Test
-  public void diffReviewers() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    Timestamp now = TimeUtil.nowTs();
-    ReviewerSet r1 = reviewers(REVIEWER, new Account.Id(1), now);
-    ReviewerSet r2 = reviewers(REVIEWER, new Account.Id(2), now);
-
-    ChangeBundle b1 =
-        new ChangeBundle(c, messages(), latest(c), approvals(), comments(), r1, REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(c, messages(), latest(c), approvals(), comments(), r2, REVIEW_DB);
-    assertNoDiffs(b1, b1);
-    assertNoDiffs(b2, b2);
-    assertDiffs(b1, b2, "reviewer sets differ: [1] only in A; [2] only in B");
-  }
-
-  @Test
-  public void diffReviewersIgnoresStateAndTimestamp() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    ReviewerSet r1 = reviewers(REVIEWER, new Account.Id(1), TimeUtil.nowTs());
-    ReviewerSet r2 = reviewers(CC, new Account.Id(1), TimeUtil.nowTs());
-
-    ChangeBundle b1 =
-        new ChangeBundle(c, messages(), latest(c), approvals(), comments(), r1, REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(c, messages(), latest(c), approvals(), comments(), r2, REVIEW_DB);
-    assertNoDiffs(b1, b1);
-    assertNoDiffs(b2, b2);
-  }
-
-  @Test
-  public void diffPatchLineCommentKeySets() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    int id = c.getId().get();
-    PatchLineComment c1 =
-        new PatchLineComment(
-            new PatchLineComment.Key(new Patch.Key(c.currentPatchSetId(), "filename1"), "uuid1"),
-            5,
-            accountId,
-            null,
-            TimeUtil.nowTs());
-    PatchLineComment c2 =
-        new PatchLineComment(
-            new PatchLineComment.Key(new Patch.Key(c.currentPatchSetId(), "filename2"), "uuid2"),
-            5,
-            accountId,
-            null,
-            TimeUtil.nowTs());
-
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c1), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c2), reviewers(), REVIEW_DB);
-
-    assertDiffs(
-        b1,
-        b2,
-        "PatchLineComment.Key sets differ:"
-            + " ["
-            + id
-            + ",1,filename1,uuid1] only in A;"
-            + " ["
-            + id
-            + ",1,filename2,uuid2] only in B");
-  }
-
-  @Test
-  public void diffPatchLineComments() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    PatchLineComment c1 =
-        new PatchLineComment(
-            new PatchLineComment.Key(new Patch.Key(c.currentPatchSetId(), "filename"), "uuid"),
-            5,
-            accountId,
-            null,
-            TimeUtil.nowTs());
-    PatchLineComment c2 = clone(c1);
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c1), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c2), reviewers(), REVIEW_DB);
-
-    assertNoDiffs(b1, b2);
-
-    c2.setStatus(PatchLineComment.Status.PUBLISHED);
-    assertDiffs(
-        b1,
-        b2,
-        "status differs for PatchLineComment.Key " + c.getId() + ",1,filename,uuid: {d} != {P}");
-  }
-
-  @Test
-  public void diffPatchLineCommentsMixedSourcesAllowsSlop() throws Exception {
-    subWindowResolution();
-    Change c = TestChanges.newChange(project, accountId);
-    PatchLineComment c1 =
-        new PatchLineComment(
-            new PatchLineComment.Key(new Patch.Key(c.currentPatchSetId(), "filename"), "uuid"),
-            5,
-            accountId,
-            null,
-            truncateToSecond(TimeUtil.nowTs()));
-    PatchLineComment c2 = clone(c1);
-    c2.setWrittenOn(TimeUtil.nowTs());
-
-    // Both are ReviewDb, exact timestamp match is required.
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c1), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c2), reviewers(), REVIEW_DB);
-    assertDiffs(
-        b1,
-        b2,
-        "writtenOn differs for PatchLineComment.Key "
-            + c.getId()
-            + ",1,filename,uuid:"
-            + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:03.0}");
-
-    // One NoteDb, slop is allowed.
-    b1 =
-        new ChangeBundle(c, messages(), latest(c), approvals(), comments(c1), reviewers(), NOTE_DB);
-    b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c2), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-
-    // But not too much slop.
-    superWindowResolution();
-    PatchLineComment c3 = clone(c1);
-    c3.setWrittenOn(TimeUtil.nowTs());
-    b1 =
-        new ChangeBundle(c, messages(), latest(c), approvals(), comments(c1), reviewers(), NOTE_DB);
-    ChangeBundle b3 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c3), reviewers(), REVIEW_DB);
-    String msg =
-        "writtenOn differs for PatchLineComment.Key "
-            + c.getId()
-            + ",1,filename,uuid in NoteDb vs. ReviewDb:"
-            + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:10.0}";
-    assertDiffs(b1, b3, msg);
-    assertDiffs(b3, b1, msg);
-  }
-
-  @Test
-  public void diffPatchLineCommentsIgnoresCommentsOnInvalidPatchSet() throws Exception {
-    Change c = TestChanges.newChange(project, accountId);
-    PatchLineComment c1 =
-        new PatchLineComment(
-            new PatchLineComment.Key(new Patch.Key(c.currentPatchSetId(), "filename1"), "uuid1"),
-            5,
-            accountId,
-            null,
-            TimeUtil.nowTs());
-    PatchLineComment c2 =
-        new PatchLineComment(
-            new PatchLineComment.Key(
-                new Patch.Key(new PatchSet.Id(c.getId(), 0), "filename2"), "uuid2"),
-            5,
-            accountId,
-            null,
-            TimeUtil.nowTs());
-
-    ChangeBundle b1 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c1, c2), reviewers(), REVIEW_DB);
-    ChangeBundle b2 =
-        new ChangeBundle(
-            c, messages(), latest(c), approvals(), comments(c1), reviewers(), REVIEW_DB);
-    assertNoDiffs(b1, b2);
-  }
-
-  private static void assertNoDiffs(ChangeBundle a, ChangeBundle b) {
-    assertThat(a.differencesFrom(b)).isEmpty();
-    assertThat(b.differencesFrom(a)).isEmpty();
-  }
-
-  private static void assertDiffs(ChangeBundle a, ChangeBundle b, String first, String... rest) {
-    List<String> actual = a.differencesFrom(b);
-    if (actual.size() == 1 && rest.length == 0) {
-      // This error message is much easier to read.
-      assertThat(actual.get(0)).isEqualTo(first);
-    } else {
-      List<String> expected = new ArrayList<>(1 + rest.length);
-      expected.add(first);
-      Collections.addAll(expected, rest);
-      assertThat(actual).containsExactlyElementsIn(expected).inOrder();
-    }
-    assertThat(a).isNotEqualTo(b);
-  }
-
-  private static List<ChangeMessage> messages(ChangeMessage... ents) {
-    return Arrays.asList(ents);
-  }
-
-  private static List<PatchSet> patchSets(PatchSet... ents) {
-    return Arrays.asList(ents);
-  }
-
-  private static List<PatchSet> latest(Change c) {
-    PatchSet ps = new PatchSet(c.currentPatchSetId());
-    ps.setCreatedOn(c.getLastUpdatedOn());
-    return ImmutableList.of(ps);
-  }
-
-  private static List<PatchSetApproval> approvals(PatchSetApproval... ents) {
-    return Arrays.asList(ents);
-  }
-
-  private static ReviewerSet reviewers(Object... ents) {
-    checkArgument(ents.length % 3 == 0);
-    Table<ReviewerStateInternal, Account.Id, Timestamp> t = HashBasedTable.create();
-    for (int i = 0; i < ents.length; i += 3) {
-      t.put((ReviewerStateInternal) ents[i], (Account.Id) ents[i + 1], (Timestamp) ents[i + 2]);
-    }
-    return ReviewerSet.fromTable(t);
-  }
-
-  private static List<PatchLineComment> comments(PatchLineComment... ents) {
-    return Arrays.asList(ents);
-  }
-
-  private static Change clone(Change ent) {
-    return clone(CHANGE_CODEC, ent);
-  }
-
-  private static ChangeMessage clone(ChangeMessage ent) {
-    return clone(CHANGE_MESSAGE_CODEC, ent);
-  }
-
-  private static PatchSet clone(PatchSet ent) {
-    return clone(PATCH_SET_CODEC, ent);
-  }
-
-  private static PatchSetApproval clone(PatchSetApproval ent) {
-    return clone(PATCH_SET_APPROVAL_CODEC, ent);
-  }
-
-  private static PatchLineComment clone(PatchLineComment ent) {
-    return clone(PATCH_LINE_COMMENT_CODEC, ent);
-  }
-
-  private static <T> T clone(ProtobufCodec<T> codec, T obj) {
-    return codec.decode(codec.encodeToByteArray(obj));
-  }
-}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 549f5db..e274cdf 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -2605,7 +2605,7 @@
     }
 
     // Looking at drafts directly shows the zombie comment.
-    DraftCommentNotes draftNotes = draftNotesFactory.create(c, otherUserId);
+    DraftCommentNotes draftNotes = draftNotesFactory.create(c.getId(), otherUserId);
     assertThat(draftNotes.load().getComments().get(rev1)).containsExactly(comment1, comment2);
 
     // Zombie comment is filtered out of drafts via ChangeNotes.
diff --git a/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java b/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java
index b9027bc..fbec5e6 100644
--- a/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java
@@ -38,6 +38,7 @@
 import com.google.gerrit.testing.TestChanges;
 import com.google.inject.Inject;
 import java.io.ByteArrayOutputStream;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.eclipse.jgit.junit.TestRepository;
@@ -185,7 +186,6 @@
 
     // Comments at each commit all have JSON format.
     ImmutableList<RevCommit> newLog = log(project, RefNames.changeMetaRef(c.getId()));
-    assertLogEqualExceptTrees(newLog, oldLog);
     assertThat(getLegacyFormatMapForPublishedComments(notes, newLog.get(0))).isEmpty();
     assertThat(getLegacyFormatMapForPublishedComments(notes, newLog.get(1))).isEmpty();
     assertThat(getLegacyFormatMapForPublishedComments(notes, newLog.get(2)))
@@ -297,7 +297,6 @@
     // Comments at each commit all have JSON format.
     ImmutableList<RevCommit> newOwnerLog =
         log(allUsers, RefNames.refsDraftComments(c.getId(), changeOwner.getAccountId()));
-    assertLogEqualExceptTrees(newOwnerLog, oldOwnerLog);
     assertThat(getLegacyFormatMapForDraftComments(notes, newOwnerLog.get(0)))
         .containsExactly(ownerCommentPs1.key, false);
     assertThat(getLegacyFormatMapForDraftComments(notes, newOwnerLog.get(1)))
@@ -305,7 +304,6 @@
 
     ImmutableList<RevCommit> newOtherLog =
         log(allUsers, RefNames.refsDraftComments(c.getId(), otherUser.getAccountId()));
-    assertLogEqualExceptTrees(newOtherLog, oldOtherLog);
     assertThat(getLegacyFormatMapForDraftComments(notes, newOtherLog.get(0)))
         .containsExactly(otherCommentPs1.key, false);
   }
@@ -392,7 +390,6 @@
 
     // Comments at each commit all have JSON format.
     ImmutableList<RevCommit> newLog = log(project, RefNames.changeMetaRef(c.getId()));
-    assertLogEqualExceptTrees(newLog, oldLog);
     assertThat(getLegacyFormatMapForPublishedComments(notes, newLog.get(0))).isEmpty();
     assertThat(getLegacyFormatMapForPublishedComments(notes, newLog.get(1))).isEmpty();
     assertThat(getLegacyFormatMapForPublishedComments(notes, newLog.get(2))).isEmpty();
@@ -491,17 +488,35 @@
   }
 
   private ImmutableList<RevCommit> log(Project.NameKey project, String refName) throws Exception {
-    try (Repository repo = repoManager.openRepository(project);
-        RevWalk rw = new RevWalk(repo)) {
+    try (Repository repo = repoManager.openRepository(project)) {
+      return log(repo, refName);
+    }
+  }
+
+  private ImmutableList<RevCommit> log(Repository repo, String refName) throws Exception {
+    try (RevWalk rw = new RevWalk(repo)) {
       rw.sort(RevSort.TOPO);
       rw.sort(RevSort.REVERSE);
       Ref ref = repo.exactRef(refName);
-      checkArgument(ref != null, "missing ref: %s", refName);
+      if (ref == null) {
+        return ImmutableList.of();
+      }
       rw.markStart(rw.parseCommit(ref.getObjectId()));
       return ImmutableList.copyOf(rw);
     }
   }
 
+  private ImmutableListMultimap<String, RevCommit> logAll(
+      Project.NameKey project, Collection<Ref> refs) throws Exception {
+    ImmutableListMultimap.Builder<String, RevCommit> logs = ImmutableListMultimap.builder();
+    try (Repository repo = repoManager.openRepository(project)) {
+      for (Ref r : refs) {
+        logs.putAll(r.getName(), log(repo, r.getName()));
+      }
+    }
+    return logs.build();
+  }
+
   private static void assertLogEqualExceptTrees(
       ImmutableList<RevCommit> actualLog, ImmutableList<RevCommit> expectedLog) {
     assertThat(actualLog).hasSize(expectedLog.size());
@@ -522,9 +537,61 @@
   }
 
   private void assertNoDifferences(ChangeNotes actual, ChangeNotes expected) throws Exception {
-    assertThat(
-            ChangeBundle.fromNotes(commentsUtil, actual)
-                .differencesFrom(ChangeBundle.fromNotes(commentsUtil, expected)))
-        .isEmpty();
+    checkArgument(
+        actual.getChangeId().equals(expected.getChangeId()),
+        "must be same change: %s != %s",
+        actual.getChangeId(),
+        expected.getChangeId());
+
+    // Parsed comment representations are equal.
+    // TODO(dborowitz): Comparing collections directly would be much easier, but Comment doesn't
+    // have a proper equals; switch to that when the issues with
+    // https://gerrit-review.googlesource.com/c/gerrit/+/207013 are resolved.
+    assertCommentsEqual(commentsUtil.draftByChange(actual), commentsUtil.draftByChange(expected));
+    assertCommentsEqual(
+        commentsUtil.publishedByChange(actual), commentsUtil.publishedByChange(expected));
+
+    // Change metadata is equal.
+    assertLogEqualExceptTrees(
+        log(project, actual.getRefName()), log(project, expected.getRefName()));
+
+    // Logs of all draft refs are equal.
+    ImmutableListMultimap<String, RevCommit> actualDraftLogs =
+        logAll(allUsersName, commentsUtil.getDraftRefs(actual.getChangeId()));
+    ImmutableListMultimap<String, RevCommit> expectedDraftLogs =
+        logAll(allUsersName, commentsUtil.getDraftRefs(expected.getChangeId()));
+    assertThat(actualDraftLogs.keySet())
+        .named("draft ref names")
+        .containsExactlyElementsIn(expectedDraftLogs.keySet());
+    for (String refName : actualDraftLogs.keySet()) {
+      assertLogEqualExceptTrees(actualDraftLogs.get(refName), actualDraftLogs.get(refName));
+    }
+  }
+
+  private static void assertCommentsEqual(List<Comment> actualList, List<Comment> expectedList) {
+    ImmutableMap<Comment.Key, Comment> actualMap = byKey(actualList);
+    ImmutableMap<Comment.Key, Comment> expectedMap = byKey(expectedList);
+    assertThat(actualMap.keySet()).isEqualTo(expectedMap.keySet());
+    for (Comment.Key key : actualMap.keySet()) {
+      Comment actual = actualMap.get(key);
+      Comment expected = expectedMap.get(key);
+      assertThat(actual.key).isEqualTo(expected.key);
+      assertThat(actual.lineNbr).isEqualTo(expected.lineNbr);
+      assertThat(actual.author).isEqualTo(expected.author);
+      assertThat(actual.getRealAuthor()).isEqualTo(expected.getRealAuthor());
+      assertThat(actual.writtenOn).isEqualTo(expected.writtenOn);
+      assertThat(actual.side).isEqualTo(expected.side);
+      assertThat(actual.message).isEqualTo(expected.message);
+      assertThat(actual.parentUuid).isEqualTo(expected.parentUuid);
+      assertThat(actual.range).isEqualTo(expected.range);
+      assertThat(actual.tag).isEqualTo(expected.tag);
+      assertThat(actual.revId).isEqualTo(expected.revId);
+      assertThat(actual.serverId).isEqualTo(expected.serverId);
+      assertThat(actual.unresolved).isEqualTo(expected.unresolved);
+    }
+  }
+
+  private static ImmutableMap<Comment.Key, Comment> byKey(List<Comment> comments) {
+    return comments.stream().collect(toImmutableMap(c -> c.key, c -> c));
   }
 }
diff --git a/javatests/com/google/gerrit/server/notedb/NoteDbSchemaVersionManagerTest.java b/javatests/com/google/gerrit/server/notedb/NoteDbSchemaVersionManagerTest.java
new file mode 100644
index 0000000..c8900309
--- /dev/null
+++ b/javatests/com/google/gerrit/server/notedb/NoteDbSchemaVersionManagerTest.java
@@ -0,0 +1,79 @@
+package com.google.gerrit.server.notedb;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_VERSION;
+
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import com.google.gwtorm.server.OrmException;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NoteDbSchemaVersionManagerTest extends GerritBaseTests {
+  private NoteDbSchemaVersionManager manager;
+  private TestRepository<?> tr;
+
+  @Before
+  public void setUp() throws Exception {
+    AllProjectsName allProjectsName = new AllProjectsName("The-Projects");
+    GitRepositoryManager repoManager = new InMemoryRepositoryManager();
+    tr = new TestRepository<>(repoManager.createRepository(allProjectsName));
+    manager = new NoteDbSchemaVersionManager(allProjectsName, repoManager);
+  }
+
+  @Test
+  public void readMissing() throws Exception {
+    assertThat(manager.read()).isEqualTo(0);
+  }
+
+  @Test
+  public void read() throws Exception {
+    tr.update(REFS_VERSION, tr.blob("123"));
+    assertThat(manager.read()).isEqualTo(123);
+  }
+
+  @Test
+  public void readInvalid() throws Exception {
+    ObjectId blobId = tr.blob(" 1 2 3 ");
+    tr.update(REFS_VERSION, blobId);
+    try {
+      manager.read();
+      assert_().fail("expected OrmException");
+    } catch (OrmException e) {
+      assertThat(e)
+          .hasMessageThat()
+          .isEqualTo("invalid value in refs/meta/version blob at " + blobId.name());
+    }
+  }
+
+  @Test
+  public void incrementFromMissing() throws Exception {
+    manager.increment(123);
+    assertThat(manager.read()).isEqualTo(124);
+  }
+
+  @Test
+  public void increment() throws Exception {
+    tr.update(REFS_VERSION, tr.blob("123"));
+    manager.increment(123);
+    assertThat(manager.read()).isEqualTo(124);
+  }
+
+  @Test
+  public void incrementWrongOldVersion() throws Exception {
+    tr.update(REFS_VERSION, tr.blob("123"));
+    try {
+      manager.increment(456);
+      assert_().fail("expected OrmException");
+    } catch (OrmException e) {
+      assertThat(e)
+          .hasMessageThat()
+          .isEqualTo("Expected old version 456 for refs/meta/version, found 123");
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java b/javatests/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
deleted file mode 100644
index 7fb9d82..0000000
--- a/javatests/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright (C) 2016 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.notedb.rebuild;
-
-import static com.google.common.truth.Truth.assertThat;
-import static java.util.stream.Collectors.toList;
-import static org.junit.Assert.fail;
-
-import com.google.common.collect.Collections2;
-import com.google.common.collect.Lists;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
-import com.google.gerrit.testing.TestTimeUtil;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
-import org.junit.Before;
-import org.junit.Test;
-
-public class EventSorterTest extends GerritBaseTests {
-  private class TestEvent extends Event {
-    protected TestEvent(Timestamp when) {
-      super(
-          new PatchSet.Id(new Change.Id(1), 1),
-          new Account.Id(1000),
-          new Account.Id(1000),
-          when,
-          changeCreatedOn,
-          null);
-    }
-
-    @Override
-    boolean uniquePerUpdate() {
-      return false;
-    }
-
-    @Override
-    void apply(ChangeUpdate update) {
-      throw new UnsupportedOperationException();
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    public String toString() {
-      return "E{" + when.getSeconds() + '}';
-    }
-  }
-
-  private Timestamp changeCreatedOn;
-
-  @Before
-  public void setUp() {
-    TestTimeUtil.resetWithClockStep(10, TimeUnit.SECONDS);
-    changeCreatedOn = TimeUtil.nowTs();
-  }
-
-  @Test
-  public void naturalSort() {
-    Event e1 = new TestEvent(TimeUtil.nowTs());
-    Event e2 = new TestEvent(TimeUtil.nowTs());
-    Event e3 = new TestEvent(TimeUtil.nowTs());
-
-    for (List<Event> events : Collections2.permutations(events(e1, e2, e3))) {
-      assertSorted(events, events(e1, e2, e3));
-    }
-  }
-
-  @Test
-  public void topoSortOneDep() {
-    List<Event> es;
-
-    // Input list is 0,1,2
-
-    // 0 depends on 1 => 1,0,2
-    es = threeEventsOneDep(0, 1);
-    assertSorted(es, events(es, 1, 0, 2));
-
-    // 1 depends on 0 => 0,1,2
-    es = threeEventsOneDep(1, 0);
-    assertSorted(es, events(es, 0, 1, 2));
-
-    // 0 depends on 2 => 1,2,0
-    es = threeEventsOneDep(0, 2);
-    assertSorted(es, events(es, 1, 2, 0));
-
-    // 2 depends on 0 => 0,1,2
-    es = threeEventsOneDep(2, 0);
-    assertSorted(es, events(es, 0, 1, 2));
-
-    // 1 depends on 2 => 0,2,1
-    es = threeEventsOneDep(1, 2);
-    assertSorted(es, events(es, 0, 2, 1));
-
-    // 2 depends on 1 => 0,1,2
-    es = threeEventsOneDep(2, 1);
-    assertSorted(es, events(es, 0, 1, 2));
-  }
-
-  private List<Event> threeEventsOneDep(int depFromIdx, int depOnIdx) {
-    List<Event> events =
-        Lists.newArrayList(
-            new TestEvent(TimeUtil.nowTs()),
-            new TestEvent(TimeUtil.nowTs()),
-            new TestEvent(TimeUtil.nowTs()));
-    events.get(depFromIdx).addDep(events.get(depOnIdx));
-    return events;
-  }
-
-  @Test
-  public void lastEventDependsOnFirstEvent() {
-    List<Event> events = new ArrayList<>();
-    for (int i = 0; i < 20; i++) {
-      events.add(new TestEvent(TimeUtil.nowTs()));
-    }
-    events.get(events.size() - 1).addDep(events.get(0));
-    assertSorted(events, events);
-  }
-
-  @Test
-  public void firstEventDependsOnLastEvent() {
-    List<Event> events = new ArrayList<>();
-    for (int i = 0; i < 20; i++) {
-      events.add(new TestEvent(TimeUtil.nowTs()));
-    }
-    events.get(0).addDep(events.get(events.size() - 1));
-
-    List<Event> expected = new ArrayList<>();
-    expected.addAll(events.subList(1, events.size()));
-    expected.add(events.get(0));
-    assertSorted(events, expected);
-  }
-
-  @Test
-  public void topoSortChainOfDeps() {
-    Event e1 = new TestEvent(TimeUtil.nowTs());
-    Event e2 = new TestEvent(TimeUtil.nowTs());
-    Event e3 = new TestEvent(TimeUtil.nowTs());
-    Event e4 = new TestEvent(TimeUtil.nowTs());
-    e1.addDep(e2);
-    e2.addDep(e3);
-    e3.addDep(e4);
-
-    assertSorted(events(e1, e2, e3, e4), events(e4, e3, e2, e1));
-  }
-
-  @Test
-  public void topoSortMultipleDeps() {
-    Event e1 = new TestEvent(TimeUtil.nowTs());
-    Event e2 = new TestEvent(TimeUtil.nowTs());
-    Event e3 = new TestEvent(TimeUtil.nowTs());
-    Event e4 = new TestEvent(TimeUtil.nowTs());
-    e1.addDep(e2);
-    e1.addDep(e4);
-    e2.addDep(e3);
-
-    // Processing 3 pops 2, processing 4 pops 1.
-    assertSorted(events(e2, e3, e1, e4), events(e3, e2, e4, e1));
-  }
-
-  @Test
-  public void topoSortMultipleDepsPreservesNaturalOrder() {
-    Event e1 = new TestEvent(TimeUtil.nowTs());
-    Event e2 = new TestEvent(TimeUtil.nowTs());
-    Event e3 = new TestEvent(TimeUtil.nowTs());
-    Event e4 = new TestEvent(TimeUtil.nowTs());
-    e1.addDep(e4);
-    e2.addDep(e4);
-    e3.addDep(e4);
-
-    // Processing 4 pops 1, 2, 3 in natural order.
-    assertSorted(events(e4, e3, e2, e1), events(e4, e1, e2, e3));
-  }
-
-  @Test
-  public void topoSortCycle() {
-    Event e1 = new TestEvent(TimeUtil.nowTs());
-    Event e2 = new TestEvent(TimeUtil.nowTs());
-
-    // Implementation is not really defined, but infinite looping would be bad.
-    // According to current implementation details, 2 pops 1, 1 pops 2 which was
-    // already seen.
-    assertSorted(events(e2, e1), events(e1, e2));
-  }
-
-  @Test
-  public void topoSortDepNotInInputList() {
-    Event e1 = new TestEvent(TimeUtil.nowTs());
-    Event e2 = new TestEvent(TimeUtil.nowTs());
-    Event e3 = new TestEvent(TimeUtil.nowTs());
-    e1.addDep(e3);
-
-    List<Event> events = events(e2, e1);
-    try {
-      new EventSorter(events).sort();
-      fail("expected IllegalArgumentException");
-    } catch (IllegalArgumentException e) {
-      // Expected.
-    }
-  }
-
-  private static List<Event> events(Event... es) {
-    return Lists.newArrayList(es);
-  }
-
-  private static List<Event> events(List<Event> in, Integer... indexes) {
-    return Stream.of(indexes).map(in::get).collect(toList());
-  }
-
-  private static void assertSorted(List<Event> unsorted, List<Event> expected) {
-    List<Event> actual = new ArrayList<>(unsorted);
-    new EventSorter(actual).sort();
-    assertThat(actual).named("sorted" + unsorted).isEqualTo(expected);
-  }
-}
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
index c467312..bd0316c 100644
--- a/javatests/com/google/gerrit/server/permissions/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -63,7 +63,7 @@
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.project.RefPattern;
 import com.google.gerrit.server.project.testing.Util;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.testing.GerritBaseTests;
@@ -200,7 +200,7 @@
 
   @Inject private PermissionBackend permissionBackend;
   @Inject private CapabilityCollection.Factory capabilityCollectionFactory;
-  @Inject private ReviewDbSchemaCreator schemaCreator;
+  @Inject private SchemaCreator schemaCreator;
   @Inject private SingleVersionListener singleVersionListener;
   @Inject private InMemoryDatabase schemaFactory;
   @Inject private ThreadLocalRequestContext requestContext;
@@ -289,7 +289,7 @@
     db = schemaFactory.open();
     singleVersionListener.start();
     try {
-      schemaCreator.create(db);
+      schemaCreator.create();
     } finally {
       singleVersionListener.stop();
     }
@@ -329,7 +329,6 @@
     if (db != null) {
       db.close();
     }
-    InMemoryDatabase.drop(schemaFactory);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index 26cc3f8..b0d0849 100644
--- a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -74,7 +74,7 @@
 import com.google.gerrit.server.index.account.AccountIndex;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
 import com.google.gerrit.server.index.account.AccountIndexer;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
@@ -120,7 +120,7 @@
 
   @Inject protected InMemoryDatabase schemaFactory;
 
-  @Inject protected ReviewDbSchemaCreator schemaCreator;
+  @Inject protected SchemaCreator schemaCreator;
 
   @Inject protected ThreadLocalRequestContext requestContext;
 
@@ -165,7 +165,7 @@
 
   protected void setUpDatabase() throws Exception {
     db = schemaFactory.open();
-    schemaCreator.create(db);
+    schemaCreator.create();
 
     Account.Id adminId = createAccount("admin", "Administrator", "admin@example.com", true);
     admin = userFactory.create(adminId);
@@ -214,7 +214,6 @@
     if (db != null) {
       db.close();
     }
-    InMemoryDatabase.drop(schemaFactory);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index de2234e..fb20a05 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -52,7 +52,6 @@
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
-import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
 import com.google.gerrit.extensions.api.changes.StarsInput;
 import com.google.gerrit.extensions.api.groups.GroupInput;
 import com.google.gerrit.extensions.api.projects.ConfigInput;
@@ -69,7 +68,6 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.index.FieldDef;
 import com.google.gerrit.index.IndexConfig;
-import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.Account;
@@ -81,7 +79,6 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PatchSetUtil;
@@ -102,23 +99,17 @@
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.index.change.ChangeIndexer;
-import com.google.gerrit.server.index.change.IndexedChangeQuery;
-import com.google.gerrit.server.index.change.StalenessChecker;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.NoteDbChangeState;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.DisabledReviewDb;
 import com.google.gerrit.testing.GerritServerTests;
-import com.google.gerrit.testing.InMemoryDatabase;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
 import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
 import com.google.gerrit.testing.TestTimeUtil;
@@ -131,13 +122,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.ObjectId;
@@ -177,7 +166,7 @@
   @Inject protected PatchSetUtil psUtil;
   @Inject protected ChangeNotes.Factory changeNotesFactory;
   @Inject protected Provider<ChangeQueryProcessor> queryProcessorProvider;
-  @Inject protected ReviewDbSchemaCreator schemaCreator;
+  @Inject protected SchemaCreator schemaCreator;
   @Inject protected SchemaFactory<ReviewDb> schemaFactory;
   @Inject protected Sequences seq;
   @Inject protected ThreadLocalRequestContext requestContext;
@@ -185,9 +174,6 @@
   @Inject protected MetaDataUpdate.Server metaDataUpdateFactory;
   @Inject protected IdentifiedUser.GenericFactory identifiedUserFactory;
 
-  // Only for use in setting up/tearing down injector; other users should use schemaFactory.
-  @Inject private InMemoryDatabase inMemoryDatabase;
-
   protected Injector injector;
   protected LifecycleManager lifecycle;
   protected ReviewDb db;
@@ -234,9 +220,7 @@
   protected void initAfterLifecycleStart() throws Exception {}
 
   protected void setUpDatabase() throws Exception {
-    try (ReviewDb underlyingDb = inMemoryDatabase.getDatabase().open()) {
-      schemaCreator.create(underlyingDb);
-    }
+    schemaCreator.create();
     db = schemaFactory.open();
 
     userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
@@ -279,7 +263,6 @@
     if (db != null) {
       db.close();
     }
-    InMemoryDatabase.drop(inMemoryDatabase);
   }
 
   @Before
@@ -577,7 +560,6 @@
   @Test
   public void restorePendingReviewers() throws Exception {
     assume().that(getSchemaVersion()).isAtLeast(44);
-    assume().that(notesMigration.readChanges()).isTrue();
 
     Project.NameKey project = new Project.NameKey("repo");
     TestRepository<Repo> repo = createProject(project.get());
@@ -1604,8 +1586,7 @@
   }
 
   @Test
-  public void byHashtagWithNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
+  public void byHashtag() throws Exception {
     List<Change> changes = setUpHashtagChanges();
     assertQuery("hashtag:foo", changes.get(1), changes.get(0));
     assertQuery("hashtag:bar", changes.get(1));
@@ -1617,35 +1598,6 @@
   }
 
   @Test
-  public void byHashtagWithoutNoteDb() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-
-    notesMigration.setWriteChanges(true);
-    notesMigration.setReadChanges(true);
-    db.close();
-    db = schemaFactory.open();
-    List<Change> changes;
-    try {
-      changes = setUpHashtagChanges();
-      notesMigration.setWriteChanges(false);
-      notesMigration.setReadChanges(false);
-    } finally {
-      db.close();
-    }
-    db = schemaFactory.open();
-    for (Change c : changes) {
-      indexer.index(db, c); // Reindex without hashtag field.
-    }
-    assertQuery("hashtag:foo");
-    assertQuery("hashtag:bar");
-    assertQuery("hashtag:\" bar \"");
-    assertQuery("hashtag:\"a tag\"");
-    assertQuery("hashtag:\" a tag \"");
-    assertQuery("hashtag:#foo");
-    assertQuery("hashtag:\"# #foo\"");
-  }
-
-  @Test
   public void byDefault() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
 
@@ -1709,6 +1661,10 @@
         accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
     assertQuery(q + " visibleto:" + user2.get(), change1);
 
+    String g1 = createGroup("group1", "Administrators");
+    gApi.groups().id(g1).addMembers("anotheruser");
+    assertQuery(q + " visibleto:" + g1, change1);
+
     requestContext.setContext(
         newRequestContext(
             accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId()));
@@ -1772,8 +1728,6 @@
 
   @Test
   public void byDraftByExcludesZombieDrafts() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     Project.NameKey project = new Project.NameKey("repo");
     TestRepository<Repo> repo = createProject(project.get());
     Change change = insert(repo, newChange(repo));
@@ -1805,18 +1759,6 @@
     allUsers.update(draftsRef.getName(), draftsRef.getObjectId());
     assertThat(allUsers.getRepository().exactRef(draftsRef.getName())).isNotNull();
 
-    if (PrimaryStorage.of(change) == PrimaryStorage.REVIEW_DB
-        && !notesMigration.disableChangeReviewDb()) {
-      // Record draft ref in noteDbState as well.
-      ReviewDb db = ReviewDbUtil.unwrapDb(this.db);
-      change = db.changes().get(id);
-      NoteDbChangeState.applyDelta(
-          change,
-          NoteDbChangeState.Delta.create(
-              id, Optional.empty(), ImmutableMap.of(userId, draftsRef.getObjectId())));
-      db.changes().update(Collections.singleton(change));
-    }
-
     indexer.index(db, project, id);
     assertQuery("draftby:" + userId);
   }
@@ -2031,17 +1973,10 @@
     assertQuery("reviewer:self", change3);
 
     requestContext.setContext(newRequestContext(user1));
-    if (notesMigration.readChanges()) {
-      assertQuery("reviewer:" + user1, change1);
-      assertQuery("cc:" + user1, change2);
-      assertQuery("is:cc", change2);
-      assertQuery("cc:self", change2);
-    } else {
-      assertQuery("reviewer:" + user1, change2, change1);
-      assertQuery("cc:" + user1);
-      assertQuery("is:cc");
-      assertQuery("cc:self");
-    }
+    assertQuery("reviewer:" + user1, change1);
+    assertQuery("cc:" + user1, change2);
+    assertQuery("is:cc", change2);
+    assertQuery("cc:self", change2);
   }
 
   @Test
@@ -2104,36 +2039,19 @@
             .collect(toList());
     assertThat(members).contains(user2.toString());
 
-    if (notesMigration.readChanges()) {
-      // CC and REVIEWER are separate in NoteDB
-      assertQuery("reviewerin:\"Registered Users\"", change2, change1);
-      assertQuery("reviewerin:" + group, change2);
-    } else {
-      // CC and REVIEWER are the same in ReviewDb
-      assertQuery("reviewerin:\"Registered Users\"", change3, change2, change1);
-      assertQuery("reviewerin:" + group, change3, change2);
-    }
+    assertQuery("reviewerin:\"Registered Users\"", change2, change1);
+    assertQuery("reviewerin:" + group, change2);
 
     gApi.changes().id(change2.getId().get()).current().review(ReviewInput.approve());
     gApi.changes().id(change2.getId().get()).current().submit();
 
-    if (notesMigration.readChanges()) {
-      // CC and REVIEWER are separate in NoteDB
-      assertQuery("reviewerin:" + group, change2);
-      assertQuery("project:repo reviewerin:" + group, change2);
-      assertQuery("status:merged reviewerin:" + group, change2);
-    } else {
-      // CC and REVIEWER are the same in ReviewDb
-      assertQuery("reviewerin:" + group, change2, change3);
-      assertQuery("project:repo reviewerin:" + group, change2, change3);
-      assertQuery("status:merged reviewerin:" + group, change2);
-    }
+    assertQuery("reviewerin:" + group, change2);
+    assertQuery("project:repo reviewerin:" + group, change2);
+    assertQuery("status:merged reviewerin:" + group, change2);
   }
 
   @Test
   public void reviewerAndCcByEmail() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     Project.NameKey project = new Project.NameKey("repo");
     TestRepository<Repo> repo = createProject(project.get());
     ConfigInput conf = new ConfigInput();
@@ -2180,8 +2098,6 @@
 
   @Test
   public void reviewerAndCcByEmailWithQueryForDifferentUser() throws Exception {
-    assume().that(notesMigration.readChanges()).isTrue();
-
     Project.NameKey project = new Project.NameKey("repo");
     TestRepository<Repo> repo = createProject(project.get());
     ConfigInput conf = new ConfigInput();
@@ -2354,71 +2270,6 @@
   }
 
   @Test
-  public void prepopulatedFields() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo));
-
-    db = new DisabledReviewDb();
-    requestContext.setContext(newRequestContext(userId));
-    // Use QueryProcessor directly instead of API so we get ChangeDatas back.
-    List<ChangeData> cds =
-        queryProcessorProvider
-            .get()
-            .query(queryBuilder.parse(change.getId().toString()))
-            .entities();
-    assertThat(cds).hasSize(1);
-
-    ChangeData cd = cds.get(0);
-    cd.change();
-    cd.patchSets();
-    cd.currentApprovals();
-    cd.changedLines();
-    cd.reviewedBy();
-    cd.reviewers();
-    cd.unresolvedCommentCount();
-
-    if (getSchemaVersion() < 51) {
-      assertMissingField(ChangeField.TOTAL_COMMENT_COUNT);
-    } else {
-      cd.totalCommentCount();
-    }
-
-    // TODO(dborowitz): Swap out GitRepositoryManager somehow? Will probably be
-    // necessary for NoteDb anyway.
-    cd.isMergeable();
-
-    exception.expect(DisabledReviewDb.Disabled.class);
-    cd.messages();
-  }
-
-  @Test
-  public void prepopulateOnlyRequestedFields() throws Exception {
-    assume().that(notesMigration.readChanges()).isFalse();
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo));
-
-    db = new DisabledReviewDb();
-    requestContext.setContext(newRequestContext(userId));
-    // Use QueryProcessor directly instead of API so we get ChangeDatas back.
-    List<ChangeData> cds =
-        queryProcessorProvider
-            .get()
-            .setRequestedFields(
-                ImmutableSet.of(ChangeField.PATCH_SET.getName(), ChangeField.CHANGE.getName()))
-            .query(queryBuilder.parse(change.getId().toString()))
-            .entities();
-    assertThat(cds).hasSize(1);
-
-    ChangeData cd = cds.get(0);
-    cd.change();
-    cd.patchSets();
-
-    exception.expect(DisabledReviewDb.Disabled.class);
-    cd.currentApprovals();
-  }
-
-  @Test
   public void reindexIfStale() throws Exception {
     Account.Id user = createAccount("user");
     Project.NameKey project = new Project.NameKey("repo");
@@ -2426,7 +2277,7 @@
     Change change = insert(repo, newChange(repo));
     String changeId = change.getKey().get();
     ChangeNotes notes = notesFactory.create(db, change.getProject(), change.getId());
-    PatchSet ps = psUtil.get(db, notes, change.currentPatchSetId());
+    PatchSet ps = psUtil.get(notes, change.currentPatchSetId());
 
     requestContext.setContext(newRequestContext(user));
     gApi.changes().id(changeId).edit().create();
@@ -2446,93 +2297,6 @@
   }
 
   @Test
-  public void refStateFields() throws Exception {
-    // This test method manages primary storage manually.
-    assume().that(notesMigration.changePrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
-    Account.Id user = createAccount("user");
-    Project.NameKey project = new Project.NameKey("repo");
-    TestRepository<Repo> repo = createProject(project.get());
-    String path = "file";
-    RevCommit commit = repo.parseBody(repo.commit().message("one").add(path, "contents").create());
-    Change change = insert(repo, newChangeForCommit(repo, commit));
-    Change.Id id = change.getId();
-    int c = id.get();
-    String changeId = change.getKey().get();
-    requestContext.setContext(newRequestContext(user));
-
-    // Ensure one of each type of supported ref is present for the change. If
-    // any more refs are added, update this test to reflect them.
-
-    // Edit
-    gApi.changes().id(changeId).edit().create();
-
-    // Star
-    gApi.accounts().self().starChange(change.getId().toString());
-
-    if (notesMigration.readChanges()) {
-      // Robot comment.
-      ReviewInput rin = new ReviewInput();
-      RobotCommentInput rcin = new RobotCommentInput();
-      rcin.robotId = "happyRobot";
-      rcin.robotRunId = "1";
-      rcin.line = 1;
-      rcin.message = "nit: trailing whitespace";
-      rcin.path = path;
-      rin.robotComments = ImmutableMap.of(path, ImmutableList.of(rcin));
-      gApi.changes().id(c).current().review(rin);
-    }
-
-    // Draft.
-    DraftInput din = new DraftInput();
-    din.path = path;
-    din.line = 1;
-    din.message = "draft";
-    gApi.changes().id(c).current().createDraft(din);
-
-    if (notesMigration.readChanges()) {
-      // Force NoteDb primary.
-      change = ReviewDbUtil.unwrapDb(db).changes().get(id);
-      change.setNoteDbState(NoteDbChangeState.NOTE_DB_PRIMARY_STATE);
-      ReviewDbUtil.unwrapDb(db).changes().update(Collections.singleton(change));
-      indexer.index(db, change);
-    }
-
-    QueryOptions opts =
-        IndexedChangeQuery.createOptions(indexConfig, 0, 1, StalenessChecker.FIELDS);
-    ChangeData cd = indexes.getSearchIndex().get(id, opts).get();
-
-    String cs = RefNames.shard(c);
-    int u = user.get();
-    String us = RefNames.shard(u);
-
-    List<String> expectedStates =
-        Lists.newArrayList(
-            "repo:refs/users/" + us + "/edit-" + c + "/1",
-            "All-Users:refs/starred-changes/" + cs + "/" + u);
-    if (notesMigration.readChanges()) {
-      expectedStates.add("repo:refs/changes/" + cs + "/meta");
-      expectedStates.add("repo:refs/changes/" + cs + "/robot-comments");
-      expectedStates.add("All-Users:refs/draft-comments/" + cs + "/" + u);
-    }
-    assertThat(
-            cd.getRefStates()
-                .stream()
-                .map(String::new)
-                // Omit SHA-1, we're just concerned with the project/ref names.
-                .map(s -> s.substring(0, s.lastIndexOf(':')))
-                .collect(toList()))
-        .containsExactlyElementsIn(expectedStates);
-
-    List<String> expectedPatterns = Lists.newArrayList("repo:refs/users/*/edit-" + c + "/*");
-    expectedPatterns.add("All-Users:refs/starred-changes/" + cs + "/*");
-    if (notesMigration.readChanges()) {
-      expectedPatterns.add("All-Users:refs/draft-comments/" + cs + "/*");
-    }
-    assertThat(cd.getRefStatePatterns().stream().map(String::new).collect(toList()))
-        .containsExactlyElementsIn(expectedPatterns);
-  }
-
-  @Test
   public void watched() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
diff --git a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index 7e4a0a4..2906409 100644
--- a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -52,7 +52,7 @@
 import com.google.gerrit.server.index.group.GroupField;
 import com.google.gerrit.server.index.group.GroupIndex;
 import com.google.gerrit.server.index.group.GroupIndexCollection;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
@@ -91,7 +91,7 @@
 
   @Inject protected InMemoryDatabase schemaFactory;
 
-  @Inject protected ReviewDbSchemaCreator schemaCreator;
+  @Inject protected SchemaCreator schemaCreator;
 
   @Inject protected ThreadLocalRequestContext requestContext;
 
@@ -134,7 +134,7 @@
 
   protected void setUpDatabase() throws Exception {
     db = schemaFactory.open();
-    schemaCreator.create(db);
+    schemaCreator.create();
 
     Account.Id userId =
         createAccountOutsideRequestContext("user", "User", "user@example.com", true);
@@ -186,7 +186,6 @@
     if (db != null) {
       db.close();
     }
-    InMemoryDatabase.drop(schemaFactory);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
index 3219dfd..4750353 100644
--- a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -50,7 +50,7 @@
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.schema.ReviewDbSchemaCreator;
+import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
@@ -88,7 +88,7 @@
 
   @Inject protected InMemoryDatabase schemaFactory;
 
-  @Inject protected ReviewDbSchemaCreator schemaCreator;
+  @Inject protected SchemaCreator schemaCreator;
 
   @Inject protected ThreadLocalRequestContext requestContext;
 
@@ -125,7 +125,7 @@
 
   protected void setUpDatabase() throws Exception {
     db = schemaFactory.open();
-    schemaCreator.create(db);
+    schemaCreator.create();
 
     Account.Id userId = createAccount("user", "User", "user@example.com", true);
     user = userFactory.create(userId);
@@ -174,7 +174,6 @@
     if (db != null) {
       db.close();
     }
-    InMemoryDatabase.drop(schemaFactory);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/schema/GroupBundleTest.java b/javatests/com/google/gerrit/server/schema/GroupBundleTest.java
deleted file mode 100644
index c1de3a3..0000000
--- a/javatests/com/google/gerrit/server/schema/GroupBundleTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.server.schema.GroupBundle.Source;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
-import com.google.gerrit.testing.TestTimeUtil;
-import java.sql.Timestamp;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class GroupBundleTest extends GerritBaseTests {
-  // This class just contains sanity checks that GroupBundle#compare correctly compares all parts of
-  // the bundle. Most other test coverage should come via the slightly more realistic
-  // GroupRebuilderTest.
-
-  private static final String TIMEZONE_ID = "US/Eastern";
-
-  private String systemTimeZoneProperty;
-  private TimeZone systemTimeZone;
-  private Timestamp ts;
-
-  @Before
-  public void setUp() {
-    systemTimeZoneProperty = System.setProperty("user.timezone", TIMEZONE_ID);
-    systemTimeZone = TimeZone.getDefault();
-    TimeZone.setDefault(TimeZone.getTimeZone(TIMEZONE_ID));
-    TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
-    ts = TimeUtil.nowTs();
-  }
-
-  @After
-  public void tearDown() {
-    TestTimeUtil.useSystemTime();
-    System.setProperty("user.timezone", systemTimeZoneProperty);
-    TimeZone.setDefault(systemTimeZone);
-  }
-
-  @Test
-  public void compareNonEqual() throws Exception {
-    GroupBundle reviewDbBundle = newBundle().source(Source.REVIEW_DB).build();
-    AccountGroup g2 = new AccountGroup(reviewDbBundle.group());
-    g2.setDescription("Hello!");
-    GroupBundle noteDbBundle = GroupBundle.builder().source(Source.NOTE_DB).group(g2).build();
-    assertThat(GroupBundle.compareWithAudits(reviewDbBundle, noteDbBundle))
-        .containsExactly(
-            "AccountGroups differ\n"
-                + ("ReviewDb: AccountGroup{name=group, groupId=1, description=null,"
-                    + " visibleToAll=false, groupUUID=group-1, ownerGroupUUID=group-1,"
-                    + " createdOn=2009-09-30 17:00:00.0}\n")
-                + ("NoteDb  : AccountGroup{name=group, groupId=1, description=Hello!,"
-                    + " visibleToAll=false, groupUUID=group-1, ownerGroupUUID=group-1,"
-                    + " createdOn=2009-09-30 17:00:00.0}"),
-            "AccountGroupMembers differ\n"
-                + "ReviewDb: [AccountGroupMember{key=1000,1}]\n"
-                + "NoteDb  : []",
-            "AccountGroupMemberAudits differ\n"
-                + ("ReviewDb: [AccountGroupMemberAudit{key=Key{groupId=1, accountId=1000,"
-                    + " addedOn=2009-09-30 17:00:00.0}, addedBy=2000, removedBy=null,"
-                    + " removedOn=null}]\n")
-                + "NoteDb  : []",
-            "AccountGroupByIds differ\n"
-                + "ReviewDb: [AccountGroupById{key=1,subgroup}]\n"
-                + "NoteDb  : []",
-            "AccountGroupByIdAudits differ\n"
-                + ("ReviewDb: [AccountGroupByIdAud{key=Key{groupId=1, includeUUID=subgroup,"
-                    + " addedOn=2009-09-30 17:00:00.0}, addedBy=3000, removedBy=null,"
-                    + " removedOn=null}]\n")
-                + "NoteDb  : []");
-  }
-
-  @Test
-  public void compareIgnoreAudits() throws Exception {
-    GroupBundle reviewDbBundle = newBundle().source(Source.REVIEW_DB).build();
-    AccountGroup group = new AccountGroup(reviewDbBundle.group());
-
-    AccountGroupMember member =
-        new AccountGroupMember(new AccountGroupMember.Key(new Account.Id(1), group.getId()));
-    AccountGroupMemberAudit memberAudit =
-        new AccountGroupMemberAudit(member, new Account.Id(2), ts);
-    AccountGroupById byId =
-        new AccountGroupById(
-            new AccountGroupById.Key(group.getId(), new AccountGroup.UUID("subgroup-2")));
-    AccountGroupByIdAud byIdAudit = new AccountGroupByIdAud(byId, new Account.Id(3), ts);
-
-    GroupBundle noteDbBundle =
-        newBundle().source(Source.NOTE_DB).memberAudit(memberAudit).byIdAudit(byIdAudit).build();
-
-    assertThat(GroupBundle.compareWithAudits(reviewDbBundle, noteDbBundle)).isNotEmpty();
-    assertThat(GroupBundle.compareWithoutAudits(reviewDbBundle, noteDbBundle)).isEmpty();
-  }
-
-  @Test
-  public void compareEqual() throws Exception {
-    GroupBundle reviewDbBundle = newBundle().source(Source.REVIEW_DB).build();
-    GroupBundle noteDbBundle = newBundle().source(Source.NOTE_DB).build();
-    assertThat(GroupBundle.compareWithAudits(reviewDbBundle, noteDbBundle)).isEmpty();
-  }
-
-  private GroupBundle.Builder newBundle() {
-    AccountGroup group =
-        new AccountGroup(
-            new AccountGroup.NameKey("group"),
-            new AccountGroup.Id(1),
-            new AccountGroup.UUID("group-1"),
-            ts);
-    AccountGroupMember member =
-        new AccountGroupMember(new AccountGroupMember.Key(new Account.Id(1000), group.getId()));
-    AccountGroupMemberAudit memberAudit =
-        new AccountGroupMemberAudit(member, new Account.Id(2000), ts);
-    AccountGroupById byId =
-        new AccountGroupById(
-            new AccountGroupById.Key(group.getId(), new AccountGroup.UUID("subgroup")));
-    AccountGroupByIdAud byIdAudit = new AccountGroupByIdAud(byId, new Account.Id(3000), ts);
-    return GroupBundle.builder()
-        .group(group)
-        .members(member)
-        .memberAudit(memberAudit)
-        .byId(byId)
-        .byIdAudit(byIdAudit);
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java b/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
deleted file mode 100644
index 6a8a55a..0000000
--- a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
+++ /dev/null
@@ -1,747 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
-import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.assertThat;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_GROUPNAMES;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.GroupDescription;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.git.RefUpdateUtil;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupById;
-import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
-import com.google.gerrit.reviewdb.client.AccountGroupMember;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.AllUsersNameProvider;
-import com.google.gerrit.server.group.db.AuditLogFormatter;
-import com.google.gerrit.server.group.db.AuditLogReader;
-import com.google.gerrit.server.group.db.GroupNameNotes;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.GerritBaseTests;
-import com.google.gerrit.testing.GitTestUtil;
-import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.TestTimeUtil;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
-import java.sql.Timestamp;
-import java.util.Optional;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.IntStream;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class GroupRebuilderTest extends GerritBaseTests {
-  private static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles");
-  private static final String SERVER_ID = "server-id";
-  private static final String SERVER_NAME = "Gerrit Server";
-  private static final String SERVER_EMAIL = "noreply@gerritcodereview.com";
-
-  private AtomicInteger idCounter;
-  private AllUsersName allUsersName;
-  private Repository repo;
-  private GroupRebuilder rebuilder;
-  private GroupBundle.Factory bundleFactory;
-
-  @Before
-  public void setUp() throws Exception {
-    TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
-    idCounter = new AtomicInteger();
-    allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
-    repo = new InMemoryRepositoryManager().createRepository(allUsersName);
-    rebuilder =
-        new GroupRebuilder(
-            GroupRebuilderTest.newPersonIdent(),
-            allUsersName,
-            // Note that the expected name/email values in tests are not necessarily realistic,
-            // since they use these trivial name/email functions.
-            getAuditLogFormatter());
-    bundleFactory = new GroupBundle.Factory(new AuditLogReader(SERVER_ID, allUsersName));
-  }
-
-  @After
-  public void tearDown() {
-    TestTimeUtil.useSystemTime();
-  }
-
-  @Test
-  public void minimalGroupFields() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(1);
-    assertCommit(log.get(0), "Create group", SERVER_NAME, SERVER_EMAIL);
-    assertThat(logGroupNames()).isEmpty();
-  }
-
-  @Test
-  public void allGroupFields() throws Exception {
-    AccountGroup g = newGroup("a");
-    g.setDescription("Description");
-    g.setOwnerGroupUUID(new AccountGroup.UUID("owner"));
-    g.setVisibleToAll(true);
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(1);
-    assertServerCommit(log.get(0), "Create group");
-  }
-
-  @Test
-  public void emptyGroupName() throws Exception {
-    AccountGroup g = newGroup("");
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    GroupBundle noteDbBundle = reload(g);
-    assertMigratedCleanly(noteDbBundle, b);
-    assertThat(noteDbBundle.group().getName()).isEmpty();
-  }
-
-  @Test
-  public void nullGroupDescription() throws Exception {
-    AccountGroup g = newGroup("a");
-    g.setDescription(null);
-    assertThat(g.getDescription()).isNull();
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    GroupBundle noteDbBundle = reload(g);
-    assertMigratedCleanly(noteDbBundle, b);
-    assertThat(noteDbBundle.group().getDescription()).isNull();
-  }
-
-  @Test
-  public void emptyGroupDescription() throws Exception {
-    AccountGroup g = newGroup("a");
-    g.setDescription("");
-    assertThat(g.getDescription()).isEmpty();
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    GroupBundle noteDbBundle = reload(g);
-    assertMigratedCleanly(noteDbBundle, b);
-    assertThat(noteDbBundle.group().getDescription()).isNull();
-  }
-
-  @Test
-  public void membersAndSubgroups() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1), member(g, 2))
-            .byId(byId(g, "x"), byId(g, "y"))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(2);
-    assertServerCommit(log.get(0), "Create group");
-    assertServerCommit(
-        log.get(1),
-        "Update group\n"
-            + "\n"
-            + "Add-group: Group x <x>\n"
-            + "Add-group: Group y <y>\n"
-            + "Add: Account 1 <1@server-id>\n"
-            + "Add: Account 2 <2@server-id>");
-  }
-
-  @Test
-  public void memberAudit() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1))
-            .memberAudit(addMember(g, 1, 8, t2), addAndRemoveMember(g, 2, 8, t1, 9, t3))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(4);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1), "Update group\n\nAdd: Account 2 <2@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nRemove: Account 2 <2@server-id>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void memberAuditLegacyRemoved() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 2))
-            .memberAudit(
-                addAndLegacyRemoveMember(g, 1, 8, TimeUtil.nowTs()),
-                addMember(g, 2, 8, TimeUtil.nowTs()))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(4);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nAdd: Account 2 <2@server-id>", "Account 8", "8@server-id");
-  }
-
-  @Test
-  public void unauditedMembershipsAddedAtEnd() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1), member(g, 2), member(g, 3))
-            .memberAudit(addMember(g, 1, 8, TimeUtil.nowTs()))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(3);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertServerCommit(
-        log.get(2), "Update group\n\nAdd: Account 2 <2@server-id>\nAdd: Account 3 <3@server-id>");
-  }
-
-  @Test
-  public void byIdAudit() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .byId(byId(g, "x"))
-            .byIdAudit(addById(g, "x", 8, t2), addAndRemoveById(g, "y", 8, t1, 9, t3))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(4);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(log.get(1), "Update group\n\nAdd-group: Group y <y>", "Account 8", "8@server-id");
-    assertCommit(log.get(2), "Update group\n\nAdd-group: Group x <x>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nRemove-group: Group y <y>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void unauditedByIdAddedAtEnd() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b =
-        builder()
-            .group(g)
-            .byId(byId(g, "x"), byId(g, "y"), byId(g, "z"))
-            .byIdAudit(addById(g, "x", 8, TimeUtil.nowTs()))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(3);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(log.get(1), "Update group\n\nAdd-group: Group x <x>", "Account 8", "8@server-id");
-    assertServerCommit(
-        log.get(2), "Update group\n\nAdd-group: Group y <y>\nAdd-group: Group z <z>");
-  }
-
-  @Test
-  public void auditsAtSameTimestampBrokenDownByType() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp ts = TimeUtil.nowTs();
-    int user = 8;
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1), member(g, 2))
-            .memberAudit(
-                addMember(g, 1, user, ts),
-                addMember(g, 2, user, ts),
-                addAndRemoveMember(g, 3, user, ts, user, ts))
-            .byId(byId(g, "x"), byId(g, "y"))
-            .byIdAudit(
-                addById(g, "x", user, ts),
-                addById(g, "y", user, ts),
-                addAndRemoveById(g, "z", user, ts, user, ts))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(5);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1),
-        "Update group\n"
-            + "\n"
-            + "Add: Account 1 <1@server-id>\n"
-            + "Add: Account 2 <2@server-id>\n"
-            + "Add: Account 3 <3@server-id>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove: Account 3 <3@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3),
-        "Update group\n"
-            + "\n"
-            + "Add-group: Group x <x>\n"
-            + "Add-group: Group y <y>\n"
-            + "Add-group: Group z <z>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(4), "Update group\n\nRemove-group: Group z <z>", "Account 8", "8@server-id");
-  }
-
-  @Test
-  public void auditsAtSameTimestampBrokenDownByUserAndType() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp ts = TimeUtil.nowTs();
-    int user1 = 8;
-    int user2 = 9;
-
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1), member(g, 2), member(g, 3))
-            .memberAudit(
-                addMember(g, 1, user1, ts), addMember(g, 2, user2, ts), addMember(g, 3, user1, ts))
-            .byId(byId(g, "x"), byId(g, "y"), byId(g, "z"))
-            .byIdAudit(
-                addById(g, "x", user1, ts), addById(g, "y", user2, ts), addById(g, "z", user1, ts))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(5);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1),
-        "Update group\n" + "\n" + "Add: Account 1 <1@server-id>\n" + "Add: Account 3 <3@server-id>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(2),
-        "Update group\n\nAdd-group: Group x <x>\nAdd-group: Group z <z>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nAdd: Account 2 <2@server-id>", "Account 9", "9@server-id");
-    assertCommit(log.get(4), "Update group\n\nAdd-group: Group y <y>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void fixupCommitPostDatesAllAuditEventsEvenIfAuditEventsAreInTheFuture() throws Exception {
-    AccountGroup g = newGroup("a");
-    IntStream.range(0, 20).forEach(i -> TimeUtil.nowTs());
-    Timestamp future = TimeUtil.nowTs();
-    TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
-
-    GroupBundle b =
-        builder()
-            .group(g)
-            .byId(byId(g, "x"), byId(g, "y"), byId(g, "z"))
-            .byIdAudit(addById(g, "x", 8, future))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(3);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(log.get(1), "Update group\n\nAdd-group: Group x <x>", "Account 8", "8@server-id");
-    assertServerCommit(
-        log.get(2), "Update group\n\nAdd-group: Group y <y>\nAdd-group: Group z <z>");
-
-    assertThat(log.stream().map(c -> c.committer.date).collect(toImmutableList()))
-        .named("%s", log)
-        .isOrdered();
-    assertThat(TimeUtil.nowTs()).isLessThan(future);
-  }
-
-  @Test
-  public void redundantMemberAuditsAreIgnored() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    Timestamp t4 = TimeUtil.nowTs();
-    Timestamp t5 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 2))
-            .memberAudit(
-                addMember(g, 1, 8, t1),
-                addMember(g, 1, 8, t1),
-                addMember(g, 1, 8, t3),
-                addMember(g, 1, 9, t4),
-                addAndRemoveMember(g, 1, 8, t2, 9, t5),
-                addAndLegacyRemoveMember(g, 2, 9, t3),
-                addMember(g, 2, 8, t1),
-                addMember(g, 2, 9, t4),
-                addMember(g, 1, 8, t5))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(5);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1),
-        "Update group\n\nAdd: Account 1 <1@server-id>\nAdd: Account 2 <2@server-id>",
-        "Account 8",
-        "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove: Account 2 <2@server-id>", "Account 9", "9@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nAdd: Account 2 <2@server-id>", "Account 9", "9@server-id");
-    assertCommit(
-        log.get(4), "Update group\n\nRemove: Account 1 <1@server-id>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void additionsAndRemovalsWithinSameSecondCanBeMigrated() throws Exception {
-    TestTimeUtil.resetWithClockStep(1, TimeUnit.MILLISECONDS);
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    Timestamp t4 = TimeUtil.nowTs();
-    Timestamp t5 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .members(member(g, 1))
-            .memberAudit(
-                addAndLegacyRemoveMember(g, 1, 8, t1),
-                addMember(g, 1, 10, t2),
-                addAndRemoveMember(g, 1, 8, t3, 9, t4),
-                addMember(g, 1, 8, t5))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(6);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(
-        log.get(1), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove: Account 1 <1@server-id>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(3), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 10", "10@server-id");
-    assertCommit(
-        log.get(4), "Update group\n\nRemove: Account 1 <1@server-id>", "Account 9", "9@server-id");
-    assertCommit(
-        log.get(5), "Update group\n\nAdd: Account 1 <1@server-id>", "Account 8", "8@server-id");
-  }
-
-  @Test
-  public void redundantByIdAuditsAreIgnored() throws Exception {
-    AccountGroup g = newGroup("a");
-    Timestamp t1 = TimeUtil.nowTs();
-    Timestamp t2 = TimeUtil.nowTs();
-    Timestamp t3 = TimeUtil.nowTs();
-    Timestamp t4 = TimeUtil.nowTs();
-    Timestamp t5 = TimeUtil.nowTs();
-    GroupBundle b =
-        builder()
-            .group(g)
-            .byId()
-            .byIdAudit(
-                addById(g, "x", 8, t1),
-                addById(g, "x", 8, t3),
-                addById(g, "x", 9, t4),
-                addAndRemoveById(g, "x", 8, t2, 9, t5))
-            .build();
-
-    rebuilder.rebuild(repo, b, null);
-
-    assertMigratedCleanly(reload(g), b);
-    ImmutableList<CommitInfo> log = log(g);
-    assertThat(log).hasSize(3);
-    assertServerCommit(log.get(0), "Create group");
-    assertCommit(log.get(1), "Update group\n\nAdd-group: Group x <x>", "Account 8", "8@server-id");
-    assertCommit(
-        log.get(2), "Update group\n\nRemove-group: Group x <x>", "Account 9", "9@server-id");
-  }
-
-  @Test
-  public void combineWithBatchGroupNameNotes() throws Exception {
-    AccountGroup g1 = newGroup("a");
-    AccountGroup g2 = newGroup("b");
-    GroupReference gr1 = new GroupReference(g1.getGroupUUID(), g1.getName());
-    GroupReference gr2 = new GroupReference(g2.getGroupUUID(), g2.getName());
-
-    GroupBundle b1 = builder().group(g1).build();
-    GroupBundle b2 = builder().group(g2).build();
-
-    BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
-
-    rebuilder.rebuild(repo, b1, bru);
-    rebuilder.rebuild(repo, b2, bru);
-    try (ObjectInserter inserter = repo.newObjectInserter()) {
-      ImmutableList<GroupReference> refs = ImmutableList.of(gr1, gr2);
-      GroupNameNotes.updateAllGroups(repo, inserter, bru, refs, newPersonIdent());
-      inserter.flush();
-    }
-
-    assertThat(log(g1)).isEmpty();
-    assertThat(log(g2)).isEmpty();
-    assertThat(logGroupNames()).isEmpty();
-
-    RefUpdateUtil.executeChecked(bru, repo);
-
-    assertThat(log(g1)).hasSize(1);
-    assertThat(log(g2)).hasSize(1);
-    assertThat(logGroupNames()).hasSize(1);
-    assertMigratedCleanly(reload(g1), b1);
-    assertMigratedCleanly(reload(g2), b2);
-
-    assertThat(GroupNameNotes.loadAllGroups(repo)).containsExactly(gr1, gr2);
-  }
-
-  @Test
-  public void groupNamesWithLeadingAndTrailingWhitespace() throws Exception {
-    for (String leading : ImmutableList.of("", " ", "  ")) {
-      for (String trailing : ImmutableList.of("", " ", "  ")) {
-        AccountGroup g = newGroup(leading + "a" + trailing);
-        GroupBundle b = builder().group(g).build();
-        rebuilder.rebuild(repo, b, null);
-        assertMigratedCleanly(reload(g), b);
-      }
-    }
-  }
-
-  @Test
-  public void disallowExisting() throws Exception {
-    AccountGroup g = newGroup("a");
-    GroupBundle b = builder().group(g).build();
-
-    rebuilder.rebuild(repo, b, null);
-    assertMigratedCleanly(reload(g), b);
-    String refName = RefNames.refsGroups(g.getGroupUUID());
-    ObjectId oldId = repo.exactRef(refName).getObjectId();
-
-    try {
-      rebuilder.rebuild(repo, b, null);
-      assert_().fail("expected OrmDuplicateKeyException");
-    } catch (OrmDuplicateKeyException e) {
-      // Expected.
-    }
-
-    assertThat(repo.exactRef(refName).getObjectId()).isEqualTo(oldId);
-  }
-
-  private GroupBundle reload(AccountGroup g) throws Exception {
-    return bundleFactory.fromNoteDb(allUsersName, repo, g.getGroupUUID());
-  }
-
-  private void assertMigratedCleanly(GroupBundle noteDbBundle, GroupBundle expectedReviewDbBundle) {
-    assertThat(GroupBundle.compareWithAudits(expectedReviewDbBundle, noteDbBundle)).isEmpty();
-  }
-
-  private AccountGroup newGroup(String name) {
-    int id = idCounter.incrementAndGet();
-    return new AccountGroup(
-        new AccountGroup.NameKey(name),
-        new AccountGroup.Id(id),
-        new AccountGroup.UUID(name.trim() + "-" + id),
-        TimeUtil.nowTs());
-  }
-
-  private AccountGroupMember member(AccountGroup g, int accountId) {
-    return new AccountGroupMember(new AccountGroupMember.Key(new Account.Id(accountId), g.getId()));
-  }
-
-  private AccountGroupMemberAudit addMember(
-      AccountGroup g, int accountId, int adder, Timestamp addedOn) {
-    return new AccountGroupMemberAudit(member(g, accountId), new Account.Id(adder), addedOn);
-  }
-
-  private AccountGroupMemberAudit addAndLegacyRemoveMember(
-      AccountGroup g, int accountId, int adder, Timestamp addedOn) {
-    AccountGroupMemberAudit a = addMember(g, accountId, adder, addedOn);
-    a.removedLegacy();
-    return a;
-  }
-
-  private AccountGroupMemberAudit addAndRemoveMember(
-      AccountGroup g,
-      int accountId,
-      int adder,
-      Timestamp addedOn,
-      int removedBy,
-      Timestamp removedOn) {
-    AccountGroupMemberAudit a = addMember(g, accountId, adder, addedOn);
-    a.removed(new Account.Id(removedBy), removedOn);
-    return a;
-  }
-
-  private AccountGroupByIdAud addById(
-      AccountGroup g, String subgroupUuid, int adder, Timestamp addedOn) {
-    return new AccountGroupByIdAud(byId(g, subgroupUuid), new Account.Id(adder), addedOn);
-  }
-
-  private AccountGroupByIdAud addAndRemoveById(
-      AccountGroup g,
-      String subgroupUuid,
-      int adder,
-      Timestamp addedOn,
-      int removedBy,
-      Timestamp removedOn) {
-    AccountGroupByIdAud a = addById(g, subgroupUuid, adder, addedOn);
-    a.removed(new Account.Id(removedBy), removedOn);
-    return a;
-  }
-
-  private AccountGroupById byId(AccountGroup g, String subgroupUuid) {
-    return new AccountGroupById(
-        new AccountGroupById.Key(g.getId(), new AccountGroup.UUID(subgroupUuid)));
-  }
-
-  private ImmutableList<CommitInfo> log(AccountGroup g) throws Exception {
-    return GitTestUtil.log(repo, RefNames.refsGroups(g.getGroupUUID()));
-  }
-
-  private ImmutableList<CommitInfo> logGroupNames() throws Exception {
-    return GitTestUtil.log(repo, REFS_GROUPNAMES);
-  }
-
-  private static GroupBundle.Builder builder() {
-    return GroupBundle.builder().source(GroupBundle.Source.REVIEW_DB);
-  }
-
-  private static PersonIdent newPersonIdent() {
-    return new PersonIdent(SERVER_NAME, SERVER_EMAIL, TimeUtil.nowTs(), TZ);
-  }
-
-  private static void assertServerCommit(CommitInfo commitInfo, String expectedMessage) {
-    assertCommit(commitInfo, expectedMessage, SERVER_NAME, SERVER_EMAIL);
-  }
-
-  private static void assertCommit(
-      CommitInfo commitInfo, String expectedMessage, String expectedName, String expectedEmail) {
-    assertThat(commitInfo).message().isEqualTo(expectedMessage);
-    assertThat(commitInfo).author().name().isEqualTo(expectedName);
-    assertThat(commitInfo).author().email().isEqualTo(expectedEmail);
-
-    // Committer should always be the server, regardless of author.
-    assertThat(commitInfo).committer().name().isEqualTo(SERVER_NAME);
-    assertThat(commitInfo).committer().email().isEqualTo(SERVER_EMAIL);
-    assertThat(commitInfo).committer().date().isEqualTo(commitInfo.author.date);
-    assertThat(commitInfo).committer().tz().isEqualTo(commitInfo.author.tz);
-  }
-
-  private static AuditLogFormatter getAuditLogFormatter() {
-    return AuditLogFormatter.create(
-        GroupRebuilderTest::getAccount, GroupRebuilderTest::getGroup, SERVER_ID);
-  }
-
-  private static Optional<Account> getAccount(Account.Id id) {
-    Account account = new Account(id, TimeUtil.nowTs());
-    account.setFullName("Account " + id);
-    return Optional.of(account);
-  }
-
-  private static Optional<GroupDescription.Basic> getGroup(AccountGroup.UUID uuid) {
-    GroupDescription.Basic group =
-        new GroupDescription.Basic() {
-          @Override
-          public AccountGroup.UUID getGroupUUID() {
-            return uuid;
-          }
-
-          @Override
-          public String getName() {
-            return "Group " + uuid;
-          }
-
-          @Nullable
-          @Override
-          public String getEmailAddress() {
-            return null;
-          }
-
-          @Nullable
-          @Override
-          public String getUrl() {
-            return null;
-          }
-        };
-    return Optional.of(group);
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/HANATest.java b/javatests/com/google/gerrit/server/schema/HANATest.java
deleted file mode 100644
index ccf747f..0000000
--- a/javatests/com/google/gerrit/server/schema/HANATest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2016 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
-
-package com.google.gerrit.server.schema;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.testing.GerritBaseTests;
-import org.eclipse.jgit.lib.Config;
-import org.junit.Before;
-import org.junit.Test;
-
-public class HANATest extends GerritBaseTests {
-
-  private HANA hana;
-  private Config config;
-
-  @Before
-  public void setup() {
-    config = new Config();
-    config.setString("database", null, "hostname", "my.host");
-    hana = new HANA(config);
-  }
-
-  @Test
-  public void getUrl() throws Exception {
-    config.setString("database", null, "port", "4242");
-    assertThat(hana.getUrl()).isEqualTo("jdbc:sap://my.host:4242");
-  }
-
-  @Test
-  public void getIndexScript() throws Exception {
-    assertThat(hana.getIndexScript()).isSameAs(ScriptRunner.NOOP);
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java b/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
new file mode 100644
index 0000000..6018d85
--- /dev/null
+++ b/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
@@ -0,0 +1,299 @@
+// Copyright (C) 2018 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.schema;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.server.schema.NoteDbSchemaUpdater.requiredUpgrades;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.IntBlob;
+import com.google.gerrit.server.notedb.MutableNotesMigration;
+import com.google.gerrit.server.notedb.NoteDbSchemaVersionManager;
+import com.google.gerrit.server.notedb.NotesMigrationState;
+import com.google.gerrit.server.notedb.RepoSequence;
+import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import com.google.gerrit.testing.TestUpdateUI;
+import com.google.gwtorm.server.OrmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Test;
+
+public class NoteDbSchemaUpdaterTest extends GerritBaseTests {
+  @Test
+  public void requiredUpgradesFromNoVersion() throws Exception {
+    assertThat(requiredUpgrades(0, versions(10))).containsExactly(10).inOrder();
+    assertThat(requiredUpgrades(0, versions(10, 11, 12))).containsExactly(10, 11, 12).inOrder();
+  }
+
+  @Test
+  public void requiredUpgradesFromExistingVersion() throws Exception {
+    ImmutableSortedSet<Integer> versions = versions(10, 11, 12, 13);
+    assertThat(requiredUpgrades(10, versions)).containsExactly(11, 12, 13).inOrder();
+    assertThat(requiredUpgrades(11, versions)).containsExactly(12, 13).inOrder();
+    assertThat(requiredUpgrades(12, versions)).containsExactly(13).inOrder();
+    assertThat(requiredUpgrades(13, versions)).isEmpty();
+  }
+
+  @Test
+  public void downgradeNotSupported() throws Exception {
+    try {
+      requiredUpgrades(14, versions(10, 11, 12, 13));
+      assert_().fail("expected OrmException");
+    } catch (OrmException e) {
+      assertThat(e)
+          .hasMessageThat()
+          .contains("Cannot downgrade NoteDb schema from version 14 to 13");
+    }
+  }
+
+  @Test
+  public void skipToFirstVersionNotSupported() throws Exception {
+    ImmutableSortedSet<Integer> versions = versions(10, 11, 12);
+    assertThat(requiredUpgrades(9, versions)).containsExactly(10, 11, 12).inOrder();
+    try {
+      requiredUpgrades(8, versions);
+      assert_().fail("expected OrmException");
+    } catch (OrmException e) {
+      assertThat(e).hasMessageThat().contains("Cannot skip NoteDb schema from version 8 to 10");
+    }
+  }
+
+  private static class TestUpdate {
+    protected final Config cfg;
+    protected final AllProjectsName allProjectsName;
+    protected final AllUsersName allUsersName;
+    protected final NoteDbSchemaUpdater updater;
+    protected final GitRepositoryManager repoManager;
+    protected final NoteDbSchemaVersion.Arguments args;
+    private final List<String> messages;
+
+    TestUpdate(Optional<Integer> initialVersion) {
+      cfg = new Config();
+      allProjectsName = new AllProjectsName("The-Projects");
+      allUsersName = new AllUsersName("The-Users");
+      repoManager = new InMemoryRepositoryManager();
+
+      SchemaCreator schemaCreator =
+          () -> {
+            try (Repository repo = repoManager.createRepository(allProjectsName)) {
+              if (initialVersion.isPresent()) {
+                TestRepository<?> tr = new TestRepository<>(repo);
+                tr.update(RefNames.REFS_VERSION, tr.blob(initialVersion.get().toString()));
+              }
+            } catch (Exception e) {
+              throw new OrmException(e);
+            }
+            repoManager.createRepository(allUsersName).close();
+            setUp();
+          };
+
+      args = new NoteDbSchemaVersion.Arguments(repoManager, allProjectsName);
+      NoteDbSchemaVersionManager versionManager =
+          new NoteDbSchemaVersionManager(allProjectsName, repoManager);
+      MutableNotesMigration notesMigration = MutableNotesMigration.newDisabled();
+      notesMigration.setFrom(NotesMigrationState.NOTE_DB);
+      updater =
+          new NoteDbSchemaUpdater(
+              cfg,
+              allProjectsName,
+              allUsersName,
+              repoManager,
+              schemaCreator,
+              notesMigration,
+              versionManager,
+              args,
+              ImmutableSortedMap.of(10, TestSchema_10.class, 11, TestSchema_11.class));
+      messages = new ArrayList<>();
+    }
+
+    protected void setNotesMigrationConfig() {
+      cfg.setString("noteDb", "changes", "write", "true");
+      cfg.setString("noteDb", "changes", "read", "true");
+      cfg.setString("noteDb", "changes", "primaryStorage", "NOTE_DB");
+      cfg.setString("noteDb", "changes", "disableReviewDb", "true");
+    }
+
+    protected void seedGroupSequenceRef() throws OrmException {
+      new RepoSequence(
+              repoManager,
+              GitReferenceUpdated.DISABLED,
+              allUsersName,
+              Sequences.NAME_GROUPS,
+              () -> 1,
+              1)
+          .next();
+    }
+
+    /**
+     * Test-specific setup.
+     *
+     * @throws OrmException if an error occurs.
+     */
+    protected void setUp() throws OrmException {}
+
+    ImmutableList<String> update() throws Exception {
+      updater.update(
+          new TestUpdateUI() {
+            @Override
+            public void message(String m) {
+              messages.add(m);
+            }
+          });
+      return getMessages();
+    }
+
+    ImmutableList<String> getMessages() {
+      return ImmutableList.copyOf(messages);
+    }
+
+    Optional<Integer> readVersion() throws Exception {
+      try (Repository repo = repoManager.openRepository(allProjectsName)) {
+        return IntBlob.parse(repo, RefNames.REFS_VERSION).map(IntBlob::value);
+      }
+    }
+
+    private static class TestSchema_10 implements NoteDbSchemaVersion {
+      @SuppressWarnings("unused")
+      TestSchema_10(Arguments args) {
+        // Do nothing.
+      }
+
+      @Override
+      public void upgrade(UpdateUI ui) {
+        ui.message("body of 10");
+      }
+    }
+
+    private static class TestSchema_11 implements NoteDbSchemaVersion {
+      @SuppressWarnings("unused")
+      TestSchema_11(Arguments args) {
+        // Do nothing.
+      }
+
+      @Override
+      public void upgrade(UpdateUI ui) {
+        ui.message("BODY OF 11");
+      }
+    }
+  }
+
+  @Test
+  public void bootstrapUpdateWith216Prerequisites() throws Exception {
+    TestUpdate u =
+        new TestUpdate(Optional.empty()) {
+          @Override
+          public void setUp() throws OrmException {
+            setNotesMigrationConfig();
+            seedGroupSequenceRef();
+          }
+        };
+    assertThat(u.update())
+        .containsExactly(
+            "Migrating data to schema 10 ...",
+            "body of 10",
+            "Migrating data to schema 11 ...",
+            "BODY OF 11")
+        .inOrder();
+    assertThat(u.readVersion()).hasValue(11);
+  }
+
+  @Test
+  public void bootstrapUpdateFailsWithoutNotesMigrationConfig() throws Exception {
+    TestUpdate u =
+        new TestUpdate(Optional.empty()) {
+          @Override
+          public void setUp() throws OrmException {
+            seedGroupSequenceRef();
+          }
+        };
+    try {
+      u.update();
+      assert_().fail("expected OrmException");
+    } catch (OrmException e) {
+      assertThat(e).hasMessageThat().contains("NoteDb change migration was not completed");
+    }
+    assertThat(u.getMessages()).isEmpty();
+    assertThat(u.readVersion()).isEmpty();
+  }
+
+  @Test
+  public void bootstrapUpdateFailsWithoutGroupSequenceRef() throws Exception {
+    TestUpdate u =
+        new TestUpdate(Optional.empty()) {
+          @Override
+          public void setUp() {
+            setNotesMigrationConfig();
+          }
+        };
+    try {
+      u.update();
+      assert_().fail("expected OrmException");
+    } catch (OrmException e) {
+      assertThat(e).hasMessageThat().contains("upgrade to 2.16.x first");
+    }
+    assertThat(u.getMessages()).isEmpty();
+    assertThat(u.readVersion()).isEmpty();
+  }
+
+  @Test
+  public void updateTwoVersions() throws Exception {
+    TestUpdate u = new TestUpdate(Optional.of(9));
+    assertThat(u.update())
+        .containsExactly(
+            "Migrating data to schema 10 ...",
+            "body of 10",
+            "Migrating data to schema 11 ...",
+            "BODY OF 11")
+        .inOrder();
+    assertThat(u.readVersion()).hasValue(11);
+  }
+
+  @Test
+  public void updateOneVersion() throws Exception {
+    TestUpdate u = new TestUpdate(Optional.of(10));
+    assertThat(u.update())
+        .containsExactly("Migrating data to schema 11 ...", "BODY OF 11")
+        .inOrder();
+    assertThat(u.readVersion()).hasValue(11);
+  }
+
+  @Test
+  public void updateNoOp() throws Exception {
+    // This test covers the state when running the updater after initializing a new 3.x site, which
+    // seeds the schema version ref with the latest version.
+    TestUpdate u = new TestUpdate(Optional.of(11));
+    assertThat(u.update()).isEmpty();
+    assertThat(u.readVersion()).hasValue(11);
+  }
+
+  private static ImmutableSortedSet<Integer> versions(Integer... versions) {
+    return ImmutableSortedSet.copyOf(versions);
+  }
+}
diff --git a/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java
new file mode 100644
index 0000000..042ac30
--- /dev/null
+++ b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2018 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.schema;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.server.schema.NoteDbSchemaVersions.guessVersion;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Streams;
+import com.google.common.reflect.ClassPath;
+import com.google.common.reflect.ClassPath.ClassInfo;
+import com.google.gerrit.testing.GerritBaseTests;
+import java.util.stream.IntStream;
+import org.junit.Test;
+
+public class NoteDbSchemaVersionsTest extends GerritBaseTests {
+  @Test
+  public void testGuessVersion() {
+    assertThat(guessVersion(getClass())).isEmpty();
+    assertThat(guessVersion(Schema_180.class)).hasValue(180);
+  }
+
+  @Test
+  public void contiguousVersions() {
+    ImmutableSortedSet<Integer> keys = NoteDbSchemaVersions.ALL.keySet();
+    ImmutableList<Integer> expected =
+        IntStream.rangeClosed(keys.first(), keys.last()).boxed().collect(toImmutableList());
+    assertThat(keys).containsExactlyElementsIn(expected).inOrder();
+  }
+
+  @Test
+  public void exceedsReviewDbVersion() {
+    assertThat(NoteDbSchemaVersions.ALL.firstKey()).isGreaterThan(170);
+  }
+
+  @Test
+  public void containsAllNoteDbSchemas() throws Exception {
+    int minNoteDbVersion = 180;
+    ImmutableList<Integer> allSchemaVersions =
+        ClassPath.from(getClass().getClassLoader())
+            .getTopLevelClasses(getClass().getPackage().getName())
+            .stream()
+            .map(ClassInfo::load)
+            .map(NoteDbSchemaVersions::guessVersion)
+            .flatMap(Streams::stream)
+            .filter(v -> v >= minNoteDbVersion)
+            .sorted()
+            .collect(toImmutableList());
+    assertThat(NoteDbSchemaVersions.ALL.keySet())
+        .containsExactlyElementsIn(allSchemaVersions)
+        .inOrder();
+  }
+
+  @Test
+  public void schemaConstructors() throws Exception {
+    NoteDbSchemaVersion.Arguments args = new NoteDbSchemaVersion.Arguments(null, null);
+    for (int version : NoteDbSchemaVersions.ALL.keySet()) {
+      NoteDbSchemaVersions.get(NoteDbSchemaVersions.ALL, version, args);
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/server/schema/ReviewDbSchemaUpdaterTest.java b/javatests/com/google/gerrit/server/schema/ReviewDbSchemaUpdaterTest.java
deleted file mode 100644
index 55bd5f9..0000000
--- a/javatests/com/google/gerrit/server/schema/ReviewDbSchemaUpdaterTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright (C) 2009 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.schema;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.metrics.DisabledMetricMaker;
-import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.GerritPersonIdentProvider;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.AnonymousCowardName;
-import com.google.gerrit.server.config.AnonymousCowardNameProvider;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.GerritServerId;
-import com.google.gerrit.server.config.GerritServerIdProvider;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.testing.GerritBaseTests;
-import com.google.gerrit.testing.InMemoryDatabase;
-import com.google.gerrit.testing.InMemoryH2Type;
-import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.TestUpdateUI;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Guice;
-import com.google.inject.Key;
-import com.google.inject.ProvisionException;
-import com.google.inject.TypeLiteral;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.UUID;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class ReviewDbSchemaUpdaterTest extends GerritBaseTests {
-  private LifecycleManager lifecycle;
-  private InMemoryDatabase db;
-
-  @Before
-  public void setUp() throws Exception {
-    lifecycle = new LifecycleManager();
-    db = InMemoryDatabase.newDatabase(lifecycle);
-    lifecycle.start();
-  }
-
-  @After
-  public void tearDown() throws Exception {
-    if (lifecycle != null) {
-      lifecycle.stop();
-    }
-    InMemoryDatabase.drop(db);
-  }
-
-  @Test
-  public void update() throws OrmException, FileNotFoundException, IOException {
-    db.create();
-
-    final Path site = Paths.get(UUID.randomUUID().toString());
-    final SitePaths paths = new SitePaths(site);
-    ReviewDbSchemaUpdater u =
-        Guice.createInjector(
-                new FactoryModule() {
-                  @Override
-                  protected void configure() {
-                    TypeLiteral<SchemaFactory<ReviewDb>> schemaFactory =
-                        new TypeLiteral<SchemaFactory<ReviewDb>>() {};
-                    bind(schemaFactory).to(NotesMigrationSchemaFactory.class);
-                    bind(Key.get(schemaFactory, ReviewDbFactory.class)).toInstance(db);
-                    bind(SitePaths.class).toInstance(paths);
-
-                    Config cfg = new Config();
-                    cfg.setString("user", null, "name", "Gerrit Code Review");
-                    cfg.setString("user", null, "email", "gerrit@localhost");
-                    cfg.setString(
-                        GerritServerIdProvider.SECTION,
-                        null,
-                        GerritServerIdProvider.KEY,
-                        "1234567");
-                    bind(Config.class) //
-                        .annotatedWith(GerritServerConfig.class) //
-                        .toInstance(cfg);
-
-                    bind(PersonIdent.class) //
-                        .annotatedWith(GerritPersonIdent.class) //
-                        .toProvider(GerritPersonIdentProvider.class);
-
-                    bind(String.class).annotatedWith(GerritServerId.class).toInstance("gerrit");
-
-                    bind(AllProjectsName.class).toInstance(new AllProjectsName("All-Projects"));
-                    bind(AllUsersName.class).toInstance(new AllUsersName("All-Users"));
-
-                    bind(GitRepositoryManager.class).toInstance(new InMemoryRepositoryManager());
-
-                    bind(String.class) //
-                        .annotatedWith(AnonymousCowardName.class) //
-                        .toProvider(AnonymousCowardNameProvider.class);
-
-                    bind(DataSourceType.class).to(InMemoryH2Type.class);
-
-                    bind(SystemGroupBackend.class);
-                    install(new NotesMigration.Module());
-                    bind(MetricMaker.class).to(DisabledMetricMaker.class);
-                  }
-                })
-            .getInstance(ReviewDbSchemaUpdater.class);
-
-    for (ReviewDbSchemaVersion s = u.getLatestSchemaVersion();
-        s.getVersionNbr() > 1;
-        s = s.getPrior()) {
-      try {
-        assertThat(s.getPrior().getVersionNbr())
-            .named(
-                "schema %s has prior version %s. Not true that",
-                s.getVersionNbr(), s.getPrior().getVersionNbr())
-            .isEqualTo(s.getVersionNbr() - 1);
-      } catch (ProvisionException e) {
-        // Ignored
-        // The oldest supported schema version doesn't have a prior schema
-        // version.
-        break;
-      }
-    }
-
-    u.update(new TestUpdateUI());
-
-    db.assertSchemaVersion();
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/ReviewDbSchemaCreatorTest.java b/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java
similarity index 76%
rename from javatests/com/google/gerrit/server/schema/ReviewDbSchemaCreatorTest.java
rename to javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java
index fa0d8b3..ad2d184 100644
--- a/javatests/com/google/gerrit/server/schema/ReviewDbSchemaCreatorTest.java
+++ b/javatests/com/google/gerrit/server/schema/SchemaCreatorImplTest.java
@@ -28,27 +28,21 @@
 import com.google.gerrit.testing.GerritBaseTests;
 import com.google.gerrit.testing.InMemoryDatabase;
 import com.google.gerrit.testing.InMemoryModule;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import java.io.File;
-import java.sql.ResultSet;
-import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import org.eclipse.jgit.lib.Repository;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-public class ReviewDbSchemaCreatorTest extends GerritBaseTests {
+public class SchemaCreatorImplTest extends GerritBaseTests {
   @Inject private AllProjectsName allProjects;
 
   @Inject private GitRepositoryManager repoManager;
 
-  @Inject private InMemoryDatabase db;
+  @Inject private InMemoryDatabase inMemoryDatabase;
 
   @Inject private ProjectConfig.Factory projectConfigFactory;
 
@@ -57,35 +51,8 @@
     new InMemoryModule().inject(this);
   }
 
-  @After
-  public void tearDown() throws Exception {
-    InMemoryDatabase.drop(db);
-  }
-
-  @Test
-  public void getCauses_CreateSchema() throws OrmException, SQLException {
-    // Initially the schema should be empty.
-    String[] types = {"TABLE", "VIEW"};
-    try (JdbcSchema d = (JdbcSchema) db.open();
-        ResultSet rs = d.getConnection().getMetaData().getTables(null, null, null, types)) {
-      assertThat(rs.next()).isFalse();
-    }
-
-    // Create the schema using the current schema version.
-    //
-    db.create();
-    db.assertSchemaVersion();
-
-    // By default sitePath is set to the current working directory.
-    //
-    File sitePath = new File(".").getAbsoluteFile();
-    if (sitePath.getName().equals(".")) {
-      sitePath = sitePath.getParentFile();
-    }
-  }
-
   private LabelTypes getLabelTypes() throws Exception {
-    db.create();
+    inMemoryDatabase.create();
     ProjectConfig c = projectConfigFactory.create(allProjects);
     try (Repository repo = repoManager.openRepository(allProjects)) {
       c.load(repo);
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
deleted file mode 100644
index 4d5db6d..0000000
--- a/javatests/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
-
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroup.Id;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.account.GroupUUID;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.InMemoryTestEnvironment;
-import com.google.gerrit.testing.TestUpdateUI;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.Month;
-import java.time.ZoneOffset;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class Schema_150_to_151_Test {
-
-  @Rule public InMemoryTestEnvironment testEnv = new InMemoryTestEnvironment();
-
-  @Inject private Schema_151 schema151;
-  @Inject private ReviewDb db;
-  @Inject private IdentifiedUser currentUser;
-  @Inject private @GerritPersonIdent Provider<PersonIdent> serverIdent;
-  @Inject private Sequences seq;
-
-  private Connection connection;
-  private PreparedStatement createdOnRetrieval;
-  private PreparedStatement createdOnUpdate;
-  private PreparedStatement auditEntryDeletion;
-  private JdbcSchema jdbcSchema;
-
-  @Before
-  public void unwrapDb() {
-    jdbcSchema = ReviewDbWrapper.unwrapJbdcSchema(db);
-  }
-
-  @Before
-  public void setUp() throws Exception {
-    assume().that(db instanceof JdbcSchema).isTrue();
-
-    connection = ((JdbcSchema) db).getConnection();
-
-    try (Statement stmt = connection.createStatement()) {
-      stmt.execute(
-          "CREATE TABLE account_groups ("
-              + " group_uuid varchar(255) DEFAULT '' NOT NULL,"
-              + " group_id INTEGER DEFAULT 0 NOT NULL,"
-              + " name varchar(255) DEFAULT '' NOT NULL,"
-              + " created_on TIMESTAMP,"
-              + " description CLOB,"
-              + " owner_group_uuid varchar(255) DEFAULT '' NOT NULL,"
-              + " visible_to_all CHAR(1) DEFAULT 'N' NOT NULL"
-              + ")");
-
-      stmt.execute(
-          "CREATE TABLE account_group_members ("
-              + " group_id INTEGER DEFAULT 0 NOT NULL,"
-              + " account_id INTEGER DEFAULT 0 NOT NULL"
-              + ")");
-
-      stmt.execute(
-          "CREATE TABLE account_group_members_audit ("
-              + " group_id INTEGER DEFAULT 0 NOT NULL,"
-              + " account_id INTEGER DEFAULT 0 NOT NULL,"
-              + " added_by INTEGER DEFAULT 0 NOT NULL,"
-              + " added_on TIMESTAMP,"
-              + " removed_by INTEGER,"
-              + " removed_on TIMESTAMP"
-              + ")");
-    }
-
-    createdOnRetrieval =
-        connection.prepareStatement("SELECT created_on FROM account_groups WHERE group_id = ?");
-    createdOnUpdate =
-        connection.prepareStatement("UPDATE account_groups SET created_on = ? WHERE group_id = ?");
-    auditEntryDeletion =
-        connection.prepareStatement("DELETE FROM account_group_members_audit WHERE group_id = ?");
-  }
-
-  @After
-  public void tearDown() throws Exception {
-    if (auditEntryDeletion != null) {
-      auditEntryDeletion.close();
-    }
-    if (createdOnUpdate != null) {
-      createdOnUpdate.close();
-    }
-    if (createdOnRetrieval != null) {
-      createdOnRetrieval.close();
-    }
-    if (connection != null) {
-      connection.close();
-    }
-  }
-
-  @Test
-  public void createdOnIsPopulatedForGroupsCreatedAfterAudit() throws Exception {
-    Timestamp testStartTime = TimeUtil.nowTs();
-    AccountGroup.Id groupId = createGroupInReviewDb("Group for schema migration");
-    setCreatedOnToVeryOldTimestamp(groupId);
-
-    schema151.migrateData(db, new TestUpdateUI());
-
-    Timestamp createdOn = getCreatedOn(groupId);
-    assertThat(createdOn).isAtLeast(testStartTime);
-  }
-
-  @Test
-  public void createdOnIsPopulatedForGroupsCreatedBeforeAudit() throws Exception {
-    AccountGroup.Id groupId = createGroupInReviewDb("Ancient group for schema migration");
-    setCreatedOnToVeryOldTimestamp(groupId);
-    removeAuditEntriesFor(groupId);
-
-    schema151.migrateData(db, new TestUpdateUI());
-
-    Timestamp createdOn = getCreatedOn(groupId);
-    assertThat(createdOn).isEqualTo(AccountGroup.auditCreationInstantTs());
-  }
-
-  private AccountGroup.Id createGroupInReviewDb(String name) throws Exception {
-    AccountGroup group =
-        new AccountGroup(
-            new AccountGroup.NameKey(name),
-            new AccountGroup.Id(seq.nextGroupId()),
-            GroupUUID.make(name, serverIdent.get()),
-            TimeUtil.nowTs());
-    storeInReviewDb(group);
-    addMembersInReviewDb(group.getId(), currentUser.getAccountId());
-    return group.getId();
-  }
-
-  private Timestamp getCreatedOn(Id groupId) throws Exception {
-    createdOnRetrieval.setInt(1, groupId.get());
-    try (ResultSet results = createdOnRetrieval.executeQuery()) {
-      if (results.first()) {
-        return results.getTimestamp(1);
-      }
-    }
-    return null;
-  }
-
-  private void setCreatedOnToVeryOldTimestamp(Id groupId) throws Exception {
-    createdOnUpdate.setInt(1, groupId.get());
-    Instant instant = LocalDateTime.of(1800, Month.JANUARY, 1, 0, 0).toInstant(ZoneOffset.UTC);
-    createdOnUpdate.setTimestamp(1, Timestamp.from(instant));
-    createdOnUpdate.setInt(2, groupId.get());
-    createdOnUpdate.executeUpdate();
-  }
-
-  private void removeAuditEntriesFor(AccountGroup.Id groupId) throws Exception {
-    auditEntryDeletion.setInt(1, groupId.get());
-    auditEntryDeletion.executeUpdate();
-  }
-
-  private void storeInReviewDb(AccountGroup... groups) throws Exception {
-    try (PreparedStatement stmt =
-        jdbcSchema
-            .getConnection()
-            .prepareStatement(
-                "INSERT INTO account_groups"
-                    + " (group_uuid,"
-                    + " group_id,"
-                    + " name,"
-                    + " description,"
-                    + " created_on,"
-                    + " owner_group_uuid,"
-                    + " visible_to_all) VALUES (?, ?, ?, ?, ?, ?, ?)")) {
-      for (AccountGroup group : groups) {
-        stmt.setString(1, group.getGroupUUID().get());
-        stmt.setInt(2, group.getId().get());
-        stmt.setString(3, group.getName());
-        stmt.setString(4, group.getDescription());
-        stmt.setTimestamp(5, group.getCreatedOn());
-        stmt.setString(6, group.getOwnerGroupUUID().get());
-        stmt.setString(7, group.isVisibleToAll() ? "Y" : "N");
-        stmt.addBatch();
-      }
-      stmt.executeBatch();
-    }
-  }
-
-  private void addMembersInReviewDb(AccountGroup.Id groupId, Account.Id... memberIds)
-      throws Exception {
-    try (PreparedStatement addMemberStmt =
-            jdbcSchema
-                .getConnection()
-                .prepareStatement(
-                    "INSERT INTO account_group_members"
-                        + " (group_id,"
-                        + " account_id) VALUES ("
-                        + groupId.get()
-                        + ", ?)");
-        PreparedStatement addMemberAuditStmt =
-            jdbcSchema
-                .getConnection()
-                .prepareStatement(
-                    "INSERT INTO account_group_members_audit"
-                        + " (group_id,"
-                        + " account_id,"
-                        + " added_by,"
-                        + " added_on) VALUES ("
-                        + groupId.get()
-                        + ", ?, "
-                        + currentUser.getAccountId().get()
-                        + ", ?)")) {
-      Timestamp addedOn = TimeUtil.nowTs();
-      for (Account.Id memberId : memberIds) {
-        addMemberStmt.setInt(1, memberId.get());
-        addMemberStmt.addBatch();
-
-        addMemberAuditStmt.setInt(1, memberId.get());
-        addMemberAuditStmt.setTimestamp(2, addedOn);
-        addMemberAuditStmt.addBatch();
-      }
-      addMemberStmt.executeBatch();
-      addMemberAuditStmt.executeBatch();
-    }
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java b/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java
deleted file mode 100644
index 0080f3f..0000000
--- a/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.common.collect.ImmutableMap.toImmutableMap;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_USERS_DEFAULT;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_URL;
-import static com.google.gerrit.server.git.UserConfigSections.MY;
-import static com.google.gerrit.server.schema.Schema_160.DEFAULT_DRAFT_ITEMS;
-import static com.google.gerrit.server.schema.VersionedAccountPreferences.PREFERENCES;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
-import com.google.gerrit.extensions.client.MenuItem;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.account.AccountIndexer;
-import com.google.gerrit.testing.InMemoryTestEnvironment;
-import com.google.gerrit.testing.TestUpdateUI;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Supplier;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.lib.BlobBasedConfig;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class Schema_159_to_160_Test {
-  @Rule public InMemoryTestEnvironment testEnv = new InMemoryTestEnvironment();
-
-  @Inject private AccountCache accountCache;
-  @Inject private AccountIndexer accountIndexer;
-  @Inject private AllUsersName allUsersName;
-  @Inject private GerritApi gApi;
-  @Inject private GitRepositoryManager repoManager;
-  @Inject private Provider<IdentifiedUser> userProvider;
-  @Inject private ReviewDb db;
-  @Inject private Schema_160 schema160;
-
-  private Account.Id accountId;
-
-  @Before
-  public void setUp() throws Exception {
-    accountId = userProvider.get().getAccountId();
-  }
-
-  @Test
-  public void skipUnmodified() throws Exception {
-    ObjectId oldMetaId = metaRef(accountId);
-    ImmutableSet<String> fromNoteDb = myMenusFromNoteDb(accountId);
-    ImmutableSet<String> fromApi = myMenusFromApi(accountId);
-    for (String item : DEFAULT_DRAFT_ITEMS) {
-      assertThat(fromNoteDb).doesNotContain(item);
-      assertThat(fromApi).doesNotContain(item);
-    }
-
-    schema160.migrateData(db, new TestUpdateUI());
-
-    assertThat(metaRef(accountId)).isEqualTo(oldMetaId);
-  }
-
-  @Test
-  public void deleteItems() throws Exception {
-    ObjectId oldMetaId = metaRef(accountId);
-    ImmutableSet<String> defaultNames = myMenusFromApi(accountId);
-
-    GeneralPreferencesInfo prefs = gApi.accounts().id(accountId.get()).getPreferences();
-    prefs.my.add(0, new MenuItem("Something else", DEFAULT_DRAFT_ITEMS.get(0) + "+is:mergeable"));
-    for (int i = 0; i < DEFAULT_DRAFT_ITEMS.size(); i++) {
-      prefs.my.add(new MenuItem("Draft entry " + i, DEFAULT_DRAFT_ITEMS.get(i)));
-    }
-    gApi.accounts().id(accountId.get()).setPreferences(prefs);
-
-    List<String> oldNames =
-        ImmutableList.<String>builder()
-            .add("Something else")
-            .addAll(defaultNames)
-            .add("Draft entry 0")
-            .add("Draft entry 1")
-            .add("Draft entry 2")
-            .add("Draft entry 3")
-            .build();
-    assertThat(myMenusFromApi(accountId)).containsExactlyElementsIn(oldNames).inOrder();
-
-    schema160.migrateData(db, new TestUpdateUI());
-    accountCache.evict(accountId);
-    accountIndexer.index(accountId);
-    testEnv.setApiUser(accountId);
-
-    assertThat(metaRef(accountId)).isNotEqualTo(oldMetaId);
-
-    List<String> newNames =
-        ImmutableList.<String>builder().add("Something else").addAll(defaultNames).build();
-    assertThat(myMenusFromNoteDb(accountId)).containsExactlyElementsIn(newNames).inOrder();
-    assertThat(myMenusFromApi(accountId)).containsExactlyElementsIn(newNames).inOrder();
-  }
-
-  @Test
-  public void skipNonExistentRefsUsersDefault() throws Exception {
-    assertThat(readRef(REFS_USERS_DEFAULT)).isEmpty();
-    schema160.migrateData(db, new TestUpdateUI());
-    assertThat(readRef(REFS_USERS_DEFAULT)).isEmpty();
-  }
-
-  @Test
-  public void deleteDefaultItem() throws Exception {
-    assertThat(readRef(REFS_USERS_DEFAULT)).isEmpty();
-    ImmutableSet<String> defaultNames = defaultMenusFromApi();
-
-    // Setting *any* preference causes preferences.config to contain the full set of "my" sections.
-    // This mimics real-world behavior prior to the 2.15 upgrade; see Issue 8439 for details.
-    GeneralPreferencesInfo prefs = gApi.config().server().getDefaultPreferences();
-    prefs.signedOffBy = !firstNonNull(prefs.signedOffBy, false);
-    gApi.config().server().setDefaultPreferences(prefs);
-
-    try (Repository repo = repoManager.openRepository(allUsersName)) {
-      Config cfg = new BlobBasedConfig(null, repo, readRef(REFS_USERS_DEFAULT).get(), PREFERENCES);
-      assertThat(cfg.getSubsections("my")).containsExactlyElementsIn(defaultNames).inOrder();
-
-      // Add more defaults directly in git, the SetPreferences endpoint doesn't respect the "my"
-      // field in the input in 2.15 and earlier.
-      cfg.setString("my", "Drafts", "url", "#/q/owner:self+is:draft");
-      cfg.setString("my", "Something else", "url", "#/q/owner:self+is:draft+is:mergeable");
-      cfg.setString("my", "Totally not drafts", "url", "#/q/owner:self+is:draft");
-      new TestRepository<>(repo)
-          .branch(REFS_USERS_DEFAULT)
-          .commit()
-          .add(PREFERENCES, cfg.toText())
-          .create();
-    }
-
-    List<String> oldNames =
-        ImmutableList.<String>builder()
-            .addAll(defaultNames)
-            .add("Drafts")
-            .add("Something else")
-            .add("Totally not drafts")
-            .build();
-    assertThat(defaultMenusFromApi()).containsExactlyElementsIn(oldNames).inOrder();
-
-    schema160.migrateData(db, new TestUpdateUI());
-
-    assertThat(readRef(REFS_USERS_DEFAULT)).isPresent();
-
-    List<String> newNames =
-        ImmutableList.<String>builder().addAll(defaultNames).add("Something else").build();
-    assertThat(myMenusFromNoteDb(VersionedAccountPreferences::forDefault).keySet())
-        .containsExactlyElementsIn(newNames)
-        .inOrder();
-    assertThat(defaultMenusFromApi()).containsExactlyElementsIn(newNames).inOrder();
-  }
-
-  private ImmutableSet<String> myMenusFromNoteDb(Account.Id id) throws Exception {
-    return myMenusFromNoteDb(() -> VersionedAccountPreferences.forUser(id)).keySet();
-  }
-
-  // Raw config values, bypassing the defaults set by PreferencesConfig.
-  private ImmutableMap<String, String> myMenusFromNoteDb(
-      Supplier<VersionedAccountPreferences> prefsSupplier) throws Exception {
-    try (Repository repo = repoManager.openRepository(allUsersName)) {
-      VersionedAccountPreferences prefs = prefsSupplier.get();
-      prefs.load(allUsersName, repo);
-      Config cfg = prefs.getConfig();
-      return cfg.getSubsections(MY)
-          .stream()
-          .collect(toImmutableMap(i -> i, i -> cfg.getString(MY, i, KEY_URL)));
-    }
-  }
-
-  private ImmutableSet<String> myMenusFromApi(Account.Id id) throws Exception {
-    return myMenus(gApi.accounts().id(id.get()).getPreferences()).keySet();
-  }
-
-  private ImmutableSet<String> defaultMenusFromApi() throws Exception {
-    return myMenus(gApi.config().server().getDefaultPreferences()).keySet();
-  }
-
-  private static ImmutableMap<String, String> myMenus(GeneralPreferencesInfo prefs) {
-
-    return prefs.my.stream().collect(toImmutableMap(i -> i.name, i -> i.url));
-  }
-
-  private ObjectId metaRef(Account.Id id) throws Exception {
-    return readRef(RefNames.refsUsers(id))
-        .orElseThrow(() -> new AssertionError("missing ref for account " + id));
-  }
-
-  private Optional<ObjectId> readRef(String ref) throws Exception {
-    try (Repository repo = repoManager.openRepository(allUsersName)) {
-      return Optional.ofNullable(repo.exactRef(ref)).map(Ref::getObjectId);
-    }
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/Schema_161_to_162_Test.java b/javatests/com/google/gerrit/server/schema/Schema_161_to_162_Test.java
deleted file mode 100644
index 10aabe8..0000000
--- a/javatests/com/google/gerrit/server/schema/Schema_161_to_162_Test.java
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (C) 2017 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.schema;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.project.ProjectConfig;
-import com.google.gerrit.testing.InMemoryTestEnvironment;
-import com.google.gerrit.testing.TestUpdateUI;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import java.io.IOException;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class Schema_161_to_162_Test {
-  @Rule public InMemoryTestEnvironment testEnv = new InMemoryTestEnvironment();
-
-  @Inject private AllProjectsName allProjectsName;
-  @Inject private AllUsersName allUsersName;
-  @Inject private GerritApi gApi;
-  @Inject private GitRepositoryManager repoManager;
-  @Inject private Schema_162 schema162;
-  @Inject private ReviewDb db;
-  @Inject private ProjectConfig.Factory projectConfigFactory;
-  @Inject @GerritPersonIdent private PersonIdent serverUser;
-
-  @Test
-  public void skipCorrectInheritance() throws Exception {
-    assertThatAllUsersInheritsFrom(allProjectsName.get());
-    ObjectId oldHead;
-    try (Repository git = repoManager.openRepository(allUsersName)) {
-      oldHead = git.findRef(RefNames.REFS_CONFIG).getObjectId();
-    }
-
-    schema162.migrateData(db, new TestUpdateUI());
-
-    // Check that the parent remained unchanged and that no commit was made
-    assertThatAllUsersInheritsFrom(allProjectsName.get());
-    try (Repository git = repoManager.openRepository(allUsersName)) {
-      assertThat(oldHead).isEqualTo(git.findRef(RefNames.REFS_CONFIG).getObjectId());
-    }
-  }
-
-  @Test
-  public void fixIncorrectInheritance() throws Exception {
-    String testProject = gApi.projects().create("test").get().name;
-    assertThatAllUsersInheritsFrom(allProjectsName.get());
-
-    try (Repository git = repoManager.openRepository(allUsersName);
-        MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, git)) {
-      ProjectConfig cfg = projectConfigFactory.read(md);
-      cfg.getProject().setParentName(testProject);
-      md.getCommitBuilder().setCommitter(serverUser);
-      md.getCommitBuilder().setAuthor(serverUser);
-      md.setMessage("Test");
-      cfg.commit(md);
-    } catch (ConfigInvalidException | IOException ex) {
-      throw new OrmException(ex);
-    }
-    assertThatAllUsersInheritsFrom(testProject);
-
-    schema162.migrateData(db, new TestUpdateUI());
-
-    assertThatAllUsersInheritsFrom(allProjectsName.get());
-  }
-
-  private void assertThatAllUsersInheritsFrom(String parent) throws Exception {
-    assertThat(gApi.projects().name(allUsersName.get()).access().inheritsFrom.name)
-        .isEqualTo(parent);
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInNoteDbTest.java b/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInNoteDbTest.java
deleted file mode 100644
index f17550e..0000000
--- a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInNoteDbTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-// Copyright (C) 2018 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.schema;
-
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.server.notedb.NoteDbTable.GROUPS;
-import static com.google.gerrit.server.notedb.NotesMigration.DISABLE_REVIEW_DB;
-import static com.google.gerrit.server.notedb.NotesMigration.SECTION_NOTE_DB;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.ServerInitiated;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerIdProvider;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.group.InternalGroup;
-import com.google.gerrit.server.group.db.GroupNameNotes;
-import com.google.gerrit.server.group.db.GroupsUpdate;
-import com.google.gerrit.server.group.db.InternalGroupCreation;
-import com.google.gerrit.server.group.db.InternalGroupUpdate;
-import com.google.gerrit.testing.InMemoryTestEnvironment;
-import com.google.gerrit.testing.TestUpdateUI;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import java.io.IOException;
-import java.sql.PreparedStatement;
-import java.sql.Statement;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class Schema_166_to_167_WithGroupsInNoteDbTest {
-  private static Config createConfig() {
-    Config config = new Config();
-    config.setString(GerritServerIdProvider.SECTION, null, GerritServerIdProvider.KEY, "1234567");
-
-    // Disable groups in ReviewDb. This means the primary storage for groups is NoteDb.
-    config.setBoolean(SECTION_NOTE_DB, GROUPS.key(), DISABLE_REVIEW_DB, true);
-
-    return config;
-  }
-
-  @Rule
-  public InMemoryTestEnvironment testEnv =
-      new InMemoryTestEnvironment(Schema_166_to_167_WithGroupsInNoteDbTest::createConfig);
-
-  @Inject private Schema_167 schema167;
-  @Inject private ReviewDb db;
-  @Inject private GitRepositoryManager gitRepoManager;
-  @Inject private AllUsersName allUsersName;
-  @Inject private @ServerInitiated GroupsUpdate groupsUpdate;
-  @Inject private Sequences seq;
-
-  private JdbcSchema jdbcSchema;
-
-  @Before
-  public void initDb() throws Exception {
-    jdbcSchema = ReviewDbWrapper.unwrapJbdcSchema(db);
-
-    try (Statement stmt = jdbcSchema.getConnection().createStatement()) {
-      stmt.execute(
-          "CREATE TABLE account_groups ("
-              + " group_uuid varchar(255) DEFAULT '' NOT NULL,"
-              + " group_id INTEGER DEFAULT 0 NOT NULL,"
-              + " name varchar(255) DEFAULT '' NOT NULL,"
-              + " created_on TIMESTAMP,"
-              + " description CLOB,"
-              + " owner_group_uuid varchar(255) DEFAULT '' NOT NULL,"
-              + " visible_to_all CHAR(1) DEFAULT 'N' NOT NULL"
-              + ")");
-    }
-  }
-
-  @Test
-  public void migrationIsSkipped() throws Exception {
-    // Create a group in NoteDb (doesn't create the group in ReviewDb since
-    // disableReviewDb == true)
-    InternalGroup internalGroup =
-        groupsUpdate.createGroup(
-            InternalGroupCreation.builder()
-                .setNameKey(new AccountGroup.NameKey("users"))
-                .setGroupUUID(new AccountGroup.UUID("users"))
-                .setId(new AccountGroup.Id(seq.nextGroupId()))
-                .build(),
-            InternalGroupUpdate.builder().setDescription("description").build());
-
-    // Insert the group into ReviewDb
-    AccountGroup group1 =
-        newGroup()
-            .setName(internalGroup.getName())
-            .setGroupUuid(internalGroup.getGroupUUID())
-            .setId(internalGroup.getId())
-            .setCreatedOn(internalGroup.getCreatedOn())
-            .setDescription(internalGroup.getDescription())
-            .setGroupUuid(internalGroup.getGroupUUID())
-            .setVisibleToAll(internalGroup.isVisibleToAll())
-            .build();
-    storeInReviewDb(group1);
-
-    // Update the group description in ReviewDb so that the group state differs between ReviewDb and
-    // NoteDb
-    group1.setDescription("outdated");
-    updateInReviewDb(group1);
-
-    // Create a group that only exists in ReviewDb
-    AccountGroup group2 = newGroup().setName("reviewDbOnlyGroup").build();
-    storeInReviewDb(group2);
-
-    // Remember the SHA1 of the group ref in NoteDb
-    ObjectId groupSha1 = getGroupSha1(group1.getGroupUUID());
-
-    executeSchemaMigration(schema167);
-
-    // Verify the groups in NoteDb: "users" should still exist, "reviewDbOnlyGroup" should not have
-    // been created
-    ImmutableList<GroupReference> groupReferences = getAllGroupsFromNoteDb();
-    ImmutableList<String> groupNames =
-        groupReferences.stream().map(GroupReference::getName).collect(toImmutableList());
-    assertThat(groupNames).contains("users");
-    assertThat(groupNames).doesNotContain("reviewDbOnlyGroup");
-
-    // Verify that the group refs in NoteDb were not touched.
-    assertThat(getGroupSha1(group1.getGroupUUID())).isEqualTo(groupSha1);
-    assertThat(getGroupSha1(group2.getGroupUUID())).isNull();
-  }
-
-  private static TestGroup.Builder newGroup() {
-    return TestGroup.builder();
-  }
-
-  private void storeInReviewDb(AccountGroup... groups) throws Exception {
-    try (PreparedStatement stmt =
-        jdbcSchema
-            .getConnection()
-            .prepareStatement(
-                "INSERT INTO account_groups"
-                    + " (group_uuid,"
-                    + " group_id,"
-                    + " name,"
-                    + " description,"
-                    + " created_on,"
-                    + " owner_group_uuid,"
-                    + " visible_to_all) VALUES (?, ?, ?, ?, ?, ?, ?)")) {
-      for (AccountGroup group : groups) {
-        stmt.setString(1, group.getGroupUUID().get());
-        stmt.setInt(2, group.getId().get());
-        stmt.setString(3, group.getName());
-        stmt.setString(4, group.getDescription());
-        stmt.setTimestamp(5, group.getCreatedOn());
-        stmt.setString(6, group.getOwnerGroupUUID().get());
-        stmt.setString(7, group.isVisibleToAll() ? "Y" : "N");
-        stmt.addBatch();
-      }
-      stmt.executeBatch();
-    }
-  }
-
-  private void updateInReviewDb(AccountGroup... groups) throws Exception {
-    try (PreparedStatement stmt =
-        jdbcSchema
-            .getConnection()
-            .prepareStatement(
-                "UPDATE account_groups SET"
-                    + " group_uuid = ?,"
-                    + " name = ?,"
-                    + " description = ?,"
-                    + " created_on = ?,"
-                    + " owner_group_uuid = ?,"
-                    + " visible_to_all = ?"
-                    + " WHERE group_id = ?")) {
-      for (AccountGroup group : groups) {
-        stmt.setString(1, group.getGroupUUID().get());
-        stmt.setString(2, group.getName());
-        stmt.setString(3, group.getDescription());
-        stmt.setTimestamp(4, group.getCreatedOn());
-        stmt.setString(5, group.getOwnerGroupUUID().get());
-        stmt.setString(6, group.isVisibleToAll() ? "Y" : "N");
-        stmt.setInt(7, group.getId().get());
-        stmt.addBatch();
-      }
-      stmt.executeBatch();
-    }
-  }
-
-  private void executeSchemaMigration(ReviewDbSchemaVersion schema) throws Exception {
-    schema.migrateData(db, new TestUpdateUI());
-  }
-
-  private ImmutableList<GroupReference> getAllGroupsFromNoteDb()
-      throws IOException, ConfigInvalidException {
-    try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
-      return GroupNameNotes.loadAllGroups(allUsersRepo);
-    }
-  }
-
-  @Nullable
-  private ObjectId getGroupSha1(AccountGroup.UUID groupUuid) throws IOException {
-    try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
-      Ref ref = allUsersRepo.exactRef(RefNames.refsGroups(groupUuid));
-      return ref != null ? ref.getObjectId() : null;
-    }
-  }
-}
diff --git a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java b/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
deleted file mode 100644
index 890ae32..0000000
--- a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
+++ /dev/null
@@ -1,1125 +0,0 @@
-// Copyright (C) 2018 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.schema;
-
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.assertThat;
-import static com.google.gerrit.server.notedb.NoteDbTable.GROUPS;
-import static com.google.gerrit.server.notedb.NotesMigration.DISABLE_REVIEW_DB;
-import static com.google.gerrit.server.notedb.NotesMigration.SECTION_NOTE_DB;
-import static com.google.gerrit.truth.OptionalSubject.assertThat;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.api.accounts.AccountInput;
-import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
-import com.google.gerrit.extensions.api.groups.GroupInput;
-import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.common.GroupAuditEventInfo;
-import com.google.gerrit.extensions.common.GroupAuditEventInfo.GroupMemberAuditEventInfo;
-import com.google.gerrit.extensions.common.GroupAuditEventInfo.Type;
-import com.google.gerrit.extensions.common.GroupAuditEventInfo.UserMemberAuditEventInfo;
-import com.google.gerrit.extensions.common.GroupInfo;
-import com.google.gerrit.extensions.common.GroupOptionsInfo;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.GroupUUID;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerId;
-import com.google.gerrit.server.config.GerritServerIdProvider;
-import com.google.gerrit.server.git.CommitUtil;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.group.InternalGroup;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.group.db.GroupConfig;
-import com.google.gerrit.server.group.db.GroupNameNotes;
-import com.google.gerrit.server.group.db.GroupsConsistencyChecker;
-import com.google.gerrit.server.group.testing.InternalGroupSubject;
-import com.google.gerrit.server.group.testing.TestGroupBackend;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.InMemoryTestEnvironment;
-import com.google.gerrit.testing.TestTimeUtil;
-import com.google.gerrit.testing.TestTimeUtil.TempClockStep;
-import com.google.gerrit.testing.TestUpdateUI;
-import com.google.gerrit.truth.OptionalSubject;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import java.io.IOException;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.time.LocalDate;
-import java.time.Month;
-import java.time.ZoneOffset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevSort;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class Schema_166_to_167_WithGroupsInReviewDbTest {
-  private static Config createConfig() {
-    Config config = new Config();
-    config.setString(GerritServerIdProvider.SECTION, null, GerritServerIdProvider.KEY, "1234567");
-
-    // Enable groups in ReviewDb. This means the primary storage for groups is ReviewDb.
-    config.setBoolean(SECTION_NOTE_DB, GROUPS.key(), DISABLE_REVIEW_DB, false);
-
-    return config;
-  }
-
-  @Rule
-  public InMemoryTestEnvironment testEnv =
-      new InMemoryTestEnvironment(Schema_166_to_167_WithGroupsInReviewDbTest::createConfig);
-
-  @Inject private GerritApi gApi;
-  @Inject private Schema_167 schema167;
-  @Inject private ReviewDb db;
-  @Inject private GitRepositoryManager gitRepoManager;
-  @Inject private AllUsersName allUsersName;
-  @Inject private GroupsConsistencyChecker consistencyChecker;
-  @Inject private IdentifiedUser currentUser;
-  @Inject private @GerritServerId String serverId;
-  @Inject private @GerritPersonIdent PersonIdent serverIdent;
-  @Inject private GroupBundle.Factory groupBundleFactory;
-  @Inject private GroupBackend groupBackend;
-  @Inject private DynamicSet<GroupBackend> backends;
-  @Inject private Sequences seq;
-
-  private JdbcSchema jdbcSchema;
-
-  @Before
-  public void initDb() throws Exception {
-    jdbcSchema = ReviewDbWrapper.unwrapJbdcSchema(db);
-
-    try (Statement stmt = jdbcSchema.getConnection().createStatement()) {
-      stmt.execute(
-          "CREATE TABLE account_groups ("
-              + " group_uuid varchar(255) DEFAULT '' NOT NULL,"
-              + " group_id INTEGER DEFAULT 0 NOT NULL,"
-              + " name varchar(255) DEFAULT '' NOT NULL,"
-              + " created_on TIMESTAMP,"
-              + " description CLOB,"
-              + " owner_group_uuid varchar(255) DEFAULT '' NOT NULL,"
-              + " visible_to_all CHAR(1) DEFAULT 'N' NOT NULL"
-              + ")");
-
-      stmt.execute(
-          "CREATE TABLE account_group_members ("
-              + " group_id INTEGER DEFAULT 0 NOT NULL,"
-              + " account_id INTEGER DEFAULT 0 NOT NULL"
-              + ")");
-
-      stmt.execute(
-          "CREATE TABLE account_group_members_audit ("
-              + " group_id INTEGER DEFAULT 0 NOT NULL,"
-              + " account_id INTEGER DEFAULT 0 NOT NULL,"
-              + " added_by INTEGER DEFAULT 0 NOT NULL,"
-              + " added_on TIMESTAMP,"
-              + " removed_by INTEGER,"
-              + " removed_on TIMESTAMP"
-              + ")");
-
-      stmt.execute(
-          "CREATE TABLE account_group_by_id ("
-              + " group_id INTEGER DEFAULT 0 NOT NULL,"
-              + " include_uuid VARCHAR(255) DEFAULT '' NOT NULL"
-              + ")");
-
-      stmt.execute(
-          "CREATE TABLE account_group_by_id_aud ("
-              + " group_id INTEGER DEFAULT 0 NOT NULL,"
-              + " include_uuid VARCHAR(255) DEFAULT '' NOT NULL,"
-              + " added_by INTEGER DEFAULT 0 NOT NULL,"
-              + " added_on TIMESTAMP,"
-              + " removed_by INTEGER,"
-              + " removed_on TIMESTAMP"
-              + ")");
-    }
-  }
-
-  @Before
-  public void setTimeForTesting() {
-    TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
-  }
-
-  @After
-  public void resetTime() {
-    TestTimeUtil.useSystemTime();
-  }
-
-  @Test
-  public void reviewDbOnlyGroupsAreMigratedToNoteDb() throws Exception {
-    // Create groups only in ReviewDb
-    AccountGroup group1 = newGroup().setName("verifiers").build();
-    AccountGroup group2 = newGroup().setName("contributors").build();
-    storeInReviewDb(group1, group2);
-
-    executeSchemaMigration(schema167, group1, group2);
-
-    ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
-    ImmutableList<String> groupNames =
-        groups.stream().map(GroupReference::getName).collect(toImmutableList());
-    assertThat(groupNames).containsAllOf("verifiers", "contributors");
-  }
-
-  @Test
-  public void alreadyExistingGroupsAreMigratedToNoteDb() throws Exception {
-    // Create group in NoteDb and ReviewDb
-    GroupInput groupInput = new GroupInput();
-    groupInput.name = "verifiers";
-    groupInput.description = "old";
-    GroupInfo group1 = gApi.groups().create(groupInput).get();
-    storeInReviewDb(group1);
-
-    // Update group only in ReviewDb
-    AccountGroup group1InReviewDb = getFromReviewDb(new AccountGroup.Id(group1.groupId));
-    group1InReviewDb.setDescription("new");
-    updateInReviewDb(group1InReviewDb);
-
-    // Create a second group in NoteDb and ReviewDb
-    GroupInfo group2 = gApi.groups().create("contributors").get();
-    storeInReviewDb(group2);
-
-    executeSchemaMigration(schema167, group1, group2);
-
-    // Verify that both groups are present in NoteDb
-    ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
-    ImmutableList<String> groupNames =
-        groups.stream().map(GroupReference::getName).collect(toImmutableList());
-    assertThat(groupNames).containsAllOf("verifiers", "contributors");
-
-    // Verify that group1 has the description from ReviewDb
-    Optional<InternalGroup> group1InNoteDb = getGroupFromNoteDb(new AccountGroup.UUID(group1.id));
-    assertThatGroup(group1InNoteDb).value().description().isEqualTo("new");
-  }
-
-  @Test
-  public void adminGroupIsMigratedToNoteDb() throws Exception {
-    // Administrators group is automatically created for all Gerrit servers (NoteDb only).
-    GroupInfo adminGroup = gApi.groups().id("Administrators").get();
-    storeInReviewDb(adminGroup);
-
-    executeSchemaMigration(schema167, adminGroup);
-
-    ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
-    ImmutableList<String> groupNames =
-        groups.stream().map(GroupReference::getName).collect(toImmutableList());
-    assertThat(groupNames).contains("Administrators");
-  }
-
-  @Test
-  public void nonInteractiveUsersGroupIsMigratedToNoteDb() throws Exception {
-    // 'Non-Interactive Users' group is automatically created for all Gerrit servers (NoteDb only).
-    GroupInfo nonInteractiveUsersGroup = gApi.groups().id("Non-Interactive Users").get();
-    storeInReviewDb(nonInteractiveUsersGroup);
-
-    executeSchemaMigration(schema167, nonInteractiveUsersGroup);
-
-    ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
-    ImmutableList<String> groupNames =
-        groups.stream().map(GroupReference::getName).collect(toImmutableList());
-    assertThat(groupNames).contains("Non-Interactive Users");
-  }
-
-  @Test
-  public void groupsAreConsistentAfterMigrationToNoteDb() throws Exception {
-    // Administrators group are automatically created for all Gerrit servers (NoteDb only).
-    GroupInfo adminGroup = gApi.groups().id("Administrators").get();
-    GroupInfo nonInteractiveUsersGroup = gApi.groups().id("Non-Interactive Users").get();
-    storeInReviewDb(adminGroup, nonInteractiveUsersGroup);
-
-    AccountGroup group1 = newGroup().setName("verifiers").build();
-    AccountGroup group2 = newGroup().setName("contributors").build();
-    storeInReviewDb(group1, group2);
-
-    executeSchemaMigration(schema167, group1, group2);
-
-    List<ConsistencyCheckInfo.ConsistencyProblemInfo> consistencyProblems =
-        consistencyChecker.check();
-    assertThat(consistencyProblems).isEmpty();
-  }
-
-  @Test
-  public void nameIsKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup group = newGroup().setName("verifiers").build();
-    storeInReviewDb(group);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().name().isEqualTo("verifiers");
-  }
-
-  @Test
-  public void emptyNameIsKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup group = newGroup().setName("").build();
-    storeInReviewDb(group);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().name().isEqualTo("");
-  }
-
-  @Test
-  public void uuidIsKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup.UUID groupUuid = new AccountGroup.UUID("ABCDEF");
-    AccountGroup group = newGroup().setGroupUuid(groupUuid).build();
-    storeInReviewDb(group);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(groupUuid);
-    assertThatGroup(groupInNoteDb).value().groupUuid().isEqualTo(groupUuid);
-  }
-
-  @Test
-  public void idIsKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup.Id id = new AccountGroup.Id(12345);
-    AccountGroup group = newGroup().setId(id).build();
-    storeInReviewDb(group);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().id().isEqualTo(id);
-  }
-
-  @Test
-  public void createdOnIsKeptDuringMigrationToNoteDb() throws Exception {
-    Timestamp createdOn =
-        Timestamp.from(
-            LocalDate.of(2018, Month.FEBRUARY, 20)
-                .atTime(18, 2, 56)
-                .atZone(ZoneOffset.UTC)
-                .toInstant());
-    AccountGroup group = newGroup().setCreatedOn(createdOn).build();
-    storeInReviewDb(group);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().createdOn().isEqualTo(createdOn);
-  }
-
-  @Test
-  public void ownerUuidIsKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID("UVWXYZ");
-    AccountGroup group = newGroup().setOwnerGroupUuid(ownerGroupUuid).build();
-    storeInReviewDb(group);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().ownerGroupUuid().isEqualTo(ownerGroupUuid);
-  }
-
-  @Test
-  public void descriptionIsKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup group = newGroup().setDescription("A test group").build();
-    storeInReviewDb(group);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().description().isEqualTo("A test group");
-  }
-
-  @Test
-  public void absentDescriptionIsKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup group = newGroup().build();
-    storeInReviewDb(group);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().description().isNull();
-  }
-
-  @Test
-  public void visibleToAllIsKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup group = newGroup().setVisibleToAll(true).build();
-    storeInReviewDb(group);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().visibleToAll().isTrue();
-  }
-
-  @Test
-  public void membersAreKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup group = newGroup().build();
-    storeInReviewDb(group);
-    Account.Id member1 = new Account.Id(23456);
-    Account.Id member2 = new Account.Id(93483);
-    addMembersInReviewDb(group.getId(), member1, member2);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().members().containsExactly(member1, member2);
-  }
-
-  @Test
-  public void subgroupsAreKeptDuringMigrationToNoteDb() throws Exception {
-    AccountGroup group = newGroup().build();
-    storeInReviewDb(group);
-    AccountGroup.UUID subgroup1 = new AccountGroup.UUID("FGHIKL");
-    AccountGroup.UUID subgroup2 = new AccountGroup.UUID("MNOPQR");
-    addSubgroupsInReviewDb(group.getId(), subgroup1, subgroup2);
-
-    executeSchemaMigration(schema167, group);
-
-    Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
-    assertThatGroup(groupInNoteDb).value().subgroups().containsExactly(subgroup1, subgroup2);
-  }
-
-  @Test
-  public void logFormatWithAccountsAndGerritGroups() throws Exception {
-    AccountInfo user1 = createAccount("user1");
-    AccountInfo user2 = createAccount("user2");
-
-    AccountGroup group1 = createInReviewDb("group1");
-    AccountGroup group2 = createInReviewDb("group2");
-    AccountGroup group3 = createInReviewDb("group3");
-
-    // Add some accounts
-    try (TempClockStep step = TestTimeUtil.freezeClock()) {
-      addMembersInReviewDb(
-          group1.getId(), new Account.Id(user1._accountId), new Account.Id(user2._accountId));
-    }
-    TimeUtil.nowTs();
-
-    // Add some Gerrit groups
-    try (TempClockStep step = TestTimeUtil.freezeClock()) {
-      addSubgroupsInReviewDb(group1.getId(), group2.getGroupUUID(), group3.getGroupUUID());
-    }
-
-    executeSchemaMigration(schema167, group1, group2, group3);
-
-    GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group1.getGroupUUID());
-
-    ImmutableList<CommitInfo> log = log(group1);
-    assertThat(log).hasSize(4);
-
-    // Verify commit that created the group
-    assertThat(log.get(0)).message().isEqualTo("Create group");
-    assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
-    assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
-    assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
-    assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
-    assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
-
-    // Verify commit that the group creator as member
-    assertThat(log.get(1))
-        .message()
-        .isEqualTo(
-            "Update group\n\nAdd: "
-                + currentUser.getName()
-                + " <"
-                + currentUser.getAccountId()
-                + "@"
-                + serverId
-                + ">");
-    assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
-    assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
-    assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
-
-    // Verify commit that added members
-    assertThat(log.get(2))
-        .message()
-        .isEqualTo(
-            "Update group\n"
-                + "\n"
-                + ("Add: user1 <" + user1._accountId + "@" + serverId + ">\n")
-                + ("Add: user2 <" + user2._accountId + "@" + serverId + ">"));
-    assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
-    assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
-    assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
-
-    // Verify commit that added Gerrit groups
-    assertThat(log.get(3))
-        .message()
-        .isEqualTo(
-            "Update group\n"
-                + "\n"
-                + ("Add-group: " + group2.getName() + " <" + group2.getGroupUUID().get() + ">\n")
-                + ("Add-group: " + group3.getName() + " <" + group3.getGroupUUID().get() + ">"));
-    assertThat(log.get(3)).author().name().isEqualTo(currentUser.getName());
-    assertThat(log.get(3)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
-    assertThat(log.get(3)).committer().hasSameDateAs(log.get(3).author);
-
-    // Verify that audit log is correctly read by Gerrit
-    List<? extends GroupAuditEventInfo> auditEvents =
-        gApi.groups().id(group1.getGroupUUID().get()).auditLog();
-    assertThat(auditEvents).hasSize(5);
-    AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
-    assertMemberAuditEvent(
-        auditEvents.get(4), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
-    assertMemberAuditEvents(
-        auditEvents.get(3),
-        auditEvents.get(2),
-        Type.ADD_USER,
-        currentUser.getAccountId(),
-        user1,
-        user2);
-    assertSubgroupAuditEvents(
-        auditEvents.get(1),
-        auditEvents.get(0),
-        Type.ADD_GROUP,
-        currentUser.getAccountId(),
-        toGroupInfo(group2),
-        toGroupInfo(group3));
-  }
-
-  @Test
-  public void logFormatWithSystemGroups() throws Exception {
-    AccountGroup group = createInReviewDb("group");
-
-    try (TempClockStep step = TestTimeUtil.freezeClock()) {
-      addSubgroupsInReviewDb(
-          group.getId(), SystemGroupBackend.ANONYMOUS_USERS, SystemGroupBackend.REGISTERED_USERS);
-    }
-
-    executeSchemaMigration(schema167, group);
-
-    GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group.getGroupUUID());
-
-    ImmutableList<CommitInfo> log = log(group);
-    assertThat(log).hasSize(3);
-
-    // Verify commit that created the group
-    assertThat(log.get(0)).message().isEqualTo("Create group");
-    assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
-    assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
-    assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
-    assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
-    assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
-
-    // Verify commit that the group creator as member
-    assertThat(log.get(1))
-        .message()
-        .isEqualTo(
-            "Update group\n\nAdd: "
-                + currentUser.getName()
-                + " <"
-                + currentUser.getAccountId()
-                + "@"
-                + serverId
-                + ">");
-    assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
-    assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
-    assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
-
-    // Verify commit that added system groups
-    assertThat(log.get(2))
-        .message()
-        .isEqualTo(
-            "Update group\n"
-                + "\n"
-                + "Add-group: Anonymous Users <global:Anonymous-Users>\n"
-                + "Add-group: Registered Users <global:Registered-Users>");
-    assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
-    assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
-    assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
-
-    // Verify that audit log is correctly read by Gerrit
-    List<? extends GroupAuditEventInfo> auditEvents =
-        gApi.groups().id(group.getGroupUUID().get()).auditLog();
-    assertThat(auditEvents).hasSize(3);
-    AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
-    assertMemberAuditEvent(
-        auditEvents.get(2), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
-    assertSubgroupAuditEvents(
-        auditEvents.get(1),
-        auditEvents.get(0),
-        Type.ADD_GROUP,
-        currentUser.getAccountId(),
-        groupInfoForExternalGroup(SystemGroupBackend.ANONYMOUS_USERS),
-        groupInfoForExternalGroup(SystemGroupBackend.REGISTERED_USERS));
-  }
-
-  @Test
-  public void logFormatWithExternalGroup() throws Exception {
-    AccountGroup group = createInReviewDb("group");
-
-    TestGroupBackend testGroupBackend = new TestGroupBackend();
-    backends.add("gerrit", testGroupBackend);
-    AccountGroup.UUID subgroupUuid = testGroupBackend.create("test").getGroupUUID();
-    assertThat(groupBackend.handles(subgroupUuid)).isTrue();
-    addSubgroupsInReviewDb(group.getId(), subgroupUuid);
-
-    executeSchemaMigration(schema167, group);
-
-    GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group.getGroupUUID());
-
-    ImmutableList<CommitInfo> log = log(group);
-    assertThat(log).hasSize(3);
-
-    // Verify commit that created the group
-    assertThat(log.get(0)).message().isEqualTo("Create group");
-    assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
-    assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
-    assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
-    assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
-    assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
-
-    // Verify commit that the group creator as member
-    assertThat(log.get(1))
-        .message()
-        .isEqualTo(
-            "Update group\n\nAdd: "
-                + currentUser.getName()
-                + " <"
-                + currentUser.getAccountId()
-                + "@"
-                + serverId
-                + ">");
-    assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
-    assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
-    assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
-
-    // Verify commit that added system groups
-    // Note: The schema migration can only resolve names of Gerrit groups, not of external groups
-    // and system groups, hence the UUID shows up in commit messages where we would otherwise
-    // expect the group name.
-    assertThat(log.get(2))
-        .message()
-        .isEqualTo(
-            "Update group\n"
-                + "\n"
-                + "Add-group: "
-                + subgroupUuid.get()
-                + " <"
-                + subgroupUuid.get()
-                + ">");
-    assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
-    assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
-    assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
-
-    // Verify that audit log is correctly read by Gerrit
-    List<? extends GroupAuditEventInfo> auditEvents =
-        gApi.groups().id(group.getGroupUUID().get()).auditLog();
-    assertThat(auditEvents).hasSize(2);
-    AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
-    assertMemberAuditEvent(
-        auditEvents.get(1), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
-    assertSubgroupAuditEvent(
-        auditEvents.get(0),
-        Type.ADD_GROUP,
-        currentUser.getAccountId(),
-        groupInfoForExternalGroup(subgroupUuid));
-  }
-
-  @Test
-  public void logFormatWithNonExistingExternalGroup() throws Exception {
-    AccountGroup group = createInReviewDb("group");
-
-    AccountGroup.UUID subgroupUuid = new AccountGroup.UUID("notExisting:foo");
-
-    assertThat(groupBackend.handles(subgroupUuid)).isFalse();
-    addSubgroupsInReviewDb(group.getId(), subgroupUuid);
-
-    executeSchemaMigration(schema167, group);
-
-    GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group.getGroupUUID());
-
-    ImmutableList<CommitInfo> log = log(group);
-    assertThat(log).hasSize(3);
-
-    // Verify commit that created the group
-    assertThat(log.get(0)).message().isEqualTo("Create group");
-    assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
-    assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
-    assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
-    assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
-    assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
-
-    // Verify commit that the group creator as member
-    assertThat(log.get(1))
-        .message()
-        .isEqualTo(
-            "Update group\n\nAdd: "
-                + currentUser.getName()
-                + " <"
-                + currentUser.getAccountId()
-                + "@"
-                + serverId
-                + ">");
-    assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
-    assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
-    assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
-
-    // Verify commit that added system groups
-    // Note: The schema migration can only resolve names of Gerrit groups, not of external groups
-    // and system groups, hence the UUID shows up in commit messages where we would otherwise
-    // expect the group name.
-    assertThat(log.get(2))
-        .message()
-        .isEqualTo("Update group\n" + "\n" + "Add-group: notExisting:foo <notExisting:foo>");
-    assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
-    assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
-    assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
-
-    // Verify that audit log is correctly read by Gerrit
-    List<? extends GroupAuditEventInfo> auditEvents =
-        gApi.groups().id(group.getGroupUUID().get()).auditLog();
-    assertThat(auditEvents).hasSize(2);
-    AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
-    assertMemberAuditEvent(
-        auditEvents.get(1), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
-    assertSubgroupAuditEvent(
-        auditEvents.get(0),
-        Type.ADD_GROUP,
-        currentUser.getAccountId(),
-        groupInfoForExternalGroup(subgroupUuid));
-  }
-
-  private static TestGroup.Builder newGroup() {
-    return TestGroup.builder();
-  }
-
-  private AccountGroup createInReviewDb(String groupName) throws Exception {
-    AccountGroup group =
-        new AccountGroup(
-            new AccountGroup.NameKey(groupName),
-            new AccountGroup.Id(seq.nextGroupId()),
-            GroupUUID.make(groupName, serverIdent),
-            TimeUtil.nowTs());
-    storeInReviewDb(group);
-    addMembersInReviewDb(group.getId(), currentUser.getAccountId());
-    return group;
-  }
-
-  private void storeInReviewDb(GroupInfo... groups) throws Exception {
-    storeInReviewDb(
-        Arrays.stream(groups)
-            .map(Schema_166_to_167_WithGroupsInReviewDbTest::toAccountGroup)
-            .toArray(AccountGroup[]::new));
-  }
-
-  private void storeInReviewDb(AccountGroup... groups) throws Exception {
-    try (PreparedStatement stmt =
-        jdbcSchema
-            .getConnection()
-            .prepareStatement(
-                "INSERT INTO account_groups"
-                    + " (group_uuid,"
-                    + " group_id,"
-                    + " name,"
-                    + " description,"
-                    + " created_on,"
-                    + " owner_group_uuid,"
-                    + " visible_to_all) VALUES (?, ?, ?, ?, ?, ?, ?)")) {
-      for (AccountGroup group : groups) {
-        stmt.setString(1, group.getGroupUUID().get());
-        stmt.setInt(2, group.getId().get());
-        stmt.setString(3, group.getName());
-        stmt.setString(4, group.getDescription());
-        stmt.setTimestamp(5, group.getCreatedOn());
-        stmt.setString(6, group.getOwnerGroupUUID().get());
-        stmt.setString(7, group.isVisibleToAll() ? "Y" : "N");
-        stmt.addBatch();
-      }
-      stmt.executeBatch();
-    }
-  }
-
-  private void updateInReviewDb(AccountGroup... groups) throws Exception {
-    try (PreparedStatement stmt =
-        jdbcSchema
-            .getConnection()
-            .prepareStatement(
-                "UPDATE account_groups SET"
-                    + " group_uuid = ?,"
-                    + " name = ?,"
-                    + " description = ?,"
-                    + " created_on = ?,"
-                    + " owner_group_uuid = ?,"
-                    + " visible_to_all = ?"
-                    + " WHERE group_id = ?")) {
-      for (AccountGroup group : groups) {
-        stmt.setString(1, group.getGroupUUID().get());
-        stmt.setString(2, group.getName());
-        stmt.setString(3, group.getDescription());
-        stmt.setTimestamp(4, group.getCreatedOn());
-        stmt.setString(5, group.getOwnerGroupUUID().get());
-        stmt.setString(6, group.isVisibleToAll() ? "Y" : "N");
-        stmt.setInt(7, group.getId().get());
-        stmt.addBatch();
-      }
-      stmt.executeBatch();
-    }
-  }
-
-  private AccountGroup getFromReviewDb(AccountGroup.Id groupId) throws Exception {
-    try (Statement stmt = jdbcSchema.getConnection().createStatement();
-        ResultSet rs =
-            stmt.executeQuery(
-                "SELECT group_uuid,"
-                    + " name,"
-                    + " description,"
-                    + " created_on,"
-                    + " owner_group_uuid,"
-                    + " visible_to_all"
-                    + " FROM account_groups"
-                    + " WHERE group_id = "
-                    + groupId.get())) {
-      if (!rs.next()) {
-        throw new OrmException(String.format("Group %s not found", groupId.get()));
-      }
-
-      AccountGroup.UUID groupUuid = new AccountGroup.UUID(rs.getString(1));
-      AccountGroup.NameKey groupName = new AccountGroup.NameKey(rs.getString(2));
-      String description = rs.getString(3);
-      Timestamp createdOn = rs.getTimestamp(4);
-      AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID(rs.getString(5));
-      boolean visibleToAll = "Y".equals(rs.getString(6));
-
-      AccountGroup group = new AccountGroup(groupName, groupId, groupUuid, createdOn);
-      group.setDescription(description);
-      group.setOwnerGroupUUID(ownerGroupUuid);
-      group.setVisibleToAll(visibleToAll);
-
-      if (rs.next()) {
-        throw new OrmException(String.format("Group ID %s is ambiguous", groupId.get()));
-      }
-
-      return group;
-    }
-  }
-
-  private void addMembersInReviewDb(AccountGroup.Id groupId, Account.Id... memberIds)
-      throws Exception {
-    try (PreparedStatement addMemberStmt =
-            jdbcSchema
-                .getConnection()
-                .prepareStatement(
-                    "INSERT INTO account_group_members"
-                        + " (group_id,"
-                        + " account_id) VALUES ("
-                        + groupId.get()
-                        + ", ?)");
-        PreparedStatement addMemberAuditStmt =
-            jdbcSchema
-                .getConnection()
-                .prepareStatement(
-                    "INSERT INTO account_group_members_audit"
-                        + " (group_id,"
-                        + " account_id,"
-                        + " added_by,"
-                        + " added_on) VALUES ("
-                        + groupId.get()
-                        + ", ?, "
-                        + currentUser.getAccountId().get()
-                        + ", ?)")) {
-      Timestamp addedOn = TimeUtil.nowTs();
-      for (Account.Id memberId : memberIds) {
-        addMemberStmt.setInt(1, memberId.get());
-        addMemberStmt.addBatch();
-
-        addMemberAuditStmt.setInt(1, memberId.get());
-        addMemberAuditStmt.setTimestamp(2, addedOn);
-        addMemberAuditStmt.addBatch();
-      }
-      addMemberStmt.executeBatch();
-      addMemberAuditStmt.executeBatch();
-    }
-  }
-
-  private void addSubgroupsInReviewDb(AccountGroup.Id groupId, AccountGroup.UUID... subgroupUuids)
-      throws Exception {
-    try (PreparedStatement addSubGroupStmt =
-            jdbcSchema
-                .getConnection()
-                .prepareStatement(
-                    "INSERT INTO account_group_by_id"
-                        + " (group_id,"
-                        + " include_uuid) VALUES ("
-                        + groupId.get()
-                        + ", ?)");
-        PreparedStatement addSubGroupAuditStmt =
-            jdbcSchema
-                .getConnection()
-                .prepareStatement(
-                    "INSERT INTO account_group_by_id_aud"
-                        + " (group_id,"
-                        + " include_uuid,"
-                        + " added_by,"
-                        + " added_on) VALUES ("
-                        + groupId.get()
-                        + ", ?, "
-                        + currentUser.getAccountId().get()
-                        + ", ?)")) {
-      Timestamp addedOn = TimeUtil.nowTs();
-      for (AccountGroup.UUID subgroupUuid : subgroupUuids) {
-        addSubGroupStmt.setString(1, subgroupUuid.get());
-        addSubGroupStmt.addBatch();
-
-        addSubGroupAuditStmt.setString(1, subgroupUuid.get());
-        addSubGroupAuditStmt.setTimestamp(2, addedOn);
-        addSubGroupAuditStmt.addBatch();
-      }
-      addSubGroupStmt.executeBatch();
-      addSubGroupAuditStmt.executeBatch();
-    }
-  }
-
-  private AccountInfo createAccount(String name) throws RestApiException {
-    AccountInput accountInput = new AccountInput();
-    accountInput.username = name;
-    accountInput.name = name;
-    return gApi.accounts().create(accountInput).get();
-  }
-
-  private GroupBundle readGroupBundleFromNoteDb(AccountGroup.UUID groupUuid) throws Exception {
-    try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
-      return groupBundleFactory.fromNoteDb(allUsersName, allUsersRepo, groupUuid);
-    }
-  }
-
-  private void executeSchemaMigration(ReviewDbSchemaVersion schema, AccountGroup... groupsToVerify)
-      throws Exception {
-    executeSchemaMigration(
-        schema,
-        Arrays.stream(groupsToVerify)
-            .map(AccountGroup::getGroupUUID)
-            .toArray(AccountGroup.UUID[]::new));
-  }
-
-  private void executeSchemaMigration(ReviewDbSchemaVersion schema, GroupInfo... groupsToVerify)
-      throws Exception {
-    executeSchemaMigration(
-        schema,
-        Arrays.stream(groupsToVerify)
-            .map(i -> new AccountGroup.UUID(i.id))
-            .toArray(AccountGroup.UUID[]::new));
-  }
-
-  private void executeSchemaMigration(
-      ReviewDbSchemaVersion schema, AccountGroup.UUID... groupsToVerify) throws Exception {
-    List<GroupBundle> reviewDbBundles = new ArrayList<>();
-    for (AccountGroup.UUID groupUuid : groupsToVerify) {
-      reviewDbBundles.add(GroupBundle.Factory.fromReviewDb(db, groupUuid));
-    }
-
-    schema.migrateData(db, new TestUpdateUI());
-
-    for (GroupBundle reviewDbBundle : reviewDbBundles) {
-      assertMigratedCleanly(readGroupBundleFromNoteDb(reviewDbBundle.uuid()), reviewDbBundle);
-    }
-  }
-
-  private void assertMigratedCleanly(GroupBundle noteDbBundle, GroupBundle expectedReviewDbBundle) {
-    assertThat(GroupBundle.compareWithAudits(expectedReviewDbBundle, noteDbBundle)).isEmpty();
-  }
-
-  private ImmutableList<CommitInfo> log(AccountGroup group) throws Exception {
-    ImmutableList.Builder<CommitInfo> result = ImmutableList.builder();
-    List<Date> commitDates = new ArrayList<>();
-    try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName);
-        RevWalk rw = new RevWalk(allUsersRepo)) {
-      Ref ref = allUsersRepo.exactRef(RefNames.refsGroups(group.getGroupUUID()));
-      if (ref != null) {
-        rw.sort(RevSort.REVERSE);
-        rw.setRetainBody(true);
-        rw.markStart(rw.parseCommit(ref.getObjectId()));
-        for (RevCommit c : rw) {
-          result.add(CommitUtil.toCommitInfo(c));
-          commitDates.add(c.getCommitterIdent().getWhen());
-        }
-      }
-    }
-    assertThat(commitDates).named("commit timestamps for %s", result).isOrdered();
-    return result.build();
-  }
-
-  private ImmutableList<GroupReference> getAllGroupsFromNoteDb()
-      throws IOException, ConfigInvalidException {
-    try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
-      return GroupNameNotes.loadAllGroups(allUsersRepo);
-    }
-  }
-
-  private Optional<InternalGroup> getGroupFromNoteDb(AccountGroup.UUID groupUuid) throws Exception {
-    try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
-      return GroupConfig.loadForGroup(allUsersName, allUsersRepo, groupUuid).getLoadedGroup();
-    }
-  }
-
-  private static OptionalSubject<InternalGroupSubject, InternalGroup> assertThatGroup(
-      Optional<InternalGroup> group) {
-    return assertThat(group, InternalGroupSubject::assertThat).named("group");
-  }
-
-  private void assertMemberAuditEvent(
-      GroupAuditEventInfo info,
-      Type expectedType,
-      Account.Id expectedUser,
-      AccountInfo expectedMember) {
-    assertThat(info.user._accountId).isEqualTo(expectedUser.get());
-    assertThat(info.type).isEqualTo(expectedType);
-    assertThat(info).isInstanceOf(UserMemberAuditEventInfo.class);
-    assertAccount(((UserMemberAuditEventInfo) info).member, expectedMember);
-  }
-
-  private void assertMemberAuditEvents(
-      GroupAuditEventInfo info1,
-      GroupAuditEventInfo info2,
-      Type expectedType,
-      Account.Id expectedUser,
-      AccountInfo expectedMember1,
-      AccountInfo expectedMember2) {
-    assertThat(info1).isInstanceOf(UserMemberAuditEventInfo.class);
-    assertThat(info2).isInstanceOf(UserMemberAuditEventInfo.class);
-
-    UserMemberAuditEventInfo event1 = (UserMemberAuditEventInfo) info1;
-    UserMemberAuditEventInfo event2 = (UserMemberAuditEventInfo) info2;
-
-    assertThat(event1.member._accountId)
-        .isAnyOf(expectedMember1._accountId, expectedMember2._accountId);
-    assertThat(event2.member._accountId)
-        .isAnyOf(expectedMember1._accountId, expectedMember2._accountId);
-    assertThat(event1.member._accountId).isNotEqualTo(event2.member._accountId);
-
-    if (event1.member._accountId == expectedMember1._accountId) {
-      assertMemberAuditEvent(info1, expectedType, expectedUser, expectedMember1);
-      assertMemberAuditEvent(info2, expectedType, expectedUser, expectedMember2);
-    } else {
-      assertMemberAuditEvent(info1, expectedType, expectedUser, expectedMember2);
-      assertMemberAuditEvent(info2, expectedType, expectedUser, expectedMember1);
-    }
-  }
-
-  private void assertSubgroupAuditEvent(
-      GroupAuditEventInfo info,
-      Type expectedType,
-      Account.Id expectedUser,
-      GroupInfo expectedSubGroup) {
-    assertThat(info.user._accountId).isEqualTo(expectedUser.get());
-    assertThat(info.type).isEqualTo(expectedType);
-    assertThat(info).isInstanceOf(GroupMemberAuditEventInfo.class);
-    assertGroup(((GroupMemberAuditEventInfo) info).member, expectedSubGroup);
-  }
-
-  private void assertSubgroupAuditEvents(
-      GroupAuditEventInfo info1,
-      GroupAuditEventInfo info2,
-      Type expectedType,
-      Account.Id expectedUser,
-      GroupInfo expectedSubGroup1,
-      GroupInfo expectedSubGroup2) {
-    assertThat(info1).isInstanceOf(GroupMemberAuditEventInfo.class);
-    assertThat(info2).isInstanceOf(GroupMemberAuditEventInfo.class);
-
-    GroupMemberAuditEventInfo event1 = (GroupMemberAuditEventInfo) info1;
-    GroupMemberAuditEventInfo event2 = (GroupMemberAuditEventInfo) info2;
-
-    assertThat(event1.member.id).isAnyOf(expectedSubGroup1.id, expectedSubGroup2.id);
-    assertThat(event2.member.id).isAnyOf(expectedSubGroup1.id, expectedSubGroup2.id);
-    assertThat(event1.member.id).isNotEqualTo(event2.member.id);
-
-    if (event1.member.id.equals(expectedSubGroup1.id)) {
-      assertSubgroupAuditEvent(info1, expectedType, expectedUser, expectedSubGroup1);
-      assertSubgroupAuditEvent(info2, expectedType, expectedUser, expectedSubGroup2);
-    } else {
-      assertSubgroupAuditEvent(info1, expectedType, expectedUser, expectedSubGroup2);
-      assertSubgroupAuditEvent(info2, expectedType, expectedUser, expectedSubGroup1);
-    }
-  }
-
-  private void assertAccount(AccountInfo actual, AccountInfo expected) {
-    assertThat(actual._accountId).isEqualTo(expected._accountId);
-    assertThat(actual.name).isEqualTo(expected.name);
-    assertThat(actual.email).isEqualTo(expected.email);
-    assertThat(actual.username).isEqualTo(expected.username);
-  }
-
-  private void assertGroup(GroupInfo actual, GroupInfo expected) {
-    assertThat(actual.id).isEqualTo(expected.id);
-    assertThat(actual.name).isEqualTo(expected.name);
-    assertThat(actual.groupId).isEqualTo(expected.groupId);
-  }
-
-  private GroupInfo groupInfoForExternalGroup(AccountGroup.UUID groupUuid) {
-    GroupInfo groupInfo = new GroupInfo();
-    groupInfo.id = IdString.fromDecoded(groupUuid.get()).encoded();
-
-    if (groupBackend.handles(groupUuid)) {
-      groupInfo.name = groupBackend.get(groupUuid).getName();
-    }
-
-    return groupInfo;
-  }
-
-  private static AccountGroup toAccountGroup(GroupInfo info) {
-    AccountGroup group =
-        new AccountGroup(
-            new AccountGroup.NameKey(info.name),
-            new AccountGroup.Id(info.groupId),
-            new AccountGroup.UUID(info.id),
-            info.createdOn);
-    group.setDescription(info.description);
-    if (info.ownerId != null) {
-      group.setOwnerGroupUUID(new AccountGroup.UUID(info.ownerId));
-    }
-    group.setVisibleToAll(
-        info.options != null && info.options.visibleToAll != null && info.options.visibleToAll);
-    return group;
-  }
-
-  private static GroupInfo toGroupInfo(AccountGroup group) {
-    GroupInfo groupInfo = new GroupInfo();
-    groupInfo.id = group.getGroupUUID().get();
-    groupInfo.groupId = group.getId().get();
-    groupInfo.name = group.getName();
-    groupInfo.createdOn = group.getCreatedOn();
-    groupInfo.description = group.getDescription();
-    groupInfo.owner = group.getOwnerGroupUUID().get();
-    groupInfo.options = new GroupOptionsInfo();
-    groupInfo.options.visibleToAll = group.isVisibleToAll() ? true : null;
-    return groupInfo;
-  }
-}
diff --git a/lib/BUILD b/lib/BUILD
index 95ca4db..e5e5765 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -80,6 +80,13 @@
 )
 
 java_library(
+    name = "guava-failureaccess",
+    data = ["//lib:LICENSE-Apache2.0"],
+    visibility = ["//visibility:public"],
+    exports = ["@guava-failureaccess//jar"],
+)
+
+java_library(
     name = "j2objc",
     data = ["//lib:LICENSE-Apache2.0"],
     visibility = ["//visibility:public"],
@@ -91,6 +98,7 @@
     data = ["//lib:LICENSE-Apache2.0"],
     visibility = ["//visibility:public"],
     exports = [
+        ":guava-failureaccess",
         ":j2objc",
         "@guava//jar",
     ],
@@ -497,10 +505,3 @@
     visibility = ["//visibility:public"],
     exports = ["@icu4j//jar"],
 )
-
-java_library(
-    name = "postgresql",
-    data = ["//lib:LICENSE-postgresql"],
-    visibility = ["//visibility:public"],
-    exports = ["@postgresql//jar"],
-)
diff --git a/lib/LICENSE-postgresql b/lib/LICENSE-postgresql
deleted file mode 100644
index fd416d2..0000000
--- a/lib/LICENSE-postgresql
+++ /dev/null
@@ -1,26 +0,0 @@
-Copyright (c) 1997-2011, PostgreSQL Global Development Group
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright notice,
-   this list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright notice,
-   this list of conditions and the following disclaimer in the documentation
-   and/or other materials provided with the distribution.
-3. Neither the name of the PostgreSQL Global Development Group nor the names
-   of its contributors may be used to endorse or promote products derived
-   from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/commons/BUILD b/lib/commons/BUILD
index bb36389..93d3c2f 100644
--- a/lib/commons/BUILD
+++ b/lib/commons/BUILD
@@ -3,21 +3,18 @@
 java_library(
     name = "codec",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-codec//jar"],
 )
 
 java_library(
     name = "compress",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-compress//jar"],
 )
 
 java_library(
     name = "lang",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-lang//jar"],
 )
 
@@ -30,14 +27,12 @@
 java_library(
     name = "net",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-net//jar"],
 )
 
 java_library(
     name = "dbcp",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-dbcp//jar"],
     runtime_deps = [":pool"],
 )
@@ -45,20 +40,17 @@
 java_library(
     name = "pool",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-pool//jar"],
 )
 
 java_library(
     name = "validator",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-validator//jar"],
 )
 
 java_library(
     name = "io",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-io//jar"],
 )
diff --git a/lib/elasticsearch-rest-client/BUILD b/lib/elasticsearch-rest-client/BUILD
index c6357d0..8df3c70 100644
--- a/lib/elasticsearch-rest-client/BUILD
+++ b/lib/elasticsearch-rest-client/BUILD
@@ -3,6 +3,5 @@
 java_library(
     name = "elasticsearch-rest-client",
     data = ["//lib:LICENSE-elasticsearch"],
-    visibility = ["//visibility:public"],
     exports = ["@elasticsearch-rest-client//jar"],
 )
diff --git a/lib/greenmail/BUILD b/lib/greenmail/BUILD
index b09f27b..5d8e1d6 100644
--- a/lib/greenmail/BUILD
+++ b/lib/greenmail/BUILD
@@ -13,7 +13,6 @@
     name = "greenmail",
     testonly = 1,
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@greenmail//jar"],
     runtime_deps = select({
         "//:java9": POST_JDK8_DEPS,
diff --git a/lib/guava.bzl b/lib/guava.bzl
index 1add718..e4c9083 100644
--- a/lib/guava.bzl
+++ b/lib/guava.bzl
@@ -1,5 +1,5 @@
-GUAVA_VERSION = "26.0-jre"
+GUAVA_VERSION = "27.0.1-jre"
 
-GUAVA_BIN_SHA1 = "6a806eff209f36f635f943e16d97491f00f6bfab"
+GUAVA_BIN_SHA1 = "bd41a290787b5301e63929676d792c507bbc00ae"
 
 GUAVA_DOC_URL = "https://google.github.io/guava/releases/" + GUAVA_VERSION + "/api/docs/"
diff --git a/lib/httpcomponents/BUILD b/lib/httpcomponents/BUILD
index a875eaf..03d9b68 100644
--- a/lib/httpcomponents/BUILD
+++ b/lib/httpcomponents/BUILD
@@ -3,7 +3,6 @@
 java_library(
     name = "fluent-hc",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@fluent-hc//jar"],
     runtime_deps = [":httpclient"],
 )
@@ -11,7 +10,6 @@
 java_library(
     name = "httpclient",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@httpclient//jar"],
     runtime_deps = [
         ":httpcore",
@@ -23,14 +21,16 @@
 java_library(
     name = "httpcore",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@httpcore//jar"],
 )
 
 java_library(
     name = "httpasyncclient",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//java/com/google/gerrit/elasticsearch:__pkg__"],
+    visibility = [
+        "//java/com/google/gerrit/elasticsearch:__pkg__",
+        "//javatests/com/google/gerrit/elasticsearch:__pkg__",
+    ],
     exports = ["@httpasyncclient//jar"],
 )
 
diff --git a/lib/jackson/BUILD b/lib/jackson/BUILD
index 3d751ab..0034748 100644
--- a/lib/jackson/BUILD
+++ b/lib/jackson/BUILD
@@ -1,5 +1,3 @@
-package(default_visibility = ["//visibility:public"])
-
 java_library(
     name = "jackson-core",
     data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
index de254be..4c576586 100644
--- a/lib/jgit/jgit.bzl
+++ b/lib/jgit/jgit.bzl
@@ -1,6 +1,6 @@
 load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_CENTRAL", "MAVEN_LOCAL", "maven_jar")
 
-_JGIT_VERS = "5.1.3.201810200350-r"
+_JGIT_VERS = "5.2.0.201812061821-r"
 
 _DOC_VERS = _JGIT_VERS  # Set to _JGIT_VERS unless using a snapshot
 
@@ -40,28 +40,28 @@
         name = "jgit-lib",
         artifact = "org.eclipse.jgit:org.eclipse.jgit:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "f270dbd1d792d5ad06074abe018a18644c90b60e",
-        src_sha1 = "00e24ee2b721040edbb8520d705607a7f7bafd64",
+        sha1 = "250269f30458084777a480895e390d2a42143da3",
+        src_sha1 = "eb28d59b3ed0c68a8ba54a38dfb7aa8af6ce624b",
         unsign = True,
     )
     maven_jar(
         name = "jgit-servlet",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "360405244c28b537f0eafdc0b9d9f3753503d981",
+        sha1 = "5d7fbe1c8528d881e2987c75e512df2cfa408d73",
         unsign = True,
     )
     maven_jar(
         name = "jgit-archive",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "08e10921fcc75ead2736dd5bf099ba8e2ed8a3fb",
+        sha1 = "6e49b0516b46ca90d394256d40c6069cdd8f2957",
     )
     maven_jar(
         name = "jgit-junit",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "1dc8f86bba3c461cb90c9dc3e91bf343889ca684",
+        sha1 = "723b9e6c54f8b3012dd7d4fe42b616b8d10ee230",
         unsign = True,
     )
 
diff --git a/lib/lucene/BUILD b/lib/lucene/BUILD
index 421caed..eab2ac8 100644
--- a/lib/lucene/BUILD
+++ b/lib/lucene/BUILD
@@ -11,13 +11,11 @@
         "@lucene-core//jar",
     ],
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
 )
 
 java_library(
     name = "lucene-analyzers-common",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@lucene-analyzers-common//jar"],
     runtime_deps = [":lucene-core-and-backward-codecs"],
 )
@@ -25,14 +23,12 @@
 java_library(
     name = "lucene-core",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@lucene-core//jar"],
 )
 
 java_library(
     name = "lucene-misc",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@lucene-misc//jar"],
     runtime_deps = [":lucene-core-and-backward-codecs"],
 )
@@ -40,7 +36,6 @@
 java_library(
     name = "lucene-queryparser",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@lucene-queryparser//jar"],
     runtime_deps = [":lucene-core-and-backward-codecs"],
 )
diff --git a/lib/mockito/BUILD b/lib/mockito/BUILD
new file mode 100644
index 0000000..7448462
--- /dev/null
+++ b/lib/mockito/BUILD
@@ -0,0 +1,35 @@
+package(
+    default_testonly = 1,
+    default_visibility = ["//visibility:private"],
+)
+
+java_library(
+    name = "mockito",
+    data = ["//lib:LICENSE-Apache2.0"],
+    # Only exposed for plugin tests; core tests should use Easymock
+    visibility = ["//java/com/google/gerrit/acceptance:__pkg__"],
+    exports = ["@mockito//jar"],
+    runtime_deps = [
+        ":byte-buddy",
+        ":byte-buddy-agent",
+        ":objenesis",
+    ],
+)
+
+java_library(
+    name = "byte-buddy",
+    data = ["//lib:LICENSE-Apache2.0"],
+    exports = ["@byte-buddy//jar"],
+)
+
+java_library(
+    name = "byte-buddy-agent",
+    data = ["//lib:LICENSE-Apache2.0"],
+    exports = ["@byte-buddy-agent//jar"],
+)
+
+java_library(
+    name = "objenesis",
+    data = ["//lib:LICENSE-Apache2.0"],
+    exports = ["@objenesis//jar"],
+)
diff --git a/lib/polymer_externs/BUILD b/lib/polymer_externs/BUILD
index 2f1bdbd..cd71d64 100644
--- a/lib/polymer_externs/BUILD
+++ b/lib/polymer_externs/BUILD
@@ -12,9 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-package(
-    default_visibility = ["//visibility:public"],
-)
+package(default_visibility = ["//visibility:public"])
 
 load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
 
diff --git a/lib/testcontainers/BUILD b/lib/testcontainers/BUILD
index f99365d..5e2d039 100644
--- a/lib/testcontainers/BUILD
+++ b/lib/testcontainers/BUILD
@@ -35,3 +35,12 @@
         "//lib/log:ext",
     ],
 )
+
+java_library(
+    name = "testcontainers-elasticsearch",
+    testonly = 1,
+    data = ["//lib:LICENSE-testcontainers"],
+    visibility = ["//visibility:public"],
+    exports = ["@testcontainers-elasticsearch//jar"],
+    runtime_deps = [":testcontainers"],
+)
diff --git a/plugins/delete-project b/plugins/delete-project
new file mode 160000
index 0000000..d8fdd55
--- /dev/null
+++ b/plugins/delete-project
@@ -0,0 +1 @@
+Subproject commit d8fdd5596181cc06707665051f0e03a49e5c3a97
diff --git a/plugins/hooks b/plugins/hooks
index de469e8..25ac76f 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit de469e8e2598779773652abb43a0356650e257b3
+Subproject commit 25ac76fe18537c33f9f27c5463a081449c13ba67
diff --git a/plugins/replication b/plugins/replication
index bc5efb5..534b041 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit bc5efb5b60a5a93c25c075f3667841e02532a99c
+Subproject commit 534b041232a196d244b041f35225d6f569e5e084
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 041ac8d..fdbadf3 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 041ac8d9fb0c0e5adff0aa37773a1931aced5a9a
+Subproject commit fdbadf312d829990d3a4be3491d13a79d6c0cf5b
diff --git a/plugins/webhooks b/plugins/webhooks
new file mode 160000
index 0000000..37b062e
--- /dev/null
+++ b/plugins/webhooks
@@ -0,0 +1 @@
+Subproject commit 37b062e08c5a83c4a939777cfddc9b6f97c44cdf
diff --git a/polygerrit-ui/BUILD b/polygerrit-ui/BUILD
index 384f835..5889ffd 100644
--- a/polygerrit-ui/BUILD
+++ b/polygerrit-ui/BUILD
@@ -1,6 +1,4 @@
-package(
-    default_visibility = ["//visibility:public"],
-)
+package(default_visibility = ["//visibility:public"])
 
 load("@io_bazel_rules_go//go:def.bzl", "go_binary")
 load("//tools/bzl:js.bzl", "bower_component_bundle")
@@ -51,7 +49,6 @@
         "zip -qr $$ROOT/$@ fonts",
     ]),
     output_to_bindir = 1,
-    visibility = ["//visibility:public"],
 )
 
 go_binary(
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index c735746..0aa70b8 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -1,6 +1,4 @@
-package(
-    default_visibility = ["//visibility:public"],
-)
+package(default_visibility = ["//visibility:public"])
 
 load(":rules.bzl", "polygerrit_bundle")
 load("//tools/bzl:genrule2.bzl", "genrule2")
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
index 9a3fc03..8c152b6 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
@@ -165,8 +165,9 @@
         hidden$="[[isColumnHidden('Assignee', visibleChangeTableColumns)]]">
       <template is="dom-if" if="[[change.assignee]]">
         <gr-account-link
+            id="assigneeAccountLink"
             account="[[change.assignee]]"
-            additional-text="[[_computeAccountStatusString(change.owner)]]"></gr-account-link>
+            additional-text="[[_computeAccountStatusString(change.assignee)]]"></gr-account-link>
       </template>
       <template is="dom-if" if="[[!change.assignee]]">
         <span class="placeholder">--</span>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index aaad362..3637653 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -182,10 +182,13 @@
       element.change = {
         assignee: {
           name: 'test',
+          status: 'test',
         },
       };
       flushAsynchronousOperations();
       assert.isOk(element.$$('.assignee gr-account-link'));
+      assert.equal(Polymer.dom(element.root)
+          .querySelector('#assigneeAccountLink').additionalText, '(test)');
     });
 
     test('_computeAccountStatusString', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index 098a4af..42a262a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -124,26 +124,13 @@
           '_groupsChanged(_groups.splices)',
         ],
 
-        attached() {
-          // Setup annotation layers.
-          const layers = [
-            this._createTrailingWhitespaceLayer(),
-            this.$.syntaxLayer,
-            this._createIntralineLayer(),
-            this._createTabIndicatorLayer(),
-            this.$.rangeLayer,
-          ];
-
-          // Get layers from plugins (if any).
-          for (const pluginLayer of this.$.jsAPI.getDiffLayers(
-              this.diffPath, this.changeNum, this.patchNum)) {
-            layers.push(pluginLayer);
-          }
-
-          this._layers = layers;
-        },
-
         render(keyLocations, prefs) {
+          // Setting up annotation layers must happen after plugins are
+          // installed, and |render| satisfies the requirement, however,
+          // |attached| doesn't because in the diff view page, the element is
+          // attached before plugins are installed.
+          this._setupAnnotationLayers();
+
           this.$.syntaxLayer.enabled = prefs.syntax_highlighting;
           this._showTabs = !!prefs.show_tabs;
           this._showTrailingWhitespace = !!prefs.show_whitespace_errors;
@@ -188,6 +175,24 @@
               });
         },
 
+        _setupAnnotationLayers() {
+          const layers = [
+            this._createTrailingWhitespaceLayer(),
+            this.$.syntaxLayer,
+            this._createIntralineLayer(),
+            this._createTabIndicatorLayer(),
+            this.$.rangeLayer,
+          ];
+
+          // Get layers from plugins (if any).
+          for (const pluginLayer of this.$.jsAPI.getDiffLayers(
+              this.diffPath, this.changeNum, this.patchNum)) {
+            layers.push(pluginLayer);
+          }
+
+          this._layers = layers;
+        },
+
         getLineElByChild(node) {
           while (node) {
             if (node instanceof Element) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index 1b0ba04..294d085 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -582,13 +582,14 @@
       setup(() => {
         element = fixture('basic');
         element._showTrailingWhitespace = true;
+        element._setupAnnotationLayers();
         initialLayersCount = element._layers.length;
       });
 
       test('no plugin layers', () => {
         const getDiffLayersStub = sinon.stub(element.$.jsAPI, 'getDiffLayers')
                                        .returns([]);
-        element.attached();
+        element._setupAnnotationLayers();
         assert.isTrue(getDiffLayersStub.called);
         assert.equal(element._layers.length, initialLayersCount);
       });
@@ -596,9 +597,9 @@
       test('with plugin layers', () => {
         const getDiffLayersStub = sinon.stub(element.$.jsAPI, 'getDiffLayers')
                                        .returns([{}, {}]);
-        element.attached();
+        element._setupAnnotationLayers();
         assert.isTrue(getDiffLayersStub.called);
-        assert.equal(element._layers.length, initialLayersCount+2);
+        assert.equal(element._layers.length, initialLayersCount + 2);
       });
     });
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
index 8b9d8066..fd460cc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
@@ -45,24 +45,6 @@
     return !!(diff.binary && (isA || isB));
   }
 
-  /**
-   * Compare two ranges. Either argument may be falsy, but will only return
-   * true if both are falsy or if neither are falsy and have the same position
-   * values.
-   *
-   * @param {Gerrit.Range=} a range 1
-   * @param {Gerrit.Range=} b range 2
-   * @return {boolean}
-   */
-  function rangesEqual(a, b) {
-    if (!a && !b) { return true; }
-    if (!a || !b) { return false; }
-    return a.start_line === b.start_line &&
-        a.start_character === b.start_character &&
-        a.end_line === b.end_line &&
-        a.end_character === b.end_character;
-  }
-
   /** @enum {string} */
   Gerrit.DiffSide = {
     LEFT: 'left',
@@ -652,7 +634,7 @@
       function matchesRange(threadEl) {
         const threadRange = /** @type {!Gerrit.Range} */(
             JSON.parse(threadEl.getAttribute('range')));
-        return rangesEqual(threadRange, range);
+        return Gerrit.rangesEqual(threadRange, range);
       }
 
       const filteredThreadEls = this._filterThreadElsForLocation(
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 996d484..b134d4e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -55,6 +55,24 @@
    *             end_line: number, end_character: number}} */
   Gerrit.Range;
 
+  /**
+   * Compare two ranges. Either argument may be falsy, but will only return
+   * true if both are falsy or if neither are falsy and have the same position
+   * values.
+   *
+   * @param {Gerrit.Range=} a range 1
+   * @param {Gerrit.Range=} b range 2
+   * @return {boolean}
+   */
+  Gerrit.rangesEqual = function(a, b) {
+    if (!a && !b) { return true; }
+    if (!a || !b) { return false; }
+    return a.start_line === b.start_line &&
+        a.start_character === b.start_character &&
+        a.end_line === b.end_line &&
+        a.end_character === b.end_character;
+  };
+
   function isThreadEl(node) {
     return node.nodeType === Node.ELEMENT_NODE &&
         node.classList.contains('comment-thread');
@@ -257,18 +275,14 @@
     _observeNodes() {
       this._nodeObserver = Polymer.dom(this).observeNodes(info => {
         const addedThreadEls = info.addedNodes.filter(isThreadEl);
-        // In principle we should also handle removed nodes, but I have not
-        // figured out how to do that yet without also catching all the removals
-        // caused by further redistribution. Right now, comments are never
-        // removed by no longer slotting them in, so I decided to not handle
-        // this situation until it occurs.
-        this._updateRanges(addedThreadEls);
-        this._updateKeyLocations(addedThreadEls);
+        const removedThreadEls = info.removedNodes.filter(isThreadEl);
+        this._updateRanges(addedThreadEls, removedThreadEls);
+        this._updateKeyLocations(addedThreadEls, removedThreadEls);
         this._redispatchHoverEvents(addedThreadEls);
       });
     },
 
-    _updateRanges(addedThreadEls) {
+    _updateRanges(addedThreadEls, removedThreadEls) {
       function commentRangeFromThreadEl(threadEl) {
         const side = threadEl.getAttribute('comment-side');
         const range = JSON.parse(threadEl.getAttribute('range'));
@@ -278,15 +292,30 @@
       const addedCommentRanges = addedThreadEls
           .map(commentRangeFromThreadEl)
           .filter(({range}) => range);
+      const removedCommentRanges = removedThreadEls
+          .map(commentRangeFromThreadEl)
+          .filter(({range}) => range);
+      for (const removedCommentRange of removedCommentRanges) {
+        const i = this._commentRanges.findIndex(commentRange => {
+          return commentRange.side === removedCommentRange.side &&
+              Gerrit.rangesEqual(commentRange.range, removedCommentRange.range);
+        });
+        this.splice('_commentRanges', i, 1);
+      }
       this.push('_commentRanges', ...addedCommentRanges);
     },
 
-    _updateKeyLocations(addedThreadEls) {
+    _updateKeyLocations(addedThreadEls, removedThreadEls) {
       for (const threadEl of addedThreadEls) {
         const commentSide = threadEl.getAttribute('comment-side');
         const lineNum = threadEl.getAttribute('line-num') || GrDiffLine.FILE;
         this._keyLocations[commentSide][lineNum] = true;
       }
+      for (const threadEl of removedThreadEls) {
+        const commentSide = threadEl.getAttribute('comment-side');
+        const lineNum = threadEl.getAttribute('line-num') || GrDiffLine.FILE;
+        this._keyLocations[commentSide][lineNum] = false;
+      }
     },
 
     // Dispatch events that are handled by the gr-diff-highlight.
@@ -628,11 +657,11 @@
     _handleRenderContent() {
       this._incrementalNodeObserver = Polymer.dom(this).observeNodes(info => {
         const addedThreadEls = info.addedNodes.filter(isThreadEl);
-        // In principle we should also handle removed nodes, but I have not
-        // figured out how to do that yet without also catching all the removals
-        // caused by further redistribution. Right now, comments are never
-        // removed by no longer slotting them in, so I decided to not handle
-        // this situation until it occurs.
+        // Removed nodes do not need to be handled because all this code does is
+        // adding a slot for the added thread elements, and the extra slots do
+        // not hurt. It's probably a bigger performance cost to remove them than
+        // to keep them around. Medium term we can even consider to add one slot
+        // for each line from the start.
         for (const threadEl of addedThreadEls) {
           const lineNumString = threadEl.getAttribute('line-num') || 'FILE';
           const commentSide = threadEl.getAttribute('comment-side');
diff --git a/proto/BUILD b/proto/BUILD
index 00d725a..88445c1 100644
--- a/proto/BUILD
+++ b/proto/BUILD
@@ -9,13 +9,6 @@
     deps = [":cache_proto"],
 )
 
-genrule(
-    name = "gen_reviewdb_proto",
-    outs = ["reviewdb.proto"],
-    cmd = "$(location //java/com/google/gerrit/proto:ProtoGen) -o $@",
-    tools = ["//java/com/google/gerrit/proto:ProtoGen"],
-)
-
 proto_library(
     name = "reviewdb_proto",
     srcs = [":reviewdb.proto"],
diff --git a/proto/reviewdb.proto b/proto/reviewdb.proto
new file mode 100644
index 0000000..bc45eea
--- /dev/null
+++ b/proto/reviewdb.proto
@@ -0,0 +1,211 @@
+// Copyright (C) 2018 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.
+
+syntax = "proto2";
+
+package devtools.gerritcodereview;
+
+option java_package = "com.google.gerrit.proto.reviewdb";
+
+// Serialized form of com.google.gerrit.reviewdb.client.Change.Id.
+// Next ID: 2
+message Change_Id {
+  required int32 id = 1;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.Change.Key.
+// Next ID: 2
+message Change_Key {
+  optional string id = 1;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.Change.
+// Next ID: 24
+message Change {
+  required Change_Id change_id = 1;
+  optional Change_Key change_key = 2;
+  optional int32 row_version = 3;
+  optional fixed64 created_on = 4;
+  optional fixed64 last_updated_on = 5;
+  optional Account_Id owner_account_id = 7;
+  optional Branch_NameKey dest = 8;
+  optional uint32 status = 10;
+  optional int32 current_patch_set_id = 12;
+  optional string subject = 13;
+  optional string topic = 14;
+  optional string original_subject = 17;
+  optional string submission_id = 18;
+  optional Account_Id assignee = 19;
+  optional bool is_private = 20;
+  optional bool work_in_progress = 21;
+  optional bool review_started = 22;
+  optional Change_Id revert_of = 23;
+  optional string note_db_state = 101;
+
+  // Deleted fields, should not be reused:
+  reserved 6;  // sortkey
+  reserved 9;  // open
+  reserved 11; // nbrPatchSets
+  reserved 15; // lastSha1MergeTested
+  reserved 16; // mergeable
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.ChangeMessage.
+// Next ID: 3
+message ChangeMessage_Key {
+  required Change_Id change_id = 1;
+  required string uuid = 2;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.ChangeMessage.
+// Next ID: 8
+message ChangeMessage {
+  required ChangeMessage_Key key = 1;
+  optional Account_Id author_id = 2;
+  optional fixed64 written_on = 3;
+  optional string message = 4;
+  optional PatchSet_Id patchset = 5;
+  optional string tag = 6;
+  optional Account_Id real_author = 7;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.Patch.Key.
+// Next ID: 3
+message Patch_Key {
+  required PatchSet_Id patch_set_id = 1;
+  required string file_name = 2;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.PatchSet.Id.
+// Next ID: 3
+message PatchSet_Id {
+  required Change_Id change_id = 1;
+  required int32 patch_set_id = 2;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.PatchSet.
+// Next ID: 10
+message PatchSet {
+  required PatchSet_Id id = 1;
+  optional RevId revision = 2;
+  optional Account_Id uploader_account_id = 3;
+  optional fixed64 created_on = 4;
+  optional string groups = 6;
+  optional string push_certificate = 8;
+  optional string description = 9;
+
+  // Deleted fields, should not be reused:
+  reserved 5;  // draft
+  reserved 7;  // pushCertficate
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.PatchLineComment.Key.
+// Next ID: 3
+message PatchLineComment_Key {
+  required Patch_Key patch_key = 1;
+  required string uuid = 2;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.PatchLineComment.
+// Next ID: 13
+message PatchLineComment {
+  required PatchLineComment_Key key = 1;
+  optional int32 line_nbr = 2;
+  optional Account_Id author_id = 3;
+  optional fixed64 written_on = 4;
+  optional uint32 status = 5;
+  optional int32 side = 6;
+  optional string message = 7;
+  optional string parent_uuid = 8;
+  optional CommentRange range = 9;
+  optional string tag = 10;
+  optional Account_Id real_author = 11;
+  optional bool unresolved = 12;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.Account.Id.
+// Next ID: 2
+message Account_Id {
+  required int32 id = 1;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.LabelId.
+// Next ID: 2
+message LabelId {
+  required string id = 1;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.PatchSetApproval.Key.
+// Next ID: 4
+message PatchSetApproval_Key {
+  required PatchSet_Id patch_set_id = 1;
+  required Account_Id account_id = 2;
+  required LabelId category_id = 3;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.PatchSetApproval.
+// Next ID: 9
+message PatchSetApproval {
+  required PatchSetApproval_Key key = 1;
+  optional int32 value = 2;
+  optional fixed64 granted = 3;
+  optional string tag = 6;
+  optional Account_Id real_account_id = 7;
+  optional bool post_submit = 8;
+
+  // Deleted fields, should not be reused:
+  reserved 4;  // changeOpen
+  reserved 5;  // changeSortKey
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.CurrentSchemaVersion.Key.
+// Next ID: 2
+message CurrentSchemaVersion_Key {
+  required string one = 1;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.CurrentSchemaVersion.
+// Next ID: 3
+message CurrentSchemaVersion {
+  required CurrentSchemaVersion_Key singleton = 1;
+  optional int32 version_nbr = 2;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.Project.NameKey.
+// Next ID: 2
+message Project_NameKey {
+  optional string name = 1;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.Branch.NameKey.
+// Next ID: 3
+message Branch_NameKey {
+  optional Project_NameKey project_name = 1;
+  optional string branch_name = 2;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.CommentRange.
+// Next ID: 5
+message CommentRange {
+  optional int32 start_line = 1;
+  optional int32 start_character = 2;
+  optional int32 end_line = 3;
+  optional int32 end_character = 4;
+}
+
+// Serialized form of com.google.gerrit.reviewdb.client.RevId.
+// Next ID: 2
+message RevId {
+  optional string id = 1;
+}
diff --git a/resources/com/google/gerrit/reviewdb/BUILD b/resources/com/google/gerrit/reviewdb/BUILD
deleted file mode 100644
index 8a1b457..0000000
--- a/resources/com/google/gerrit/reviewdb/BUILD
+++ /dev/null
@@ -1,8 +0,0 @@
-filegroup(
-    name = "reviewdb",
-    srcs = glob(
-        ["**/*"],
-        exclude = ["BUILD"],
-    ),
-    visibility = ["//visibility:public"],
-)
diff --git a/resources/com/google/gerrit/reviewdb/server/index_generic.sql b/resources/com/google/gerrit/reviewdb/server/index_generic.sql
deleted file mode 100644
index c58edb7..0000000
--- a/resources/com/google/gerrit/reviewdb/server/index_generic.sql
+++ /dev/null
@@ -1,40 +0,0 @@
--- Gerrit 2 : Generic
---
-
--- Indexes to support @Query
---
-
--- *********************************************************************
--- ApprovalCategoryAccess
---    too small to bother indexing
-
-
--- *********************************************************************
--- ApprovalCategoryValueAccess
---     @PrimaryKey covers: byCategory
-
-
--- *********************************************************************
--- BranchAccess
---    @PrimaryKey covers: byProject
-
-
--- *********************************************************************
--- ChangeMessageAccess
---    @PrimaryKey covers: byChange
-
---    covers:             byPatchSet
-CREATE INDEX change_messages_byPatchset
-ON change_messages (patchset_change_id, patchset_patch_set_id);
-
--- *********************************************************************
--- PatchLineCommentAccess
---    @PrimaryKey covers: published, draft
-CREATE INDEX patch_comment_drafts
-ON patch_comments (status, author_id);
-
-
--- *********************************************************************
--- PatchSetAccess
-CREATE INDEX patch_sets_byRevision
-ON patch_sets (revision);
diff --git a/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql b/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql
deleted file mode 100644
index 7f0f1bd..0000000
--- a/resources/com/google/gerrit/reviewdb/server/index_maxdb.sql
+++ /dev/null
@@ -1,43 +0,0 @@
-delimiter #
--- Gerrit 2 : MaxDB
---
-
--- Indexes to support @Query
---
-
--- *********************************************************************
--- ApprovalCategoryAccess
---    too small to bother indexing
-
-
--- *********************************************************************
--- ApprovalCategoryValueAccess
---     @PrimaryKey covers: byCategory
-
-
--- *********************************************************************
--- BranchAccess
---    @PrimaryKey covers: byProject
-
-
--- *********************************************************************
--- ChangeMessageAccess
---    @PrimaryKey covers: byChange
-
---    covers:             byPatchSet
-CREATE INDEX change_messages_byPatchset
-ON change_messages (patchset_change_id, patchset_patch_set_id)
-#
-
--- *********************************************************************
--- PatchLineCommentAccess
---    @PrimaryKey covers: published, draft
-CREATE INDEX patch_comment_drafts
-ON patch_comments (status, author_id)
-#
-
--- *********************************************************************
--- PatchSetAccess
-CREATE INDEX patch_sets_byRevision
-ON patch_sets (revision)
-#
diff --git a/resources/com/google/gerrit/reviewdb/server/index_postgres.sql b/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
deleted file mode 100644
index f2f24e1..0000000
--- a/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
+++ /dev/null
@@ -1,88 +0,0 @@
--- Gerrit 2 : PostgreSQL
---
-
--- Cluster hot tables by their primary method of access
---
-ALTER TABLE patch_sets CLUSTER ON patch_sets_pkey;
-ALTER TABLE change_messages CLUSTER ON change_messages_pkey;
-ALTER TABLE patch_comments CLUSTER ON patch_comments_pkey;
-ALTER TABLE patch_set_approvals CLUSTER ON patch_set_approvals_pkey;
-
-ALTER TABLE account_group_members CLUSTER ON account_group_members_pkey;
-CLUSTER;
-
-
--- Define function for conditional installation of PL/pgSQL.
--- This is required, because starting with PostgreSQL 9.0, PL/pgSQL
--- language is installed by default and database returns error when
--- we try to install it again.
---
--- Source: http://wiki.postgresql.org/wiki/CREATE_OR_REPLACE_LANGUAGE
--- Author: David Fetter
---
-
-delimiter //
-
-CREATE OR REPLACE FUNCTION make_plpgsql()
-RETURNS VOID
-LANGUAGE SQL
-AS $$
-CREATE LANGUAGE plpgsql;
-$$;
-
-//
-
-delimiter ;
-
-SELECT
-    CASE
-    WHEN EXISTS(
-        SELECT 1
-        FROM pg_catalog.pg_language
-        WHERE lanname='plpgsql'
-    )
-    THEN NULL
-    ELSE make_plpgsql() END;
-
-DROP FUNCTION make_plpgsql();
-
-delimiter ;
-
--- Indexes to support @Query
---
-
--- *********************************************************************
--- ApprovalCategoryAccess
---    too small to bother indexing
-
-
--- *********************************************************************
--- ApprovalCategoryValueAccess
---     @PrimaryKey covers: byCategory
-
-
--- *********************************************************************
--- BranchAccess
---    @PrimaryKey covers: byProject
-
-
--- *********************************************************************
--- ChangeMessageAccess
---    @PrimaryKey covers: byChange
-
---    covers:             byPatchSet
-CREATE INDEX change_messages_byPatchset
-ON change_messages (patchset_change_id, patchset_patch_set_id);
-
--- *********************************************************************
--- PatchLineCommentAccess
---    @PrimaryKey covers: published, draft
-CREATE INDEX patch_comment_drafts
-ON patch_comments (author_id)
-WHERE status = 'd';
-
-
--- *********************************************************************
--- PatchSetAccess
-CREATE INDEX patch_sets_byRevision
-ON patch_sets (revision);
diff --git a/tools/bzl/pkg_war.bzl b/tools/bzl/pkg_war.bzl
index 72de14b..1fd1c81 100644
--- a/tools/bzl/pkg_war.bzl
+++ b/tools/bzl/pkg_war.bzl
@@ -19,7 +19,6 @@
 LIBS = [
     "//java/com/google/gerrit/common:version",
     "//java/com/google/gerrit/httpd/init",
-    "//lib:postgresql",
     "//lib/bouncycastle:bcpkix",
     "//lib/bouncycastle:bcprov",
     "//lib/bouncycastle:bcpg",
diff --git a/tools/bzl/plugins.bzl b/tools/bzl/plugins.bzl
index 7fd7625..4b4065f 100644
--- a/tools/bzl/plugins.bzl
+++ b/tools/bzl/plugins.bzl
@@ -1,11 +1,13 @@
 CORE_PLUGINS = [
     "codemirror-editor",
     "commit-message-length-validator",
+    "delete-project",
     "download-commands",
     "hooks",
     "replication",
     "reviewnotes",
     "singleusergroup",
+    "webhooks",
 ]
 
 CUSTOM_PLUGINS = [
diff --git a/webapp/WEB-INF/extra/jetty7/gerrit.xml b/webapp/WEB-INF/extra/jetty7/gerrit.xml
index cb0a256..4102f56 100644
--- a/webapp/WEB-INF/extra/jetty7/gerrit.xml
+++ b/webapp/WEB-INF/extra/jetty7/gerrit.xml
@@ -6,11 +6,7 @@
   so it answers to simple URLs like "/$changeid" and "/mine".
 
   * Copy this file to $JETTY_HOME/contexts/gerrit.xml
-  * Edit url, username, password as necessary below for database.
 
-  * Copy commons-dbcp-*.jar     to $JETTY_HOME/lib/ext/
-  * Copy commons-pool-*.jar     to $JETTY_HOME/lib/ext/
-  * Copy JDBC driver            to $JETTY_HOME/lib/ext/
   * Copy www/gerrit-*.war       to $JETTY_HOME/webapps/gerrit.war
 
   * Make sure you remove $JETTY_HOME/context/test.xml
@@ -33,36 +29,4 @@
       <Item>org.eclipse.jetty.webapp.JettyWebXmlConfiguration</Item>
     </Array>
   </Set>
-
-  <New id="ReviewDb" class="org.eclipse.jetty.plus.jndi.Resource">
-    <Arg></Arg>
-    <Arg>jdbc/ReviewDb</Arg>
-    <Arg>
-      <New class="org.apache.commons.dbcp.BasicDataSource">
-<!--  PostgreSQL
-        <Set name="driverClassName">org.postgresql.Driver</Set>
-        <Set name="url">jdbc:postgresql:reviewdb</Set>
-        <Set name="username">gerrit</Set>
-        <Set name="password">secretkey</Set>
--->
-<!--  MySQL
-        <Set name="driverClassName">com.mysql.jdbc.Driver</Set>
-        <Set name="url">jdbc:mysql://localhost/reviewdb?user=gerrit&amp;password=secretkey</Set>
--->
-<!--  MariaDB
-        <Set name="driverClassName">org.mariadb.jdbc.Driver</Set>
-        <Set name="url">jdbc:mariadb://localhost/reviewdb?user=gerrit&amp;password=secretkey</Set>
--->
-<!--  H2
-        <Set name="driverClassName">org.h2.Driver</Set>
-        <Set name="url">jdbc:h2:file:ReviewDb</Set>
--->
-        <Set name="initialSize">4</Set>
-        <Set name="maxActive">8</Set>
-        <Set name="minIdle">4</Set>
-        <Set name="maxIdle">4</Set>
-        <Set name="maxWait">30000</Set>
-      </New>
-    </Arg>
-  </New>
 </Configure>