Merge "Insert Change-Id into access right changes"
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index b082339..ff252f7 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -907,7 +907,7 @@
 * xref:category_forge_author[`Forge Author Identity`] to 'refs/heads/*'
 * link:config-labels.html#label_Code-Review[`Label: Code-Review`] with range '-2' to '+2' for 'refs/heads/*'
 * link:config-labels.html#label_Verified[`Label: Verified`] with range '-1' to '+1' for 'refs/heads/*'
-* xref:category_submit[`Submit`]
+* xref:category_submit[`Submit`] on 'refs/heads/*'
 
 If the project is small or the developers are seasoned it might make
 sense to give them the freedom to push commits directly to a branch.
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index c754f35..dcdbb07 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -153,6 +153,19 @@
 eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
 created.
 
+=== Project Created
+
+Sent when a new project has been created.
+
+type:: "project-created"
+
+projectName:: The created project name
+
+projectHead:: The created project head name
+
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
 === Merge Failed
 
 Sent when a change has failed to be merged into the git repository.
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index bd8980f..742d996 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -502,15 +502,17 @@
 * y, year, years (`1 year` is treated as `365 days`)
 
 +
+--
 If a unit suffix is not specified, `seconds` is assumed.  If 0 is
 supplied, the maximum age is infinite and items are never purged
 except when the cache is full.
-+
+
 Default is `0`, meaning store forever with no expire, except:
-+
+
 * `"adv_bases"`: default is `10 minutes`
 * `"ldap_groups"`: default is `1 hour`
 * `"web_sessions"`: default is `12 hours`
+--
 
 [[cache.name.memoryLimit]]cache.<name>.memoryLimit::
 +
@@ -735,9 +737,11 @@
 * h, hr, hour, hours
 
 +
+--
 If a unit suffix is not specified, `milliseconds` is assumed.
-+
+
 Default is 5 seconds.
+--
 
 [[cache.diff_intraline.timeout]]cache.diff_intraline.timeout::
 +
@@ -758,9 +762,11 @@
 * h, hr, hour, hours
 
 +
+--
 If a unit suffix is not specified, `milliseconds` is assumed.
-+
+
 Default is 5 seconds.
+--
 
 [[cache.diff_intraline.enabled]]cache.diff_intraline.enabled::
 +
@@ -1205,7 +1211,7 @@
 [[core.useRecursiveMerge]]core.useRecursiveMerge::
 +
 Use JGit's recursive merger for three-way merges. This only affects
-projects configured to automatically resolve conflicts.
+projects that allow content merges.
 +
 As explained in this
 link:http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html[
@@ -1345,12 +1351,14 @@
 * 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::
 
@@ -1824,6 +1832,11 @@
 Optional filename for the hashtags changed hook, if not specified then
 `hashtags-changed` will be used.
 
+[[hooks.projectCreatedHook]]hooks.projectCreatedHook::
++
+Optional filename for the project created hook, if not specified then
+`project-created` will be used.
+
 [[hooks.mergeFailedHook]]hooks.mergeFailedHook::
 +
 Optional filename for the merge failed hook, if not specified then
@@ -1947,10 +1960,12 @@
 'https://' is the proper URL back to the server.
 
 +
+--
 If multiple values are supplied, the daemon will listen on all
 of them.
-+
+
 By default, http://*:8080.
+--
 
 [[httpd.reuseAddress]]httpd.reuseAddress::
 +
@@ -2078,11 +2093,13 @@
 * y, year, years (`1 year` is treated as `365 days`)
 
 +
+--
 If a unit suffix is not specified, `minutes` is assumed.  If 0
 is supplied, the maximum age is infinite and connections will not
 abort until the client disconnects.
-+
+
 By default, 5 minutes.
+--
 
 [[httpd.filterClass]]httpd.filterClass::
 +
@@ -3082,15 +3099,17 @@
 * 'hostname':'port' (for example `review.example.com:29418`)
 * 'IPv4':'port' (for example `10.0.0.1:29418`)
 * ['IPv6']:'port' (for example `[ff02::1]:29418`)
-* *:'port' (for example `*:29418`)
+* +*:'port'+ (for example `+*:29418+`)
 
 +
+--
 If multiple values are supplied, the daemon will listen on all
 of them.
-+
+
 To disable the internal SSHD, set listenAddress to `off`.
-+
+
 By default, *:29418.
+--
 
 [[sshd.advertisedAddress]]sshd.advertisedAddress::
 +
@@ -3099,16 +3118,18 @@
 redirector is being used, making Gerrit appear to answer on port
 22. The following forms may be used to specify an address.  In any
 form, `:'port'` may be omitted to use the default SSH port of 22.
-+
+
 * 'hostname':'port' (for example `review.example.com:22`)
 * 'IPv4':'port' (for example `10.0.0.1:29418`)
 * ['IPv6']:'port' (for example `[ff02::1]:29418`)
 
 +
+--
 If multiple values are supplied, the daemon will advertise all
 of them.
-+
+
 By default, sshd.listenAddress.
+--
 
 [[sshd.tcpKeepAlive]]sshd.tcpKeepAlive::
 +
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 5666dff..d4f53bf 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -110,6 +110,14 @@
   ref-updated --oldrev <old rev> --newrev <new rev> --refname <ref name> --project <project name> --submitter <submitter>
 ====
 
+=== project-created
+
+Called whenever a project has been created.
+
+====
+  project-created --project <project name> --head <head name>
+====
+
 === reviewer-added
 
 Called whenever a reviewer is added to a change.
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index 6ba85e0..3887ff3 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -39,6 +39,7 @@
 The core plugins are developed and maintained by the Gerrit maintainers
 and the Gerrit community.
 
+[[commit-message-length-validator]]
 === commit-message-length-validator
 
 This plugin checks the length of a commit’s commit message subject and
@@ -52,6 +53,7 @@
 link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[cookbook-plugin]]
 === cookbook-plugin
 
 Sample plugin to demonstrate features of Gerrit's plugin API.
@@ -61,6 +63,7 @@
 link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
+[[download-commands]]
 === download-commands
 
 This plugin defines commands for downloading changes in different
@@ -73,6 +76,7 @@
 link:https://gerrit.googlesource.com/plugins/download-commands/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[replication]]
 === replication
 
 This plugin can automatically push any changes Gerrit Code Review makes
@@ -87,6 +91,7 @@
 link:https://gerrit.googlesource.com/plugins/replication/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[reviewnotes]]
 === reviewnotes
 
 Stores review information for Gerrit changes in the `refs/notes/review`
@@ -97,6 +102,7 @@
 link:https://gerrit.googlesource.com/plugins/reviewnotes/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
+[[singleusergroup]]
 === singleusergroup
 
 This plugin provides a group per user. This is useful to assign access
@@ -121,6 +127,7 @@
 link:https://gerrit-review.googlesource.com/#/admin/projects/?filter=plugins%252F[
 gerrit-review].
 
+[[admin-console]]
 === admin-console
 
 Plugin to provide administrator-only functionality, intended to
@@ -133,6 +140,7 @@
 link:https://gerrit.googlesource.com/plugins/admin-console/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
+[[avatars-external]]
 === avatars/external
 
 This plugin allows to use an external url to load the avatar images
@@ -145,6 +153,7 @@
 link:https://gerrit.googlesource.com/plugins/avatars/external/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[avatars-gravatar]]
 === avatars/gravatar
 
 Plugin to display user icons from Gravatar.
@@ -152,6 +161,7 @@
 link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/avatars/gravatar[
 Project]
 
+[[branch-network]]
 === branch-network
 
 This plugin allows the rendering of Git repository branch network in a
@@ -166,6 +176,7 @@
 link:https://gerrit.googlesource.com/plugins/branch-network/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[changemessage]]
 === changemessage
 
 This plugin allows to display a static info message on the change screen.
@@ -177,6 +188,7 @@
 link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[codenvy]]
 === codenvy
 
 Plugin to allow to edit code on-line on either an existing branch or an
@@ -186,6 +198,7 @@
 link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/codenvy[
 Project]
 
+[[delete-project]]
 === delete-project
 
 Provides the ability to delete a project.
@@ -195,6 +208,7 @@
 link:https://gerrit.googlesource.com/plugins/delete-project/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
+[[egit]]
 === egit
 
 This plugin provides extensions for easier usage with EGit.
@@ -208,6 +222,7 @@
 link:https://gerrit.googlesource.com/plugins/egit/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
+[[force-draft]]
 === force-draft
 
 Provides an ssh command to force a change or patch set to draft status.
@@ -217,6 +232,7 @@
 link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/force-draft[
 Project]
 
+[[gitblit]]
 === gitblit
 
 GitBlit code-viewer plugin with SSO and Security Access Control.
@@ -224,6 +240,7 @@
 link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/gitblit[
 Project]
 
+[[github]]
 === github
 
 Plugin to integrate with GitHub: replication, pull-request to Change-Sets
@@ -231,6 +248,7 @@
 link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/github[
 Project]
 
+[[gitiles]]
 === gitiles
 
 Plugin running Gitiles alongside a Gerrit server.
@@ -238,6 +256,7 @@
 link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/gitiles[
 Project]
 
+[[imagare]]
 === imagare
 
 The imagare plugin allows Gerrit users to upload and share images.
@@ -249,6 +268,33 @@
 link:https://gerrit.googlesource.com/plugins/imagare/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[importer]]
+=== importer
+
+The importer plugin allows to import projects from one Gerrit server
+into another Gerrit server.
+
+Projects can be imported while both source and target Gerrit server
+are online. There is no downtime required.
+
+The git repository and all changes of the project, including approvals
+and review comments, are imported. Historic timestamps are preserved.
+
+Project imports can be resumed. This means a project team can continue
+to work in the source system while the import to the target system is
+done. By resuming the import the project in the target system can be
+updated with the missing delta.
+
+The importer plugin can also be used to copy a project within one Gerrit
+server, and in combination with the link:#delete-project[delete-project]
+plugin it can be used to rename a project.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/importer[
+Project] |
+link:https://gerrit.googlesource.com/plugins/importer/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
+[[its-plugins]]
 === Issue Tracker System Plugins
 
 Plugins to integrate with issue tracker systems (ITS), that (based
@@ -266,6 +312,7 @@
 link:https://gerrit.googlesource.com/plugins/its-base/+doc/master/src/main/resources/Documentation/config.md[
 its-base Configuration]
 
+[[its-bugzilla]]
 ==== its-bugzilla
 
 Plugin to integrate with Bugzilla.
@@ -275,6 +322,7 @@
 link:https://gerrit.googlesource.com/plugins/its-bugzilla/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
+[[its-jira]]
 ==== its-jira
 
 Plugin to integrate with Jira.
@@ -284,6 +332,7 @@
 link:https://gerrit.googlesource.com/plugins/its-jira/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[its-rtc]]
 ==== its-rtc
 
 Plugin to integrate with IBM Rational Team Concert (RTC).
@@ -293,6 +342,7 @@
 link:https://gerrit.googlesource.com/plugins/its-rtc/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[javamelody]]
 === javamelody
 
 This plugin allows to monitor the Gerrit server.
@@ -307,6 +357,7 @@
 https://gerrit.googlesource.com/plugins/javamelody/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[menuextender]]
 === menuextender
 
 The menuextender plugin allows Gerrit administrators to configure
@@ -319,6 +370,7 @@
 link:https://gerrit.googlesource.com/plugins/menuextender/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[motd]]
 === motd
 
 This plugin can output messages to clients when pulling/fetching/cloning
@@ -334,8 +386,8 @@
 link:https://gerrit.googlesource.com/plugins/motd/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[oauth-authentication-provider]]
 === OAuth authentication provider
-
 This plugin enables Gerrit to use OAuth2 protocol for authentication.
 Two different OAuth providers are supported:
 
@@ -345,6 +397,7 @@
 https://github.com/davido/gerrit-oauth-provider[Project] |
 https://github.com/davido/gerrit-oauth-provider/wiki/Getting-Started[Configuration]
 
+[[project-download-commands]]
 === project-download-commands
 
 This plugin adds support for project specific download commands.
@@ -360,6 +413,7 @@
 link:https://gerrit.googlesource.com/plugins/project-download-commands/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[quota]]
 === quota
 
 This plugin allows to enforce quotas in Gerrit.
@@ -375,6 +429,7 @@
 link:https://gerrit.googlesource.com/plugins/quota/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[reviewers]]
 === reviewers
 
 A plugin that allows adding default reviewers to a change.
@@ -386,6 +441,7 @@
 link:https://gerrit.googlesource.com/plugins/reviewers/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[reviewers-by-blame]]
 === reviewers-by-blame
 
 A plugin that allows automatically adding reviewers to a change from
@@ -401,6 +457,7 @@
 link:https://gerrit.googlesource.com/plugins/reviewers-by-blame/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[groovy-provider]]
 === scripting/groovy-provider
 
 This plugin provides a Groovy runtime environment for Gerrit plugins in Groovy.
@@ -410,6 +467,7 @@
 link:https://gerrit.googlesource.com/plugins/scripting/groovy-provider/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
+[[scala-provider]]
 === scripting/scala-provider
 
 This plugin provides a Scala runtime environment for Gerrit plugins in Scala.
@@ -419,6 +477,7 @@
 link:https://gerrit.googlesource.com/plugins/scripting/scala-provider/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
+[[server-config]]
 === server-config
 
 This plugin enables access (download and upload) to the server config
@@ -430,6 +489,7 @@
 link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripting/server-config[
 Project]
 
+[[serviceuser]]
 === serviceuser
 
 This plugin allows to create service users in Gerrit.
@@ -446,6 +506,7 @@
 link:https://gerrit.googlesource.com/plugins/serviceuser/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[uploadvalidator]]
 === uploadvalidator
 
 This plugin allows to configure upload validations per project.
@@ -461,6 +522,7 @@
 link:https://gerrit.googlesource.com/plugins/uploadvalidator/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[websession-flatfile]]
 === websession-flatfile
 
 This plugin replaces the built-in Gerrit H2 based websession cache with
@@ -475,6 +537,7 @@
 link:https://gerrit.googlesource.com/plugins/websession-flatfile/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[wip]]
 === wip
 
 This plugin adds a new button that allows a change owner to set a
@@ -492,6 +555,7 @@
 link:https://gerrit.googlesource.com/plugins/wip/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[x-docs]]
 === x-docs
 
 This plugin serves project documentation as HTML pages.
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index ad036a3..89cd849 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -7,12 +7,12 @@
 
 There is currently no binary distribution of Buck, so it has to be manually
 built and installed.  Apache Ant is required.  Currently only Linux and Mac
-OS are supported.  Buck requires Python version 2.7 to be installed.
+OS are supported.
 
 Clone the git and build it:
 
 ----
-  git clone https://gerrit.googlesource.com/buck
+  git clone https://github.com/facebook/buck
   cd buck
   git checkout $(cat ../gerrit/.buckversion)
   ant
@@ -50,6 +50,10 @@
 script from `./scripts/buck_completion.bash` in the buck project.  Refer
 to the script's header comments for installation instructions.
 
+== Prerequisites
+
+Buck requires Python version 2.7 to be installed. The Maven download toolchain
+requires `curl` to be installed.
 
 [[eclipse]]
 == Eclipse Integration
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt
index f3397a4..b2c5358 100644
--- a/Documentation/dev-release-deploy-config.txt
+++ b/Documentation/dev-release-deploy-config.txt
@@ -10,12 +10,11 @@
 To be able to publish artifacts to Maven Central some preparations must
 be done:
 
-* Create a Sonatype account as described in the
-link:https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide#SonatypeOSSMavenRepositoryUsageGuide-2.Signup[
-Sonatype OSS Maven Repository Usage Guide].
+* Create an account on
+link:https://issues.sonatype.org/secure/Signup!default.jspa[Sonatype's Jira].
 +
 Sonatype is the company that runs Maven Central and you need a Sonatype
-account for uploading artifacts to Maven Central.
+account to be able to upload artifacts to Maven Central.
 
 * Configure your Sonatype user and password in `~/.m2/settings.xml`:
 +
@@ -75,7 +74,7 @@
 
 Gerrit Subproject Artifacts are stored on
 link:https://developers.google.com/storage/[Google Cloud Storage].
-Via the link:https://code.google.com/apis/console/?noredirect[API Console] the
+Via the link:https://console.developers.google.com/project/164060093628[Developers Console] the
 Gerrit maintainers have access to the `Gerrit Code Review` project.
 This projects host several buckets for storing Gerrit artifacts:
 
@@ -90,13 +89,14 @@
 
 To upload artifacts to a bucket the user must authenticate with a
 username and password. The username and password need to be retrieved
-from the link:https://code.google.com/apis/console/?noredirect[API Console]:
+from the link:https://console.developers.google.com/project/164060093628[
+Google Developers Console]:
 
-* Go to the `Gerrit Code Review` project
-* In the menu on the left select `Google Cloud Storage` >
-`Interoperable Access`
-* Use the `Access Key` as username
-* Click under `Secret` on the `Show` button to find the password
+* In the menu on the left select `Storage` -> `Cloud Storage` >
+> `Storage access`
+* Select the `Interoperability` tab
+* If no keys are listed under `Interoperable storage access keys`, select "Create a new key"
+* Use the `Access Key` as username, and `Secret` as the password
 
 To make the username and password known to Maven, they must be
 configured in the `~/.m2/settings.xml` file.
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 3d3104c..fc1ef28 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -352,9 +352,7 @@
 
 * Upload the html files manually via web browser to the
 link:https://console.developers.google.com/project/164060093628/storage/gerrit-documentation/[
-gerrit-documentation] storage bucket. The `gerrit-documentation`
-storage bucket is accessible via the
-link:https://cloud.google.com/console[Google Developers Console].
+gerrit-documentation] storage bucket.
 
 [[update-links]]
 ==== Update Google Code project links
diff --git a/Documentation/intro-project-owner.txt b/Documentation/intro-project-owner.txt
index 461aa91..eb3c4fb 100644
--- a/Documentation/intro-project-owner.txt
+++ b/Documentation/intro-project-owner.txt
@@ -238,7 +238,7 @@
 === Submit Type
 
 An important decision for a project is the choice of the submit type
-and the content merge setting (aka `Automatically resolve conflicts`).
+and the content merge setting (see the `Allow content merges` option).
 The link:project-configuration.html#submit_type[submit type] is the method
 Gerrit uses to submit a change to the project. The submit type defines
 what Gerrit should do on submit of a change if the destination branch
@@ -765,6 +765,14 @@
 . link:#import-history[import the history of the old project]
 . link:#project-deletion[delete the old project]
 
+Please note that a drawback of this workaround is that the whole review
+history (changes, review comments) is lost.
+
+Alternatively, you can use the
+link:https://gerrit.googlesource.com/plugins/importer/[importer] plugin
+to copy the project _including the review history_, and then
+link:#project-deletion[delete the old project].
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/project-configuration.txt b/Documentation/project-configuration.txt
index 94af04f..e8f870f 100644
--- a/Documentation/project-configuration.txt
+++ b/Documentation/project-configuration.txt
@@ -114,7 +114,7 @@
 the same file has also been changed on the other side of the merge.
 
 [[content_merge]]
-If `Automatically resolve conflicts` is enabled, Gerrit will try
+If `Allow content merges` is enabled, Gerrit will try
 to do a content merge when a path conflict occurs.
 
 [[project-state]]
diff --git a/Documentation/user-inline-edit.txt b/Documentation/user-inline-edit.txt
index 10f97fe..5ad6b39 100644
--- a/Documentation/user-inline-edit.txt
+++ b/Documentation/user-inline-edit.txt
@@ -45,7 +45,9 @@
 the 'Add...' button at the top of the file list.
 
 Files can be removed from the change, or restored, by clicking the icon to the
-left of the file name.
+left of the file name. Reverting a file in the change is also supported and is
+achieved in two steps: remove file from the change and restore the file in the
+change.
 
 To switch from edit mode back to review mode, click the 'Done Editing' button.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.10.3.1.txt b/ReleaseNotes/ReleaseNotes-2.10.3.1.txt
new file mode 100644
index 0000000..2b90a6d
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.10.3.1.txt
@@ -0,0 +1,11 @@
+Release notes for Gerrit 2.10.3.1
+=================================
+
+There are no schema changes from link:ReleaseNotes-2.10.3.html[2.10.3].
+
+Download:
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.1.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.1.war]
+
+The 2.10.3 release packaged wrong version of the core plugins due to a bug
+in our buck build scripts. This version fixes this issue.
diff --git a/ReleaseNotes/ReleaseNotes-2.10.3.txt b/ReleaseNotes/ReleaseNotes-2.10.3.txt
new file mode 100644
index 0000000..578a1ae
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.10.3.txt
@@ -0,0 +1,124 @@
+Release notes for Gerrit 2.10.3
+===============================
+
+Download:
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.war]
+
+Important Notes
+---------------
+
+*WARNING:* There are no schema changes from
+link:ReleaseNotes-2.10.2.html[2.10.2], but Bouncycastle was upgraded to 1.51.
+It is therefore important to upgrade the site with the `init` program, rather
+than only copying the .war file over the existing one.
+
+*WARNING:* When upgrading from version 2.8.4 or older with a site that uses
+Bouncy Castle Crypto, new versions of the libraries will be downloaded. The old
+libraries should be manually removed from site's `lib` folder to prevent the
+startup failure described in
+link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
+
+It is recommended to run the `init` program in interactive mode. Warnings will
+be suppressed in batch mode.
+
+----
+  java -jar gerrit.war init -d site_path
+----
+
+New Features
+------------
+
+* Support hybrid OpenID and OAuth2 authentication
++
+OpenID auth scheme is aware of optional OAuth2 plugin-based authentication.
+This feature is considered to be experimental and hasn't reached full feature set yet.
+Particularly, linking of user identities accross protocol boundaries and even from
+one OAuth2 identity to another OAuth2 identity wasn't implemented yet.
+
+Configuration
+~~~~~~~~~~~~~
+
+* Allow to configure
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10.3/config-gerrit.html#sshd.rekeyBytesLimit[
+SSHD rekey parameters].
+
+SSH
+---
+
+* Update SSHD to 0.14.0.
++
+This fixes link:https://issues.apache.org/jira/browse/SSHD-348[SSHD-348] which
+was causing ssh threads allocated to stream-events clients to get stuck.
++
+Also update SSHD Mina to 2.0.8 and Bouncycastle to 1.51.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2797[Issue 2797]:
+Add support for ECDSA based public key authentication.
+
+Bug Fixes
+---------
+
+* Prevent wrong content type for CSS files.
++
+The mime-util library contains two content type mappings for .css files:
+`application/x-pointplus` and `text/css`.  Unfortunately, using the wrong one
+will result in most browsers discarding the file as a CSS file.  Ensure we only
+use the correct type for CSS files.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3289[Issue 3289]:
+Prevent NullPointerException in Gitweb servlet.
+
+Replication plugin
+~~~~~~~~~~~~~~~~~~
+
+* Set connection timeout to 120 seconds for SSH remote operations.
++
+The creation of a missing Git, before starting replication, is a blocking
+operation. By setting a timeout, we ensure the operation does not get stuck
+forever, essentially blocking all future remote git creation operations.
+
+OAuth extension point
+~~~~~~~~~~~~~~~~~~~~~
+
+* Respect servlet context path in URL for login token
++
+On sites with non empty context path, first redirect was broken and ended up
+with 404 Not found.
+
+* Invalidate OAuth session after web_sessions cache expiration
++
+After web session cache expiration there is no way to re-sign-in into Gerrit.
+
+Daemon
+~~~~~~
+
+* Print proper names for tasks in output of `show-queue` command.
++
+Some tasks were not displayed with the proper name.
+
+Web UI
+~~~~~~
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3044[Issue 3044]:
+Remove stripping `#` in login redirect.
+
+SSH
+~~~
+
+* Prevent double authentication for the same public key.
+
+
+Performance
+-----------
+
+* Improved performance when creating a new branch on a repository with a large
+number of changes.
+
+
+Upgrades
+--------
+
+* Update Bouncycastle to 1.51.
+
+* Update SSHD to 0.14.0.
diff --git a/ReleaseNotes/ReleaseNotes-2.11.txt b/ReleaseNotes/ReleaseNotes-2.11.txt
index 49b1317..3bc6c96 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.txt
@@ -8,8 +8,9 @@
 https://gerrit-releases.storage.googleapis.com/gerrit-2.11.war]
 
 Gerrit 2.11 includes the bug fixes done with
-link:ReleaseNotes-2.10.1.html[Gerrit 2.10.1] and
-link:ReleaseNotes-2.10.2.html[Gerrit 2.10.2].
+link:ReleaseNotes-2.10.1.html[Gerrit 2.10.1],
+link:ReleaseNotes-2.10.2.html[Gerrit 2.10.2] and
+link:ReleaseNotes-2.10.3.html[Gerrit 2.10.3].
 These bug fixes are *not* listed in these release notes.
 
 
@@ -20,9 +21,38 @@
 *WARNING:* This release contains schema changes.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
+----
+
+Gerrit 2.11 requires a secondary index, which can be created offline
+by running the `reindex` program:
+
+----
   java -jar gerrit.war reindex -d site_path
 ----
 
+If the site that is upgraded already has a secondary index, the
+secondary index can be upgraded online. This is important for large
+sites since running the `reindex` program can take a long time and
+contributes significantly to the downtime that is required for the
+upgrade.
+
+Gerrit 2.11 supports online reindexing only from the index version `11`
+which is the index version of Gerrit 2.10. This means if you come from
+an older release it makes sense to first upgrade to 2.10 and then do
+the upgrade to 2.11 so that you can profit from online reindexing.
+
+In case you are upgrading from 2.10 it is *important* to check *before*
+the upgrade to 2.11 that the index version of your Gerrit 2.10 site is
+`11`. You can check the index version in
+`$site_path/index/gerrit_index.config`. Your Gerrit 2.10 site may run
+with an older index version (e.g. if online reindexing to index version
+`11` is still running or if online reindexing to version `11` has
+failed). In this case you first need to successfully migrate your index
+version of your Gerrit 2.10 site to `11` and only then start with the
+2.11 upgrade. If you start the 2.11 upgrade when the schema version of
+your Gerrit 2.10 site is older than `11`, online reindexing is no longer
+possible and you need to reindex offline by using the `reindex` program.
+
 *WARNING:* Upgrading to 2.11.x requires the server be first upgraded to 2.8 (or
 2.9) and then to 2.11.x. If you are upgrading from 2.8.x or later, you may ignore
 this warning and upgrade directly to 2.11.x.
@@ -118,8 +148,6 @@
 
 * Show the parent commit's subject as a tooltip.
 
-* Decorate abandoned changes in the 'Related Changes' list with a dark red dot.
-
 * link:http://code.google.com/p/gerrit/issues/detail?id=2541[Issue 2541],
 link:http://code.google.com/p/gerrit/issues/detail?id=2974[Issue 2974]:
 Allow the 'Reply' button's
@@ -367,10 +395,6 @@
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#container.daemonOpt[
 options to pass to the daemon].
 
-* Allow to configure
-link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#sshd.rekeyBytesLimit[
-SSHD rekey parameters].
-
 Daemon
 ~~~~~~
 
@@ -395,9 +419,6 @@
 SSH
 ~~~
 
-* link:https://code.google.com/p/gerrit/issues/detail?id=2797[Issue 2797]:
-Add support for ECDSA based public key authentication.
-
 * Add new commands
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-logging-ls-level.html[
 `logging ls-level`] and
@@ -542,16 +563,6 @@
 Due to link:https://github.com/google/guice/issues/745[Guice issue 745], cloning
 of a repository with a space in its name was impossible.
 
-* Print proper names for tasks in output of `show-queue` command.
-+
-Some tasks were not displayed with the proper name.
-
-
-SSH
-~~~
-
-* Prevent double authentication for the same public key.
-
 
 Secondary Index / Search
 ~~~~~~~~~~~~~~~~~~~~~~~~
@@ -600,19 +611,15 @@
 Web UI
 ~~~~~~
 
-* link:http://code.google.com/p/gerrit/issues/detail?id=3044[Issue 3044]:
-Remove stripping `#` in login redirect.
+Change List
+^^^^^^^^^^^
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3304[Issue 3304]:
+Always show a tooltip on the label column entries.
 
 Change Screen
 ^^^^^^^^^^^^^
 
-* link:http://code.google.com/p/gerrit/issues/detail?id=2894[Issue 2894]:
-Link to change screen for merged or abandoned changes in the 'Related Changes'
-list.
-+
-For changes in the 'Related Changes' tab that are closed the link was
-bringing the user to GitWeb, and not as expected to the change screen.
-
 * link:http://code.google.com/p/gerrit/issues/detail?id=3147[Issue 3147]:
 Allow to disable muting of common path prefixes in the file list.
 +
@@ -843,8 +850,6 @@
 
 * Update ASM to 5.0.3.
 
-* Update Bouncycastle to 1.51.
-
 * Update CodeMirror to 4.10.0-6-gd0a2dda.
 
 * Update Guava to 18.0.
@@ -866,5 +871,3 @@
 * Update Parboiled to 1.1.7.
 
 * Update Pegdown to 1.4.2.
-
-* Update SSHD to 0.14.0.
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 1c4c2be..99db8fb 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -9,6 +9,8 @@
 [[2_10]]
 Version 2.10.x
 --------------
+* link:ReleaseNotes-2.10.3.1.html[2.10.3.1]
+* link:ReleaseNotes-2.10.3.html[2.10.3]
 * link:ReleaseNotes-2.10.2.html[2.10.2]
 * link:ReleaseNotes-2.10.1.html[2.10.1]
 * link:ReleaseNotes-2.10.html[2.10]
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index 34f44e2..8755d29 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.index.ChangeSchemas;
 import com.google.gerrit.server.ssh.NoSshModule;
 import com.google.gerrit.server.util.SocketUtil;
+import com.google.gerrit.testutil.FakeEmailSender;
 import com.google.gerrit.testutil.TempFileUtil;
 import com.google.inject.Injector;
 import com.google.inject.Key;
@@ -115,6 +116,7 @@
         }
       }
     });
+    daemon.setEmailModuleForTesting(new FakeEmailSender.Module());
 
     final File site;
     ExecutorService daemonService = null;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
index 73dc1f0..6c7dbfe 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
@@ -19,6 +19,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.Reader;
+import java.nio.charset.StandardCharsets;
 
 public class RestResponse extends HttpResponse {
 
@@ -29,7 +30,9 @@
   @Override
   public Reader getReader() throws IllegalStateException, IOException {
     if (reader == null && response.getEntity() != null) {
-      reader = new InputStreamReader(response.getEntity().getContent());
+      reader =
+          new InputStreamReader(response.getEntity().getContent(),
+              StandardCharsets.UTF_8);
       reader.skip(JSON_MAGIC.length);
     }
     return reader;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index 016a8be..4007e32 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -72,6 +72,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Date;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
@@ -658,21 +659,21 @@
   private String newChange(PersonIdent ident) throws Exception {
     PushOneCommit push =
         pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME,
-            new String(CONTENT_OLD));
+            new String(CONTENT_OLD, StandardCharsets.UTF_8));
     return push.to("refs/for/master").getChangeId();
   }
 
   private String amendChange(PersonIdent ident, String changeId) throws Exception {
     PushOneCommit push =
         pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME2,
-            new String(CONTENT_NEW2), changeId);
+            new String(CONTENT_NEW2, StandardCharsets.UTF_8), changeId);
     return push.to("refs/for/master").getChangeId();
   }
 
   private String newChange2(PersonIdent ident) throws Exception {
     PushOneCommit push =
         pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME,
-            new String(CONTENT_OLD));
+            new String(CONTENT_OLD, StandardCharsets.UTF_8));
     return push.rm("refs/for/master").getChangeId();
   }
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
index f36c447..33a3685 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
@@ -5,6 +5,7 @@
     'DraftChangeBlockedIT.java',
     'ForcePushIT.java',
     'SubmitOnPushIT.java',
+    'SubmoduleSubscriptionsIT.java',
     'VisibleRefFilterIT.java',
   ],
   labels = ['git'],
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
new file mode 100644
index 0000000..6d677d6
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
@@ -0,0 +1,223 @@
+// 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.acceptance.git;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.reviewdb.client.Project;
+
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.RefSpec;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class SubmoduleSubscriptionsIT extends AbstractDaemonTest {
+
+  @Test
+  public void testSubscriptionToEmptyRepo() throws Exception {
+    TestRepository<?> superRepo = createProjectWithPush("super-project");
+    TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+
+    createSubscription(superRepo, "master", "subscribed-to-project", "master");
+    ObjectId subHEAD = pushChangeTo(subRepo, "master");
+    expectToHaveSubmoduleState(superRepo, "master",
+        "subscribed-to-project", subHEAD);
+  }
+
+  @Test
+  public void testSubscriptionToExistingRepo() throws Exception {
+    TestRepository<?> superRepo = createProjectWithPush("super-project");
+    TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+
+    pushChangeTo(subRepo, "master");
+    createSubscription(superRepo, "master", "subscribed-to-project", "master");
+    ObjectId subHEAD = pushChangeTo(subRepo, "master");
+    expectToHaveSubmoduleState(superRepo, "master",
+        "subscribed-to-project", subHEAD);
+  }
+
+  @Test
+  public void testSubscriptionUnsubscribe() throws Exception {
+    TestRepository<?> superRepo = createProjectWithPush("super-project");
+    TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+
+    pushChangeTo(subRepo, "master");
+    createSubscription(superRepo, "master", "subscribed-to-project", "master");
+
+    pushChangeTo(subRepo, "master");
+    ObjectId subHEADbeforeUnsubscribing = pushChangeTo(subRepo, "master");
+
+    deleteAllSubscriptions(superRepo, "master");
+    expectToHaveSubmoduleState(superRepo, "master",
+        "subscribed-to-project", subHEADbeforeUnsubscribing);
+
+    pushChangeTo(superRepo, "master", "commit after unsubscribe");
+    pushChangeTo(subRepo, "master", "commit after unsubscribe");
+    expectToHaveSubmoduleState(superRepo, "master",
+        "subscribed-to-project", subHEADbeforeUnsubscribing);
+  }
+
+  @Test
+  public void testSubscriptionToDifferentBranches() throws Exception {
+    TestRepository<?> superRepo = createProjectWithPush("super-project");
+    TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+
+    createSubscription(superRepo, "master", "subscribed-to-project", "foo");
+    ObjectId subFoo = pushChangeTo(subRepo, "foo");
+    pushChangeTo(subRepo, "master");
+
+    expectToHaveSubmoduleState(superRepo, "master",
+        "subscribed-to-project", subFoo);
+  }
+
+  @Test
+  public void testCircularSubscriptionIsDetected() throws Exception {
+    TestRepository<?> superRepo = createProjectWithPush("super-project");
+    TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+
+    pushChangeTo(subRepo, "master");
+    createSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubscription(subRepo, "master", "super-project", "master");
+
+    ObjectId subHEAD = pushChangeTo(subRepo, "master");
+    pushChangeTo(superRepo, "master");
+
+    expectToHaveSubmoduleState(superRepo, "master",
+        "subscribed-to-project", subHEAD);
+
+    assertThat(hasSubmodule(subRepo, "master", "super-project")).isFalse();
+  }
+
+  private TestRepository<?> createProjectWithPush(String name)
+      throws Exception {
+    Project.NameKey project = createProject(name);
+    grant(Permission.PUSH, project, "refs/heads/*");
+    grant(Permission.SUBMIT, project, "refs/for/refs/heads/*");
+    return cloneProject(project);
+  }
+
+  private static AtomicInteger contentCounter = new AtomicInteger(0);
+
+  private ObjectId pushChangeTo(TestRepository<?> repo, String branch, String message)
+      throws Exception {
+
+    ObjectId ret = repo.branch("HEAD").commit().insertChangeId()
+      .message(message)
+      .add("a.txt", "a contents: " + contentCounter.addAndGet(1))
+      .create();
+
+    repo.git().push().setRemote("origin").setRefSpecs(
+        new RefSpec("HEAD:refs/heads/" + branch)).call();
+
+    return ret;
+  }
+
+  private ObjectId pushChangeTo(TestRepository<?> repo, String branch)
+      throws Exception {
+    return pushChangeTo(repo, branch, "some change");
+  }
+
+  private void createSubscription(
+      TestRepository<?> repo, String branch, String subscribeToRepo,
+      String subscribeToBranch) throws Exception {
+    subscribeToRepo = name(subscribeToRepo);
+
+    // The submodule subscription module checks for gerrit.canonicalWebUrl to
+    // detect if it's configured for automatic updates. It doesn't matter if
+    // it serves from that URL.
+    String url = cfg.getString("gerrit", null, "canonicalWebUrl") + "/"
+        + subscribeToRepo;
+    String content = buildSubmoduleSection(subscribeToRepo, subscribeToRepo,
+        url, subscribeToBranch);
+    repo.branch("HEAD").commit().insertChangeId()
+      .message("subject: adding new subscription")
+      .add(".gitmodules", content)
+      .create();
+
+    repo.git().push().setRemote("origin").setRefSpecs(
+        new RefSpec("HEAD:refs/heads/" + branch)).call();
+  }
+
+  private void deleteAllSubscriptions(TestRepository<?> repo, String branch)
+      throws Exception {
+    repo.git().fetch().setRemote("origin").call();
+    repo.reset("refs/remotes/origin/" + branch);
+
+    ObjectId expectedId = repo.branch("HEAD").commit().insertChangeId()
+      .message("delete contents in .gitmodules")
+      .add(".gitmodules", "") // Just remove the contents of the file!
+      .create();
+    repo.git().push().setRemote("origin").setRefSpecs(
+      new RefSpec("HEAD:refs/heads/" + branch)).call();
+
+    ObjectId actualId = repo.git().fetch().setRemote("origin").call()
+      .getAdvertisedRef("refs/heads/master").getObjectId();
+    assertThat(actualId).isEqualTo(expectedId);
+  }
+
+  private void expectToHaveSubmoduleState(TestRepository<?> repo, String branch,
+      String submodule, ObjectId expectedId) throws Exception {
+
+    submodule = name(submodule);
+    ObjectId commitId = repo.git().fetch().setRemote("origin").call()
+        .getAdvertisedRef("refs/heads/" + branch).getObjectId();
+
+    RevWalk rw = repo.getRevWalk();
+    RevCommit c = rw.parseCommit(commitId);
+    rw.parseBody(c.getTree());
+
+    RevTree tree = c.getTree();
+    RevObject actualId = repo.get(tree, submodule);
+
+    assertThat(actualId).isEqualTo(expectedId);
+  }
+
+  private boolean hasSubmodule(TestRepository<?> repo, String branch,
+      String submodule) throws Exception {
+
+    ObjectId commitId = repo.git().fetch().setRemote("origin").call()
+        .getAdvertisedRef("refs/heads/" + branch).getObjectId();
+
+    RevWalk rw = repo.getRevWalk();
+    RevCommit c = rw.parseCommit(commitId);
+    rw.parseBody(c.getTree());
+
+    RevTree tree = c.getTree();
+    try {
+      repo.get(tree, submodule);
+      return true;
+    } catch (AssertionError e) {
+      return false;
+    }
+  }
+
+  private static String buildSubmoduleSection(String name,
+      String path, String url, String branch) {
+    Config cfg = new Config();
+    cfg.setString("submodule", name, "path", path);
+    cfg.setString("submodule", name, "url", url);
+    cfg.setString("submodule", name, "branch", branch);
+    return cfg.toText().toString();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
new file mode 100644
index 0000000..bab8e33
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -0,0 +1,78 @@
+// 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.acceptance.server.project;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
+import com.google.gerrit.server.git.NotifyConfig;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.testutil.FakeEmailSender;
+import com.google.gerrit.testutil.FakeEmailSender.Message;
+import com.google.inject.Inject;
+
+import org.junit.Test;
+
+import java.util.EnumSet;
+import java.util.List;
+
+@NoHttpd
+public class ProjectWatchIT extends AbstractDaemonTest {
+  @Inject
+  private FakeEmailSender sender;
+
+  @Test
+  public void newPatchSetsNotifyConfig() throws Exception {
+    Address addr = new Address("Watcher", "watcher@example.com");
+    NotifyConfig nc = new NotifyConfig();
+    nc.addEmail(addr);
+    nc.setName("new-patch-set");
+    nc.setHeader(NotifyConfig.Header.CC);
+    nc.setTypes(EnumSet.of(NotifyType.NEW_PATCHSETS));
+    nc.setFilter("message:sekret");
+
+    ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+    cfg.putNotifyConfig("watch", nc);
+    saveProjectConfig(project, cfg);
+
+    PushOneCommit.Result r = pushFactory.create(db, admin.getIdent(), testRepo,
+          "original subject", "a", "a1")
+        .to("refs/for/master");
+    r.assertOkStatus();
+
+    r = pushFactory.create(db, admin.getIdent(), testRepo,
+          "super sekret subject", "a", "a2", r.getChangeId())
+        .to("refs/for/master");
+    r.assertOkStatus();
+
+    r = pushFactory.create(db, admin.getIdent(), testRepo,
+          "back to original subject", "a", "a3")
+        .to("refs/for/master");
+    r.assertOkStatus();
+
+    List<Message> messages = sender.getMessages();
+    assertThat(messages).hasSize(1);
+    Message m = messages.get(0);
+    assertThat(m.rcpt()).containsExactly(addr);
+    assertThat(m.body()).contains("Change subject: super sekret subject\n");
+    assertThat(m.body()).contains("Gerrit-PatchSet: 2\n");
+  }
+
+  // TODO(anybody reading this): More tests.
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index 1c57d38..6867bab 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -210,6 +210,7 @@
     e.listenTo(legacycidInChangeTable);
     e.listenTo(muteCommonPathPrefixes);
     e.listenTo(diffView);
+    e.listenTo(reviewCategoryStrategy);
   }
 
   @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 4446354..affbe61 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -20,7 +20,7 @@
 buttonBrowseProjects = Browse
 projects = All projects
 projectRepoBrowser = Repository Browser
-useContentMerge = Automatically resolve conflicts
+useContentMerge = Allow content merges
 useContributorAgreements = Require a valid contributor agreement to upload
 useSignedOffBy = Require <code>Signed-off-by</code> in commit message
 createNewChangeForAllNotInTarget = Create a new change for every commit not in the target branch
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
index 37b90c4..532f8f7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
@@ -130,7 +130,10 @@
 
     a2b(actions, "cherrypick", cherrypick);
     a2b(actions, "rebase", rebase);
-
+    if (rebase.isVisible()) {
+      // it is the rebase button in RebaseDialog that the server wants to disable
+      rebase.setEnabled(true);
+    }
     RevisionInfo revInfo = changeInfo.revision(revision);
     for (String id : filterNonCore(actions)) {
       add(new ActionButton(changeInfo, revInfo, actions.get(id)));
@@ -176,7 +179,16 @@
 
   @UiHandler("rebase")
   void onRebase(@SuppressWarnings("unused") ClickEvent e) {
-    RebaseAction.call(rebase, project, changeInfo.branch(), changeId, revision);
+    boolean enabled = true;
+    RevisionInfo revInfo = changeInfo.revision(revision);
+    if (revInfo.has_actions()) {
+        NativeMap<ActionInfo> actions = revInfo.actions();
+        if (actions.containsKey("rebase")) {
+          enabled = actions.get("rebase").enabled();
+        }
+    }
+    RebaseAction.call(rebase, project, changeInfo.branch(), changeId, revision,
+        enabled);
   }
 
   @UiHandler("submit")
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
index 5c44a14..3a3ffe2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
@@ -28,16 +28,18 @@
   private final RevisionInfo revision;
   private final ChangeScreen.Style style;
   private final Widget addButton;
+  private final FileTable files;
 
   private AddFileBox addBox;
   private PopupPanel popup;
 
   AddFileAction(Change.Id changeId, RevisionInfo revision,
-      ChangeScreen.Style style, Widget addButton) {
+      ChangeScreen.Style style, Widget addButton, FileTable files) {
     this.changeId = changeId;
     this.revision = revision;
     this.style = style;
     this.addButton = addButton;
+    this.files = files;
   }
 
   public void onEdit() {
@@ -46,8 +48,9 @@
       return;
     }
 
+    files.unregisterKeys();
     if (addBox == null) {
-      addBox = new AddFileBox(changeId, revision);
+      addBox = new AddFileBox(changeId, revision, files);
     }
     addBox.clearPath();
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java
index 82fe806..09de2c8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java
@@ -41,6 +41,7 @@
 
   private final Change.Id changeId;
   private final RevisionInfo revision;
+  private final FileTable fileTable;
 
   @UiField Button open;
   @UiField Button cancel;
@@ -48,9 +49,10 @@
   @UiField(provided = true)
   RemoteSuggestBox path;
 
-  AddFileBox(Change.Id changeId, RevisionInfo revision) {
+  AddFileBox(Change.Id changeId, RevisionInfo revision, FileTable files) {
     this.changeId = changeId;
     this.revision = revision;
+    this.fileTable = files;
 
     path = new RemoteSuggestBox(new PathSuggestOracle(changeId, revision));
     path.addSelectionHandler(new SelectionHandler<String>() {
@@ -63,6 +65,7 @@
       @Override
       public void onClose(CloseEvent<RemoteSuggestBox> event) {
         hide();
+        fileTable.registerKeys();
       }
     });
 
@@ -92,6 +95,7 @@
   @UiHandler("cancel")
   void onCancel(@SuppressWarnings("unused") ClickEvent e) {
     hide();
+    fileTable.registerKeys();
   }
 
   private void hide() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index 4568882..d153771 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -435,7 +435,7 @@
         reviewMode.setVisible(!editMode.isVisible());
         addFileAction = new AddFileAction(
             changeId, info.revision(revision),
-            style, addFile);
+            style, addFile, files);
         deleteFileAction = new DeleteFileAction(
             changeId, info.revision(revision),
             style, addFile);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
index 4bdd17f..8cf3a0c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
@@ -20,10 +20,12 @@
 import com.google.gerrit.client.GitwebLink;
 import com.google.gerrit.client.WebLinkInfo;
 import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
 import com.google.gerrit.client.changes.ChangeInfo.GitPerson;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.InlineHyperlink;
@@ -46,6 +48,7 @@
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
@@ -77,6 +80,7 @@
   @UiField HTML text;
   @UiField ScrollPanel scroll;
   @UiField Button more;
+  @UiField Element parentNotCurrentText;
   private boolean expanded;
 
   CommitBox() {
@@ -121,7 +125,19 @@
     if (revInfo.commit().parents().length() > 1) {
       mergeCommit.setVisible(true);
     }
+
     setParents(change.project(), revInfo.commit().parents());
+
+    // display the orange ball if parent has moved on (not current)
+    boolean parentNotCurrent = false;
+    if (revInfo.has_actions()) {
+      NativeMap<ActionInfo> actions = revInfo.actions();
+      if (actions.containsKey("rebase")) {
+        parentNotCurrent = actions.get("rebase").enabled();
+      }
+    }
+    UIObject.setVisible(parentNotCurrentText, parentNotCurrent);
+    parentNotCurrentText.setInnerText(parentNotCurrent ? "\u25CF" : "");
   }
 
   private void setWebLinks(ChangeInfo change, String revision,
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
index 5b7fb89..93312fa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
@@ -68,7 +68,7 @@
       padding: 0;
       width: 560px;
     }
-    .header th { width: 70px; }
+    .header th { width: 72px; }
     .header td { white-space: nowrap; }
     .date { width: 132px; }
 
@@ -106,6 +106,16 @@
       height: 16px !important;
       vertical-align: bottom;
     }
+
+    .parent {
+      margin-right: 3px;
+      float: left;
+    }
+    .parentNotCurrent {
+      color: #FFA62F;   <!-- orange -->
+      font-weight: bold;
+    }
+
   </ui:style>
   <g:HTMLPanel>
     <g:ScrollPanel styleName='{style.scroll}' ui:field='scroll'>
@@ -163,7 +173,15 @@
         </td>
       </tr>
       <tr ui:field='firstParent' style='display: none'>
-        <th><ui:msg>Parent(s)</ui:msg></th>
+        <th>
+          <div class='{style.parent}'>
+            <ui:msg>Parent(s)</ui:msg>
+          </div>
+          <div ui:field='parentNotCurrentText'
+              title='Not current - rebase possible'
+              class='{style.parentNotCurrent}'
+              style='display: none' aria-hidden='true'/>
+        </th>
         <td>
           <g:FlowPanel ui:field='parentCommits'/>
         </td>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
index fc62c46..2947be8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -224,6 +224,14 @@
     }
   }
 
+  void unregisterKeys() {
+    register = false;
+
+    if (table != null) {
+      table.setRegisterKeys(false);
+    }
+  }
+
   void registerKeys() {
     register = true;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java
index ffe9627..790198b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RebaseAction.java
@@ -27,10 +27,10 @@
 
 class RebaseAction {
   static void call(final Button b, final String project, final String branch,
-      final Change.Id id, final String revision) {
+      final Change.Id id, final String revision, final boolean enabled) {
     b.setEnabled(false);
 
-    new RebaseDialog(project, branch, id) {
+    new RebaseDialog(project, branch, id, enabled) {
       @Override
       public void onSend() {
         ChangeApi.rebase(id.get(), revision, getBase(), new GerritCallback<ChangeInfo>() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index 5c49661..efaa7c2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -167,6 +167,7 @@
 
   String buttonRebaseChangeSend();
   String rebaseConfirmMessage();
+  String rebaseNotPossibleMessage();
   String rebasePlaceholderMessage();
   String rebaseTitle();
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 200f3e3..40c6d24 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -153,6 +153,7 @@
 
 buttonRebaseChangeSend = Rebase
 rebaseConfirmMessage = Change parent revision
+rebaseNotPossibleMessage = Change is already up to date
 rebasePlaceholderMessage = (subject, change number, or leave empty)
 rebaseTitle = Code Review - Rebase Change
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 9d5fc6b..a4f2e3f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -149,10 +149,11 @@
    *         if no label is missing, or if more than one label is missing.
    */
   public final int getMissingLabelIndex() {
-    int i = 0;
+    int i = -1;
     int ret = -1;
     List<LabelInfo> labels = Natives.asList(all_labels().values());
     for (LabelInfo label : labels) {
+      i++;
       if (!permitted_labels().containsKey(label.name())) {
         continue;
       }
@@ -183,7 +184,6 @@
           set_submittable(false);
           return -1;
       }
-      i++;
     }
     set_submittable(ret == -1);
     return ret;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
index 50ec3dd..8b71448 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
@@ -253,9 +253,6 @@
     }
     col++;
 
-    boolean displayInfo = Gerrit.isSignedIn() && Gerrit.getUserAccount()
-        .getGeneralPreferences().isShowInfoInReviewCategory();
-
     for (int idx = 0; idx < labelNames.size(); idx++, col++) {
       String name = labelNames.get(idx);
 
@@ -276,7 +273,7 @@
         user = label.rejected().name();
         info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
             label.rejected());
-        if (displayInfo && info != null) {
+        if (info != null) {
           FlowPanel panel = new FlowPanel();
           panel.add(new Image(Gerrit.RESOURCES.redNot()));
           panel.add(new InlineLabel(info));
@@ -288,7 +285,7 @@
         user = label.approved().name();
         info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
             label.approved());
-        if (displayInfo && info != null) {
+        if (info != null) {
           FlowPanel panel = new FlowPanel();
           panel.add(new Image(Gerrit.RESOURCES.greenCheck()));
           panel.add(new InlineLabel(info));
@@ -301,7 +298,7 @@
         info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
             label.disliked());
         String vstr = String.valueOf(label._value());
-        if (displayInfo && info != null) {
+        if (info != null) {
           vstr = vstr + " " + info;
         }
         fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore());
@@ -311,7 +308,7 @@
         info = getReviewCategoryDisplayInfo(reviewCategoryStrategy,
             label.recommended());
         String vstr = "+" + label._value();
-        if (displayInfo && info != null) {
+        if (info != null) {
           vstr = vstr + " " + info;
         }
         fmt.addStyleName(row, col, Gerrit.RESOURCES.css().posscore());
@@ -322,8 +319,7 @@
       }
       fmt.addStyleName(row, col, Gerrit.RESOURCES.css().singleLine());
 
-      if ((!displayInfo || reviewCategoryStrategy == ReviewCategoryStrategy.ABBREV)
-          && user != null) {
+      if (user != null) {
         // Some web browsers ignore the embedded newline; some like it;
         // so we include a space before the newline to accommodate both.
         fmt.getElement(row, col).setTitle(name + " \nby " + user);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
index 0e19e46..dd36657 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
@@ -347,7 +347,7 @@
   private void initEditor(HttpResponse<NativeString> file) {
     ModeInfo mode = null;
     String content = "";
-    if (file != null) {
+    if (file != null && file.getResult() != null) {
       content = file.getResult().asString();
       if (prefs.syntaxHighlighting()) {
         mode = ModeInfo.findMode(file.getContentType(), path);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java
index 6b96604..5f47d98 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/RebaseDialog.java
@@ -36,10 +36,12 @@
   private final SuggestBox base;
   private final CheckBox cb;
   private List<ChangeInfo> changes;
+  private final boolean sendEnabled;
 
   public RebaseDialog(final String project, final String branch,
-      final Change.Id changeId) {
+      final Change.Id changeId, final boolean sendEnabled) {
     super(Util.C.rebaseTitle(), null);
+    this.sendEnabled = sendEnabled;
     sendButton.setText(Util.C.buttonRebaseChangeSend());
 
     // create the suggestion box
@@ -63,7 +65,6 @@
         done.onSuggestionsReady(request, new Response(suggestions));
       }
     });
-    base.setEnabled(false);
     base.getElement().setAttribute("placeholder",
         Util.C.rebasePlaceholderMessage());
     base.setStyleName(Gerrit.RESOURCES.css().rebaseSuggestBox());
@@ -81,13 +82,11 @@
                 @Override
                 public void onSuccess(ChangeList result) {
                   changes = Natives.asList(result);
-                  base.setEnabled(true);
-                  base.setFocus(true);
+                  updateControls(true);
                 }
               });
         } else {
-          base.setEnabled(false);
-          sendButton.setFocus(true);
+          updateControls(false);
         }
       }
     });
@@ -102,7 +101,26 @@
   public void center() {
     super.center();
     GlobalKey.dialog(this);
-    sendButton.setFocus(true);
+    updateControls(false);
+  }
+
+  private void updateControls(boolean changeParentEnabled) {
+    if (changeParentEnabled) {
+      sendButton.setTitle(null);
+      sendButton.setEnabled(true);
+      base.setEnabled(true);
+      base.setFocus(true);
+    } else {
+      base.setEnabled(false);
+      sendButton.setEnabled(sendEnabled);
+      if (sendEnabled) {
+        sendButton.setTitle(null);
+        sendButton.setFocus(true);
+      } else {
+        sendButton.setTitle(Util.C.rebaseNotPossibleMessage());
+        cancelButton.setFocus(true);
+      }
+    }
   }
 
   public String getBase() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index 84a048a..86debdd 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -71,7 +71,8 @@
     }
     serve("/cat/*").with(CatServlet.class);
 
-    if (authConfig.getAuthType() != AuthType.OAUTH) {
+    if (authConfig.getAuthType() != AuthType.OAUTH &&
+        authConfig.getAuthType() != AuthType.OPENID) {
       serve("/logout").with(HttpLogoutServlet.class);
       serve("/signout").with(HttpLogoutServlet.class);
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index fd8d3b4..15150cf 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -363,15 +363,18 @@
     }
 
     final Map<String, String> params = getParameters(req);
-    if (deniedActions.contains(params.get("a"))) {
-      rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
-      return;
-    }
+    String a = params.get("a");
+    if (a != null) {
+      if (deniedActions.contains(a)) {
+        rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
+        return;
+      }
 
-    if (params.get("a").equals(PROJECT_LIST_ACTION)) {
-      rsp.sendRedirect(req.getContextPath() + "/#" + PageLinks.ADMIN_PROJECTS
-          + "?filter=" + Url.encode(params.get("pf") + "/"));
-      return;
+      if (a.equals(PROJECT_LIST_ACTION)) {
+        rsp.sendRedirect(req.getContextPath() + "/#" + PageLinks.ADMIN_PROJECTS
+            + "?filter=" + Url.encode(params.get("pf") + "/"));
+        return;
+      }
     }
 
     String name = params.get("p");
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index fd26837..405a861 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -583,6 +583,9 @@
       if ("application/octet-stream".equals(contentType)
           && entry.getName().endsWith(".js")) {
         contentType = "application/javascript";
+      } else if ("application/x-pointplus".equals(contentType)
+          && entry.getName().endsWith(".css")) {
+        contentType = "text/css";
       }
     }
 
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
index 8ffbbe6..739dffe 100644
--- a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
@@ -22,6 +22,8 @@
 import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
 import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.httpd.CanonicalWebUrl;
 import com.google.gerrit.httpd.WebSession;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountException;
@@ -36,8 +38,6 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 
@@ -53,17 +53,20 @@
   private final String state;
   private final DynamicItem<WebSession> webSession;
   private final AccountManager accountManager;
+  private final CanonicalWebUrl urlProvider;
   private OAuthServiceProvider serviceProvider;
   private OAuthToken token;
   private OAuthUserInfo user;
-  private String redirectUrl;
+  private String redirectToken;
 
   @Inject
   OAuthSession(DynamicItem<WebSession> webSession,
-      AccountManager accountManager) {
+      AccountManager accountManager,
+      CanonicalWebUrl urlProvider) {
     this.state = generateRandomState();
     this.webSession = webSession;
     this.accountManager = accountManager;
+    this.urlProvider = urlProvider;
   }
 
   boolean isLoggedIn() {
@@ -95,7 +98,7 @@
 
       if (isLoggedIn()) {
         log.debug("Login-SUCCESS " + this);
-        authenticateAndRedirect(response);
+        authenticateAndRedirect(request, response);
         return true;
       } else {
         response.sendError(SC_UNAUTHORIZED);
@@ -103,15 +106,22 @@
       }
     } else {
       log.debug("Login-PHASE1 " + this);
-      redirectUrl = request.getRequestURI();
+      redirectToken = request.getRequestURI();
+      // We are here in content of filter.
+      // Due to this Jetty limitation:
+      // https://bz.apache.org/bugzilla/show_bug.cgi?id=28323
+      // we cannot use LoginUrlToken.getToken() method,
+      // because it relies on getPathInfo() and it is always null here.
+      redirectToken = redirectToken.substring(
+          request.getContextPath().length());
       response.sendRedirect(oauth.getAuthorizationUrl() +
           "&state=" + state);
       return false;
     }
   }
 
-  private void authenticateAndRedirect(HttpServletResponse rsp)
-      throws IOException {
+  private void authenticateAndRedirect(HttpServletRequest req,
+      HttpServletResponse rsp) throws IOException {
     com.google.gerrit.server.account.AuthRequest areq =
         new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
     AuthResult arsp;
@@ -164,16 +174,17 @@
     }
 
     webSession.get().login(arsp, true);
-    String suffix = redirectUrl.substring(
+    String suffix = redirectToken.substring(
         OAuthWebFilter.GERRIT_LOGIN.length() + 1);
-    suffix = URLDecoder.decode(suffix, StandardCharsets.UTF_8.name());
-    rsp.sendRedirect(suffix);
+    StringBuilder rdr = new StringBuilder(urlProvider.get(req));
+    rdr.append(Url.decode(suffix));
+    rsp.sendRedirect(rdr.toString());
   }
 
   void logout() {
     token = null;
     user = null;
-    redirectUrl = null;
+    redirectToken = null;
     serviceProvider = null;
   }
 
diff --git a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
index 2965613..4021c57 100644
--- a/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
+++ b/gerrit-oauth/src/main/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
@@ -89,18 +89,22 @@
       FilterChain chain) throws IOException, ServletException {
     HttpServletRequest httpRequest = (HttpServletRequest) request;
     HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
+    OAuthSession oauthSession = oauthSessionProvider.get();
     if (currentUserProvider.get().isIdentifiedUser()) {
       if (httpSession != null) {
         httpSession.invalidate();
       }
       chain.doFilter(request, response);
       return;
+    } else {
+      if (oauthSession.isLoggedIn()) {
+        oauthSession.logout();
+      }
     }
 
     HttpServletResponse httpResponse = (HttpServletResponse) response;
 
     String provider = httpRequest.getParameter("provider");
-    OAuthSession oauthSession = oauthSessionProvider.get();
     OAuthServiceProvider service = ssoProvider == null
         ? oauthSession.getServiceProvider()
         : ssoProvider;
diff --git a/gerrit-openid/BUCK b/gerrit-openid/BUCK
index 8761d34..78abce8 100644
--- a/gerrit-openid/BUCK
+++ b/gerrit-openid/BUCK
@@ -12,6 +12,7 @@
     '//gerrit-server:server',
     '//lib:guava',
     '//lib:gwtorm',
+    '//lib/commons:codec',
     '//lib/guice:guice',
     '//lib/guice:guice-servlet',
     '//lib/jgit:jgit',
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index 435bfa7..bef165b 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -22,11 +22,14 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.auth.openid.OpenIdUrls;
+import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.httpd.HtmlDomUtil;
 import com.google.gerrit.httpd.LoginUrlToken;
 import com.google.gerrit.httpd.template.SiteHeaderFooter;
 import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -61,10 +64,13 @@
 
   private final ImmutableSet<String> suggestProviders;
   private final Provider<String> urlProvider;
+  private final Provider<OAuthSessionOverOpenID> oauthSessionProvider;
   private final OpenIdServiceImpl impl;
   private final int maxRedirectUrlLength;
   private final String ssoUrl;
   private final SiteHeaderFooter header;
+  private final Provider<CurrentUser> currentUserProvider;
+  private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
 
   @Inject
   LoginForm(
@@ -72,13 +78,19 @@
       @GerritServerConfig Config config,
       AuthConfig authConfig,
       OpenIdServiceImpl impl,
-      SiteHeaderFooter header) {
+      SiteHeaderFooter header,
+      Provider<OAuthSessionOverOpenID> oauthSessionProvider,
+      Provider<CurrentUser> currentUserProvider,
+      DynamicMap<OAuthServiceProvider> oauthServiceProviders) {
     this.urlProvider = urlProvider;
     this.impl = impl;
     this.header = header;
     this.maxRedirectUrlLength = config.getInt(
         "openid", "maxRedirectUrlLength",
         10);
+    this.oauthSessionProvider = oauthSessionProvider;
+    this.currentUserProvider = currentUserProvider;
+    this.oauthServiceProviders = oauthServiceProviders;
 
     if (urlProvider == null || Strings.isNullOrEmpty(urlProvider.get())) {
       log.error("gerrit.canonicalWebUrl must be set in gerrit.config");
@@ -152,7 +164,23 @@
       mode = SignInMode.SIGN_IN;
     }
 
-    discover(req, res, link, id, remember, token, mode);
+    OAuthServiceProvider oauthProvider = lookupOAuthServiceProvider(id);
+
+    if (oauthProvider == null) {
+      discover(req, res, link, id, remember, token, mode);
+    } else {
+      OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
+      if (!currentUserProvider.get().isIdentifiedUser()
+          && oauthSession.isLoggedIn()) {
+        oauthSession.logout();
+      }
+      if ((isGerritLogin(req)
+          || oauthSession.isOAuthFinal(req))
+          && !oauthSession.isLoggedIn()) {
+        oauthSession.setServiceProvider(oauthProvider);
+        oauthSession.login(req, res, oauthProvider);
+      }
+    }
   }
 
   private void discover(HttpServletRequest req, HttpServletResponse res,
@@ -267,6 +295,20 @@
       }
       a.setAttribute("href", u.toString());
     }
+
+    // OAuth: Add plugin based providers
+    Element providers = HtmlDomUtil.find(doc, "providers");
+    Set<String> plugins = oauthServiceProviders.plugins();
+    for (String pluginName : plugins) {
+      Map<String, Provider<OAuthServiceProvider>> m =
+          oauthServiceProviders.byPlugin(pluginName);
+        for (Map.Entry<String, Provider<OAuthServiceProvider>> e
+            : m.entrySet()) {
+          addProvider(providers, pluginName, e.getKey(),
+              e.getValue().get().getName());
+        }
+    }
+
     sendHtml(res, doc);
   }
 
@@ -285,6 +327,38 @@
     }
   }
 
+  private static void addProvider(Element form, String pluginName,
+      String id, String serviceName) {
+    Element div = form.getOwnerDocument().createElement("div");
+    div.setAttribute("id", id);
+    Element hyperlink = form.getOwnerDocument().createElement("a");
+    hyperlink.setAttribute("href", String.format("?id=%s_%s",
+        pluginName, id));
+    hyperlink.setTextContent(serviceName +
+        " (" + pluginName + " plugin)");
+    div.appendChild(hyperlink);
+    form.appendChild(div);
+  }
+
+  private OAuthServiceProvider lookupOAuthServiceProvider(String providerId) {
+    if (providerId.startsWith("http://")) {
+      providerId = providerId.substring("http://".length());
+    }
+    Set<String> plugins = oauthServiceProviders.plugins();
+    for (String pluginName : plugins) {
+      Map<String, Provider<OAuthServiceProvider>> m =
+          oauthServiceProviders.byPlugin(pluginName);
+        for (Map.Entry<String, Provider<OAuthServiceProvider>> e
+            : m.entrySet()) {
+          if (providerId.equals(
+              String.format("%s_%s", pluginName, e.getKey()))) {
+            return e.getValue().get();
+          }
+        }
+    }
+    return null;
+  }
+
   private static String getLastId(HttpServletRequest req) {
     Cookie[] cookies = req.getCookies();
     if (cookies != null) {
@@ -296,4 +370,9 @@
     }
     return null;
   }
+
+  private static boolean isGerritLogin(HttpServletRequest request) {
+    return request.getRequestURI().indexOf(
+        OAuthSessionOverOpenID.GERRIT_LOGIN) >= 0;
+  }
 }
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthOverOpenIDLogoutServlet.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthOverOpenIDLogoutServlet.java
new file mode 100644
index 0000000..9dbff03
--- /dev/null
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthOverOpenIDLogoutServlet.java
@@ -0,0 +1,55 @@
+// 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.httpd.auth.openid;
+
+import com.google.gerrit.audit.AuditService;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.httpd.HttpLogoutServlet;
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Singleton
+class OAuthOverOpenIDLogoutServlet extends HttpLogoutServlet {
+  private static final long serialVersionUID = 1L;
+
+  private final Provider<OAuthSessionOverOpenID> oauthSession;
+
+  @Inject
+  OAuthOverOpenIDLogoutServlet(AuthConfig authConfig,
+      DynamicItem<WebSession> webSession,
+      @CanonicalWebUrl @Nullable Provider<String> urlProvider,
+      AuditService audit,
+      Provider<OAuthSessionOverOpenID> oauthSession) {
+    super(authConfig, webSession, urlProvider, audit);
+    this.oauthSession = oauthSession;
+  }
+
+  @Override
+  protected void doLogout(HttpServletRequest req, HttpServletResponse rsp)
+      throws IOException {
+    super.doLogout(req, rsp);
+    oauthSession.get().logout();
+  }
+}
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
new file mode 100644
index 0000000..a02f52d
--- /dev/null
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
@@ -0,0 +1,216 @@
+// 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.httpd.auth.openid;
+
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
+import com.google.gerrit.extensions.auth.oauth.OAuthToken;
+import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
+import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.httpd.CanonicalWebUrl;
+import com.google.gerrit.httpd.LoginUrlToken;
+import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthResult;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.servlet.SessionScoped;
+
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** OAuth protocol implementation */
+@SessionScoped
+class OAuthSessionOverOpenID {
+  static final String GERRIT_LOGIN = "/login";
+  private static final Logger log = LoggerFactory.getLogger(
+      OAuthSessionOverOpenID.class);
+  private static final SecureRandom randomState = newRandomGenerator();
+  private final String state;
+  private final DynamicItem<WebSession> webSession;
+  private final AccountManager accountManager;
+  private final CanonicalWebUrl urlProvider;
+  private OAuthServiceProvider serviceProvider;
+  private OAuthToken token;
+  private OAuthUserInfo user;
+  private String redirectToken;
+
+  @Inject
+  OAuthSessionOverOpenID(DynamicItem<WebSession> webSession,
+      AccountManager accountManager,
+      CanonicalWebUrl urlProvider) {
+    this.state = generateRandomState();
+    this.webSession = webSession;
+    this.accountManager = accountManager;
+    this.urlProvider = urlProvider;
+  }
+
+  boolean isLoggedIn() {
+    return token != null && user != null;
+  }
+
+  boolean isOAuthFinal(HttpServletRequest request) {
+    return Strings.emptyToNull(request.getParameter("code")) != null;
+  }
+
+  boolean login(HttpServletRequest request, HttpServletResponse response,
+      OAuthServiceProvider oauth) throws IOException {
+    if (isLoggedIn()) {
+      return true;
+    }
+
+    log.debug("Login " + this);
+
+    if (isOAuthFinal(request)) {
+      if (!checkState(request)) {
+        response.sendError(HttpServletResponse.SC_NOT_FOUND);
+        return false;
+      }
+
+      log.debug("Login-Retrieve-User " + this);
+      token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
+
+      user = oauth.getUserInfo(token);
+
+      if (isLoggedIn()) {
+        log.debug("Login-SUCCESS " + this);
+        authenticateAndRedirect(request, response);
+        return true;
+      } else {
+        response.sendError(SC_UNAUTHORIZED);
+        return false;
+      }
+    } else {
+      log.debug("Login-PHASE1 " + this);
+      redirectToken = LoginUrlToken.getToken(request);
+      response.sendRedirect(oauth.getAuthorizationUrl() +
+          "&state=" + state);
+      return false;
+    }
+  }
+
+  private void authenticateAndRedirect(HttpServletRequest req,
+      HttpServletResponse rsp) throws IOException {
+    com.google.gerrit.server.account.AuthRequest areq =
+        new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
+    AuthResult arsp = null;
+    try {
+      String claimedIdentifier = user.getClaimedIdentity();
+      Account.Id actualId = accountManager.lookup(user.getExternalId());
+      if (!Strings.isNullOrEmpty(claimedIdentifier)) {
+        Account.Id claimedId = accountManager.lookup(claimedIdentifier);
+        if (claimedId != null && actualId != null) {
+          if (claimedId.equals(actualId)) {
+            // Both link to the same account, that's what we expected.
+          } else {
+            // This is (for now) a fatal error. There are two records
+            // for what might be the same user.
+            //
+            log.error("OAuth accounts disagree over user identity:\n"
+                + "  Claimed ID: " + claimedId + " is " + claimedIdentifier
+                + "\n" + "  Delgate ID: " + actualId + " is "
+                + user.getExternalId());
+            rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+          }
+        } else if (claimedId != null && actualId == null) {
+          // Claimed account already exists: link to it.
+          //
+          try {
+            accountManager.link(claimedId, areq);
+          } catch (OrmException e) {
+            log.error("Cannot link: " +  user.getExternalId()
+                + " to user identity:\n"
+                + "  Claimed ID: " + claimedId + " is " + claimedIdentifier);
+            rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+          }
+        }
+      }
+      areq.setUserName(user.getUserName());
+      areq.setEmailAddress(user.getEmailAddress());
+      areq.setDisplayName(user.getDisplayName());
+      arsp = accountManager.authenticate(areq);
+    } catch (AccountException e) {
+      log.error("Unable to authenticate user \"" + user + "\"", e);
+      rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
+      return;
+    }
+
+    webSession.get().login(arsp, true);
+    StringBuilder rdr = new StringBuilder(urlProvider.get(req));
+    rdr.append(Url.decode(redirectToken));
+    rsp.sendRedirect(rdr.toString());
+  }
+
+  void logout() {
+    token = null;
+    user = null;
+    redirectToken = null;
+    serviceProvider = null;
+  }
+
+  private boolean checkState(ServletRequest request) {
+    String s = Strings.nullToEmpty(request.getParameter("state"));
+    if (!s.equals(state)) {
+      log.error("Illegal request state '" + s + "' on OAuthProtocol " + this);
+      return false;
+    }
+    return true;
+  }
+
+  private static SecureRandom newRandomGenerator() {
+    try {
+      return SecureRandom.getInstance("SHA1PRNG");
+    } catch (NoSuchAlgorithmException e) {
+      throw new IllegalArgumentException(
+          "No SecureRandom available for GitHub authentication", e);
+    }
+  }
+
+  private static String generateRandomState() {
+    byte[] state = new byte[32];
+    randomState.nextBytes(state);
+    return Base64.encodeBase64URLSafeString(state);
+  }
+
+  @Override
+  public String toString() {
+    return "OAuthSession [token=" + token + ", user=" + user + "]";
+  }
+
+  public void setServiceProvider(OAuthServiceProvider provider) {
+    this.serviceProvider = provider;
+  }
+
+  public OAuthServiceProvider getServiceProvider() {
+    return serviceProvider;
+  }
+}
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
new file mode 100644
index 0000000..7766e69
--- /dev/null
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
@@ -0,0 +1,115 @@
+// 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.httpd.auth.openid;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.server.CurrentUser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+import java.util.SortedMap;
+import java.util.SortedSet;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+
+/** OAuth web filter uses active OAuth session to perform OAuth requests */
+@Singleton
+class OAuthWebFilterOverOpenID implements Filter {
+  static final String GERRIT_LOGIN = "/login";
+
+  private final Provider<CurrentUser> currentUserProvider;
+  private final Provider<OAuthSessionOverOpenID> oauthSessionProvider;
+  private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
+  private OAuthServiceProvider ssoProvider;
+
+  @Inject
+  OAuthWebFilterOverOpenID(Provider<CurrentUser> currentUserProvider,
+      DynamicMap<OAuthServiceProvider> oauthServiceProviders,
+      Provider<OAuthSessionOverOpenID> oauthSessionProvider) {
+    this.currentUserProvider = currentUserProvider;
+    this.oauthServiceProviders = oauthServiceProviders;
+    this.oauthSessionProvider = oauthSessionProvider;
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+    pickSSOServiceProvider();
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest httpRequest = (HttpServletRequest) request;
+    HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
+    if (currentUserProvider.get().isIdentifiedUser()) {
+      if (httpSession != null) {
+        httpSession.invalidate();
+      }
+      chain.doFilter(request, response);
+      return;
+    }
+
+    HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+    OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
+    OAuthServiceProvider service = ssoProvider == null
+        ? oauthSession.getServiceProvider()
+        : ssoProvider;
+
+    if ((isGerritLogin(httpRequest)
+        || oauthSession.isOAuthFinal(httpRequest))
+        && !oauthSession.isLoggedIn()) {
+        if (service == null) {
+          throw new IllegalStateException("service is unknown");
+        }
+        oauthSession.setServiceProvider(service);
+        oauthSession.login(httpRequest, httpResponse, service);
+    } else {
+      chain.doFilter(httpRequest, response);
+    }
+  }
+
+  private void pickSSOServiceProvider() {
+    SortedSet<String> plugins = oauthServiceProviders.plugins();
+    if (plugins.size() == 1) {
+      SortedMap<String, Provider<OAuthServiceProvider>> services =
+          oauthServiceProviders.byPlugin(Iterables.getOnlyElement(plugins));
+      if (services.size() == 1) {
+        ssoProvider = Iterables.getOnlyElement(services.values()).get();
+      }
+    }
+  }
+
+  private static boolean isGerritLogin(HttpServletRequest request) {
+    return request.getRequestURI().indexOf(GERRIT_LOGIN) >= 0;
+  }
+}
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdModule.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdModule.java
index c87a0cf..ace0c53 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdModule.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdModule.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.httpd.auth.openid;
 
+import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.inject.servlet.ServletModule;
 
 /** Servlets related to OpenID authentication. */
@@ -21,9 +23,12 @@
   @Override
   protected void configureServlets() {
     serve("/login", "/login/*").with(LoginForm.class);
+    serve("/logout").with(OAuthOverOpenIDLogoutServlet.class);
+    filter("/oauth").through(OAuthWebFilterOverOpenID.class);
     serve("/" + OpenIdServiceImpl.RETURN_URL).with(OpenIdLoginServlet.class);
     serve("/" + XrdsServlet.LOCATION).with(XrdsServlet.class);
     filter("/").through(XrdsFilter.class);
     bind(OpenIdServiceImpl.class);
+    DynamicMap.mapOf(binder(), OAuthServiceProvider.class);
   }
 }
diff --git a/gerrit-openid/src/main/resources/com/google/gerrit/httpd/auth/openid/LoginForm.html b/gerrit-openid/src/main/resources/com/google/gerrit/httpd/auth/openid/LoginForm.html
index 1e2c510..07e09f5 100644
--- a/gerrit-openid/src/main/resources/com/google/gerrit/httpd/auth/openid/LoginForm.html
+++ b/gerrit-openid/src/main/resources/com/google/gerrit/httpd/auth/openid/LoginForm.html
@@ -16,9 +16,19 @@
       #logo_box {
         padding-left: 160px;
       }
-      #logo_img {
+      #logo_oauth {
+        width: 96px;
+        height: 96px;
+        display: inline-block;
+        margin-bottom: 20px;
+        background: url('data:image/png;base64,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') no-repeat 0px 0px;
+      }
+      #logo_openid {
         width: 200px;
         height: 80px;
+        display: inline-block;
+        margin-left: 100px;
+        margin-bottom: 28px;
         background: url('data:image/png;base64,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') no-repeat 0px 0px;
       }
       #f_openid {
@@ -36,7 +46,7 @@
       <h1>Sign In to Gerrit Code Review at <span id="hostName">example.com</span></h1>
       <form method="POST" action="#" id="login_form">
         <input type="hidden" name="link" id="f_link" value="1" />
-        <div id="logo_box"><div id="logo_img"></div></div>
+        <div id="logo_box"><div id="logo_oauth"></div><div id="logo_openid"></div></div>
         <div id="error_message">Invalid OpenID identifier.</div>
         <div>
           <input type="text"
@@ -57,6 +67,9 @@
           <a href="../" id="cancel_link">Cancel</a>
         </div>
 
+        <div id="providers">
+        </div>
+
         <div id="provider_launchpad">
           <img height="16" width="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHKSURBVCiRjZI9SFtRGIafc5tiVFCkhUxKBAcLYoPWwE0GEUPBwc0f1MHFoWLBiKABobWtBh2kacFYwUUQRXBoB3EJCVUvyBVrIB1UskTawUGFQOoP5h6H3ki8deg7vuc853zvxyuklORLDUc9QH9nZeAL0AGM+1v0cywSOVANR8uBaaALOOysDLwCYsAp8AaY97fo2RyoqOFooRqOvgUOTMiqJ8AsEA9tuH13IFkcQD1QZHpbQE+bPXEADAK5MSsA1/p+1SMAoTbF9oAh2WYUAcWbz5tThiQE1EsI7VxVzMWvHe2lGWP1RaqgWyKe1g5vjyhAHfBdrCmuWG1zqSHRAS9gFxBQC451/+PdhbqUPSYR00JSAmDLy+IE7ICwZHRkbBQKqMo3lQeW8V/KB5PAMSAtd06Kb7iQf8/vgXuA9/2V9nN7dCKjCNyABlxKmPqx7HQvDjX0HX29bBLIUSlIA4gNT9Bpw/gMtJqPaYD/2evJX5FgTQcwDpQBaeCD8+XZx8aZZFaxYZyYv/4xQS+wFAnWVAOfTAgzRrxxJpkFUHza2IVPG3sHVAMrD+zhFBgAXL0JPZIzhbXkEe+kB+j/nf52V/LexL8lvwVTCpkwGXEEfAAAAABJRU5ErkJggg=="/>
           <a href="?id=https://login.launchpad.net/%2Bopenid" id="id_launchpad">Sign in with a Launchpad ID</a>
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 4e811bf..00a1ade 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -38,7 +38,6 @@
 import com.google.gerrit.pgm.http.jetty.JettyModule;
 import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter;
 import com.google.gerrit.pgm.util.ErrorLogFile;
-import com.google.gerrit.pgm.util.GarbageCollectionLogFile;
 import com.google.gerrit.pgm.util.LogFileCompressor;
 import com.google.gerrit.pgm.util.RuntimeShutdown;
 import com.google.gerrit.pgm.util.SiteProgram;
@@ -55,7 +54,7 @@
 import com.google.gerrit.server.config.RestCacheAdminModule;
 import com.google.gerrit.server.contact.ContactStoreModule;
 import com.google.gerrit.server.contact.HttpContactStoreConnection;
-import com.google.gerrit.server.git.GarbageCollectionRunner;
+import com.google.gerrit.server.git.GarbageCollectionModule;
 import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.index.DummyIndexModule;
@@ -152,6 +151,7 @@
   private Path runFile;
   private boolean test;
   private AbstractModule luceneModule;
+  private Module emailModule;
 
   private Runnable serverStarted;
 
@@ -196,7 +196,6 @@
       throw die("No services enabled, nothing to do");
     }
 
-    manager.add(GarbageCollectionLogFile.start(getSitePath()));
     if (!consoleLog) {
       manager.add(ErrorLogFile.start(getSitePath()));
     }
@@ -262,6 +261,11 @@
   }
 
   @VisibleForTesting
+  public void setEmailModuleForTesting(Module module) {
+    emailModule = module;
+  }
+
+  @VisibleForTesting
   public void setLuceneModule(LuceneIndexModule m) {
     luceneModule = m;
     test = true;
@@ -322,7 +326,11 @@
     modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
     modules.add(new InternalAccountDirectory.Module());
     modules.add(new DefaultCacheFactory.Module());
-    modules.add(new SmtpEmailSender.Module());
+    if (emailModule != null) {
+      modules.add(emailModule);
+    } else {
+      modules.add(new SmtpEmailSender.Module());
+    }
     modules.add(new SignedTokenEmailTokenVerifier.Module());
     modules.add(new PluginRestApiModule());
     modules.add(new RestCacheAdminModule());
@@ -361,7 +369,7 @@
         }
       }
     });
-    modules.add(GarbageCollectionRunner.module());
+    modules.add(new GarbageCollectionModule());
     return cfgInjector.createChildInjector(modules);
   }
 
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
index 9fdb560..1719a7e 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -17,6 +17,10 @@
 
 /** Constants and utilities for Gerrit-specific ref names. */
 public class RefNames {
+  public static final String REFS = "refs/";
+
+  public static final String REFS_HEADS = "refs/heads/";
+
   public static final String REFS_CHANGES = "refs/changes/";
 
   /** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
@@ -48,6 +52,10 @@
   /** Suffix of a meta ref in the notedb. */
   public static final String META_SUFFIX = "/meta";
 
+  public static String fullName(String ref) {
+    return (ref.startsWith(REFS) ? "" : REFS_HEADS) + ref;
+  }
+
   public static String refsUsers(Account.Id accountId) {
     StringBuilder r = new StringBuilder();
     r.append(REFS_USER);
@@ -78,6 +86,36 @@
     return r.toString();
   }
 
+  /**
+   * Returns reference for this change edit with sharded user and change number:
+   * refs/users/UU/UUUU/edit-CCCC/P.
+   *
+   * @param accountId account id
+   * @param changeId change number
+   * @param psId patch set number
+   * @return reference for this change edit
+   */
+  public static String refsEdit(Account.Id accountId, Change.Id changeId,
+      PatchSet.Id psId) {
+    return refsEditPrefix(accountId, changeId) + psId.get();
+  }
+
+  /**
+   * Returns reference prefix for this change edit with sharded user and
+   * change number: refs/users/UU/UUUU/edit-CCCC/.
+   *
+   * @param accountId account id
+   * @param changeId change number
+   * @return reference prefix for this change edit
+   */
+  public static String refsEditPrefix(Account.Id accountId, Change.Id changeId) {
+    return new StringBuilder(refsUsers(accountId))
+      .append("/edit-")
+      .append(changeId.get())
+      .append("/")
+      .toString();
+  }
+
   private RefNames() {
   }
 }
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 087880e..ec91d49 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -94,16 +94,19 @@
     ':server',
     '//gerrit-common:server',
     '//gerrit-cache-h2:cache-h2',
+    '//gerrit-extension-api:api',
     '//gerrit-lucene:lucene',
     '//gerrit-reviewdb:server',
     '//lib:guava',
     '//lib:gwtorm',
     '//lib:h2',
     '//lib:junit',
+    '//lib/auto:auto-value',
     '//lib/guice:guice',
     '//lib/guice:guice-servlet',
     '//lib/jgit:jgit',
     '//lib/jgit:junit',
+    '//lib/log:api',
     '//lib/log:impl_log4j',
     '//lib/log:log4j',
   ],
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 7b81560..19c3145 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.reviewdb.client.Account;
@@ -40,11 +41,11 @@
 import com.google.gerrit.server.events.ChangeRestoredEvent;
 import com.google.gerrit.server.events.CommentAddedEvent;
 import com.google.gerrit.server.events.DraftPublishedEvent;
-import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.events.HashtagsChangedEvent;
 import com.google.gerrit.server.events.MergeFailedEvent;
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import com.google.gerrit.server.events.ProjectCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.events.ReviewerAddedEvent;
 import com.google.gerrit.server.events.TopicChangedEvent;
@@ -89,7 +90,7 @@
 /** Spawns local executables when a hook action occurs. */
 @Singleton
 public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
-  EventSource, LifecycleListener {
+  EventSource, LifecycleListener, NewProjectCreatedListener {
     /** A logger for this class. */
     private static final Logger log = LoggerFactory.getLogger(ChangeHookRunner.class);
 
@@ -100,18 +101,19 @@
         bind(ChangeHooks.class).to(ChangeHookRunner.class);
         bind(EventDispatcher.class).to(ChangeHookRunner.class);
         bind(EventSource.class).to(ChangeHookRunner.class);
+        DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(ChangeHookRunner.class);
         listener().to(ChangeHookRunner.class);
       }
     }
 
     private static class EventListenerHolder {
-        final EventListener listener;
-        final CurrentUser user;
+      final EventListener listener;
+      final CurrentUser user;
 
-        EventListenerHolder(EventListener l, CurrentUser u) {
-            listener = l;
-            user = u;
-        }
+      EventListenerHolder(EventListener l, CurrentUser u) {
+        listener = l;
+        user = u;
+      }
     }
 
     /** Container class used to hold the return code and output of script hook execution */
@@ -209,6 +211,9 @@
     /** Path of the hashtags changed hook */
     private final Path hashtagsChangedHook;
 
+    /** Path of the project created hook. */
+    private final Path projectCreatedHook;
+
     private final String anonymousCowardName;
 
     /** Repository Manager. */
@@ -241,15 +246,15 @@
      * @param projectCache the project cache instance for the server.
      */
     @Inject
-    public ChangeHookRunner(final WorkQueue queue,
-      final GitRepositoryManager repoManager,
-      @GerritServerConfig final Config config,
-      @AnonymousCowardName final String anonymousCowardName,
-      final SitePaths sitePath,
-      final ProjectCache projectCache,
-      final AccountCache accountCache,
-      final EventFactory eventFactory,
-      final DynamicSet<EventListener> unrestrictedListeners) {
+    public ChangeHookRunner(WorkQueue queue,
+      GitRepositoryManager repoManager,
+      @GerritServerConfig Config config,
+      @AnonymousCowardName String anonymousCowardName,
+      SitePaths sitePath,
+      ProjectCache projectCache,
+      AccountCache accountCache,
+      EventFactory eventFactory,
+      DynamicSet<EventListener> unrestrictedListeners) {
         this.anonymousCowardName = anonymousCowardName;
         this.repoManager = repoManager;
         this.hookQueue = queue.createQueue(1, "hook");
@@ -282,6 +287,7 @@
         claSignedHook = hook(config, hooksPath, "cla-signed");
         refUpdateHook = hook(config, hooksPath, "ref-update");
         hashtagsChangedHook = hook(config, hooksPath, "hashtags-changed");
+        projectCreatedHook = hook(config, hooksPath, "project-created");
 
         syncHookTimeout = config.getInt("hooks", "syncHookTimeout", 30);
         syncHookThreadPool = Executors.newCachedThreadPool(
@@ -298,12 +304,12 @@
 
     @Override
     public void addEventListener(EventListener listener, CurrentUser user) {
-        listeners.put(listener, new EventListenerHolder(listener, user));
+      listeners.put(listener, new EventListenerHolder(listener, user));
     }
 
     @Override
     public void removeEventListener(EventListener listener) {
-        listeners.remove(listener);
+      listeners.remove(listener);
     }
 
     /**
@@ -312,20 +318,20 @@
      * @param name Project to get repo for,
      * @return Repository or null.
      */
-    private Repository openRepository(final Project.NameKey name) {
-        try {
-            return repoManager.openRepository(name);
-        } catch (IOException err) {
-            log.warn("Cannot open repository " + name.get(), err);
-            return null;
-        }
+    private Repository openRepository(Project.NameKey name) {
+      try {
+        return repoManager.openRepository(name);
+      } catch (IOException err) {
+        log.warn("Cannot open repository " + name.get(), err);
+        return null;
+      }
     }
 
     private void addArg(List<String> args, String name, String value) {
-        if (value != null) {
-            args.add(name);
-            args.add(value);
-        }
+      if (value != null) {
+        args.add(name);
+        args.add(value);
+      }
     }
 
     /**
@@ -333,10 +339,10 @@
      *
      */
     @Override
-    public HookResult doRefUpdateHook(final Project project, final String refname,
-        final Account uploader, final ObjectId oldId, final ObjectId newId) {
+    public HookResult doRefUpdateHook(Project project, String refname,
+        Account uploader, ObjectId oldId, ObjectId newId) {
 
-      final List<String> args = new ArrayList<>();
+      List<String> args = new ArrayList<>();
       addArg(args, "--project", project.getName());
       addArg(args, "--refname", refname);
       addArg(args, "--uploader", getDisplayName(uploader));
@@ -346,6 +352,20 @@
       return runSyncHook(project.getNameKey(), refUpdateHook, args);
     }
 
+    @Override
+    public void doProjectCreatedHook(Project.NameKey project, String headName) {
+      ProjectCreatedEvent event = new ProjectCreatedEvent();
+      event.projectName = project.get();
+      event.headName = headName;
+      fireEvent(project, event);
+
+      List<String> args = new ArrayList<>();
+      addArg(args, "--project", project.get());
+      addArg(args, "--head", headName);
+
+      runHook(project, projectCreatedHook, args);
+    }
+
     /**
      * Fire the Patchset Created Hook.
      *
@@ -354,219 +374,222 @@
      * @throws OrmException
      */
     @Override
-    public void doPatchsetCreatedHook(final Change change, final PatchSet patchSet,
-          final ReviewDb db) throws OrmException {
-        final PatchSetCreatedEvent event = new PatchSetCreatedEvent();
-        final AccountState uploader = accountCache.get(patchSet.getUploader());
-        final AccountState owner = accountCache.get(change.getOwner());
+    public void doPatchsetCreatedHook(Change change, PatchSet patchSet,
+          ReviewDb db) throws OrmException {
+      PatchSetCreatedEvent event = new PatchSetCreatedEvent();
+      AccountState uploader = accountCache.get(patchSet.getUploader());
+      AccountState owner = accountCache.get(change.getOwner());
 
-        event.change = eventFactory.asChangeAttribute(change);
-        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
-        event.uploader = eventFactory.asAccountAttribute(uploader.getAccount());
-        fireEvent(change, event, db);
+      event.change = eventFactory.asChangeAttribute(change);
+      event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+      event.uploader = eventFactory.asAccountAttribute(uploader.getAccount());
+      fireEvent(change, event, db);
 
-        final List<String> args = new ArrayList<>();
-        addArg(args, "--change", event.change.id);
-        addArg(args, "--is-draft", String.valueOf(patchSet.isDraft()));
-        addArg(args, "--kind", String.valueOf(event.patchSet.kind));
-        addArg(args, "--change-url", event.change.url);
-        addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
-        addArg(args, "--project", event.change.project);
-        addArg(args, "--branch", event.change.branch);
-        addArg(args, "--topic", event.change.topic);
-        addArg(args, "--uploader", getDisplayName(uploader.getAccount()));
-        addArg(args, "--commit", event.patchSet.revision);
-        addArg(args, "--patchset", event.patchSet.number);
+      List<String> args = new ArrayList<>();
+      addArg(args, "--change", event.change.id);
+      addArg(args, "--is-draft", String.valueOf(patchSet.isDraft()));
+      addArg(args, "--kind", String.valueOf(event.patchSet.kind));
+      addArg(args, "--change-url", event.change.url);
+      addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
+      addArg(args, "--project", event.change.project);
+      addArg(args, "--branch", event.change.branch);
+      addArg(args, "--topic", event.change.topic);
+      addArg(args, "--uploader", getDisplayName(uploader.getAccount()));
+      addArg(args, "--commit", event.patchSet.revision);
+      addArg(args, "--patchset", event.patchSet.number);
 
-        runHook(change.getProject(), patchsetCreatedHook, args);
+      runHook(change.getProject(), patchsetCreatedHook, args);
     }
 
     @Override
-    public void doDraftPublishedHook(final Change change, final PatchSet patchSet,
-          final ReviewDb db) throws OrmException {
-        final DraftPublishedEvent event = new DraftPublishedEvent();
-        final AccountState uploader = accountCache.get(patchSet.getUploader());
-        final AccountState owner = accountCache.get(change.getOwner());
+    public void doDraftPublishedHook(Change change, PatchSet patchSet,
+          ReviewDb db) throws OrmException {
+      DraftPublishedEvent event = new DraftPublishedEvent();
+      AccountState uploader = accountCache.get(patchSet.getUploader());
+      AccountState owner = accountCache.get(change.getOwner());
 
-        event.change = eventFactory.asChangeAttribute(change);
-        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
-        event.uploader = eventFactory.asAccountAttribute(uploader.getAccount());
-        fireEvent(change, event, db);
+      event.change = eventFactory.asChangeAttribute(change);
+      event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+      event.uploader = eventFactory.asAccountAttribute(uploader.getAccount());
+      fireEvent(change, event, db);
 
-        final List<String> args = new ArrayList<>();
-        addArg(args, "--change", event.change.id);
-        addArg(args, "--change-url", event.change.url);
-        addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
-        addArg(args, "--project", event.change.project);
-        addArg(args, "--branch", event.change.branch);
-        addArg(args, "--topic", event.change.topic);
-        addArg(args, "--uploader", getDisplayName(uploader.getAccount()));
-        addArg(args, "--commit", event.patchSet.revision);
-        addArg(args, "--patchset", event.patchSet.number);
+      List<String> args = new ArrayList<>();
+      addArg(args, "--change", event.change.id);
+      addArg(args, "--change-url", event.change.url);
+      addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
+      addArg(args, "--project", event.change.project);
+      addArg(args, "--branch", event.change.branch);
+      addArg(args, "--topic", event.change.topic);
+      addArg(args, "--uploader", getDisplayName(uploader.getAccount()));
+      addArg(args, "--commit", event.patchSet.revision);
+      addArg(args, "--patchset", event.patchSet.number);
 
-        runHook(change.getProject(), draftPublishedHook, args);
+      runHook(change.getProject(), draftPublishedHook, args);
     }
 
     @Override
-    public void doCommentAddedHook(final Change change, final Account account,
-          final PatchSet patchSet, final String comment, final Map<String, Short> approvals,
-          final ReviewDb db) throws OrmException {
-        final CommentAddedEvent event = new CommentAddedEvent();
-        final AccountState owner = accountCache.get(change.getOwner());
+    public void doCommentAddedHook(Change change, Account account,
+          PatchSet patchSet, String comment, Map<String, Short> approvals,
+          ReviewDb db) throws OrmException {
+      CommentAddedEvent event = new CommentAddedEvent();
+      AccountState owner = accountCache.get(change.getOwner());
 
-        event.change = eventFactory.asChangeAttribute(change);
-        event.author =  eventFactory.asAccountAttribute(account);
-        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
-        event.comment = comment;
+      event.change = eventFactory.asChangeAttribute(change);
+      event.author =  eventFactory.asAccountAttribute(account);
+      event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+      event.comment = comment;
 
-        LabelTypes labelTypes = projectCache.get(change.getProject()).getLabelTypes();
-        if (approvals.size() > 0) {
-            event.approvals = new ApprovalAttribute[approvals.size()];
-            int i = 0;
-            for (Map.Entry<String, Short> approval : approvals.entrySet()) {
-                event.approvals[i++] = getApprovalAttribute(labelTypes, approval);
-            }
-        }
-
-        fireEvent(change, event, db);
-
-        final List<String> args = new ArrayList<>();
-        addArg(args, "--change", event.change.id);
-        addArg(args, "--is-draft", patchSet.isDraft() ? "true" : "false");
-        addArg(args, "--change-url", event.change.url);
-        addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
-        addArg(args, "--project", event.change.project);
-        addArg(args, "--branch", event.change.branch);
-        addArg(args, "--topic", event.change.topic);
-        addArg(args, "--author", getDisplayName(account));
-        addArg(args, "--commit", event.patchSet.revision);
-        addArg(args, "--comment", comment == null ? "" : comment);
+      LabelTypes labelTypes = projectCache.get(change.getProject()).getLabelTypes();
+      if (approvals.size() > 0) {
+        event.approvals = new ApprovalAttribute[approvals.size()];
+        int i = 0;
         for (Map.Entry<String, Short> approval : approvals.entrySet()) {
-          LabelType lt = labelTypes.byLabel(approval.getKey());
-          if (lt != null) {
-            addArg(args, "--" + lt.getName(), Short.toString(approval.getValue()));
-          }
+          event.approvals[i++] = getApprovalAttribute(labelTypes, approval);
         }
+      }
 
-        runHook(change.getProject(), commentAddedHook, args);
+      fireEvent(change, event, db);
+
+      List<String> args = new ArrayList<>();
+      addArg(args, "--change", event.change.id);
+      addArg(args, "--is-draft", patchSet.isDraft() ? "true" : "false");
+      addArg(args, "--change-url", event.change.url);
+      addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
+      addArg(args, "--project", event.change.project);
+      addArg(args, "--branch", event.change.branch);
+      addArg(args, "--topic", event.change.topic);
+      addArg(args, "--author", getDisplayName(account));
+      addArg(args, "--commit", event.patchSet.revision);
+      addArg(args, "--comment", comment == null ? "" : comment);
+      for (Map.Entry<String, Short> approval : approvals.entrySet()) {
+        LabelType lt = labelTypes.byLabel(approval.getKey());
+        if (lt != null) {
+          addArg(args, "--" + lt.getName(), Short.toString(approval.getValue()));
+        }
+      }
+
+      runHook(change.getProject(), commentAddedHook, args);
     }
 
     @Override
-  public void doChangeMergedHook(final Change change, final Account account,
-      final PatchSet patchSet, final ReviewDb db, String mergeResultRev)
-      throws OrmException {
-        final ChangeMergedEvent event = new ChangeMergedEvent();
-        final AccountState owner = accountCache.get(change.getOwner());
+    public void doChangeMergedHook(Change change, Account account,
+        PatchSet patchSet, ReviewDb db, String mergeResultRev)
+        throws OrmException {
+      ChangeMergedEvent event = new ChangeMergedEvent();
+      AccountState owner = accountCache.get(change.getOwner());
 
-        event.change = eventFactory.asChangeAttribute(change);
-        event.submitter = eventFactory.asAccountAttribute(account);
-        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
-        event.newRev = mergeResultRev;
-        fireEvent(change, event, db);
+      event.change = eventFactory.asChangeAttribute(change);
+      event.submitter = eventFactory.asAccountAttribute(account);
+      event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+      event.newRev = mergeResultRev;
+      fireEvent(change, event, db);
 
-        final List<String> args = new ArrayList<>();
-        addArg(args, "--change", event.change.id);
-        addArg(args, "--change-url", event.change.url);
-        addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
-        addArg(args, "--project", event.change.project);
-        addArg(args, "--branch", event.change.branch);
-        addArg(args, "--topic", event.change.topic);
-        addArg(args, "--submitter", getDisplayName(account));
-        addArg(args, "--commit", event.patchSet.revision);
-        addArg(args, "--newrev", mergeResultRev);
+      List<String> args = new ArrayList<>();
+      addArg(args, "--change", event.change.id);
+      addArg(args, "--change-url", event.change.url);
+      addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
+      addArg(args, "--project", event.change.project);
+      addArg(args, "--branch", event.change.branch);
+      addArg(args, "--topic", event.change.topic);
+      addArg(args, "--submitter", getDisplayName(account));
+      addArg(args, "--commit", event.patchSet.revision);
+      addArg(args, "--newrev", mergeResultRev);
 
-        runHook(change.getProject(), changeMergedHook, args);
+      runHook(change.getProject(), changeMergedHook, args);
     }
 
     @Override
-    public void doMergeFailedHook(final Change change, final Account account,
-          final PatchSet patchSet, final String reason,
-          final ReviewDb db) throws OrmException {
-        final MergeFailedEvent event = new MergeFailedEvent();
-        final AccountState owner = accountCache.get(change.getOwner());
+    public void doMergeFailedHook(Change change, Account account,
+          PatchSet patchSet, String reason,
+          ReviewDb db) throws OrmException {
+      MergeFailedEvent event = new MergeFailedEvent();
+      AccountState owner = accountCache.get(change.getOwner());
 
-        event.change = eventFactory.asChangeAttribute(change);
-        event.submitter = eventFactory.asAccountAttribute(account);
-        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
-        event.reason = reason;
-        fireEvent(change, event, db);
+      event.change = eventFactory.asChangeAttribute(change);
+      event.submitter = eventFactory.asAccountAttribute(account);
+      event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+      event.reason = reason;
+      fireEvent(change, event, db);
 
-        final List<String> args = new ArrayList<>();
-        addArg(args, "--change", event.change.id);
-        addArg(args, "--change-url", event.change.url);
-        addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
-        addArg(args, "--project", event.change.project);
-        addArg(args, "--branch", event.change.branch);
-        addArg(args, "--topic", event.change.topic);
-        addArg(args, "--submitter", getDisplayName(account));
-        addArg(args, "--commit", event.patchSet.revision);
-        addArg(args, "--reason",  reason == null ? "" : reason);
+      List<String> args = new ArrayList<>();
+      addArg(args, "--change", event.change.id);
+      addArg(args, "--change-url", event.change.url);
+      addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
+      addArg(args, "--project", event.change.project);
+      addArg(args, "--branch", event.change.branch);
+      addArg(args, "--topic", event.change.topic);
+      addArg(args, "--submitter", getDisplayName(account));
+      addArg(args, "--commit", event.patchSet.revision);
+      addArg(args, "--reason",  reason == null ? "" : reason);
 
-        runHook(change.getProject(), mergeFailedHook, args);
+      runHook(change.getProject(), mergeFailedHook, args);
     }
 
     @Override
-    public void doChangeAbandonedHook(final Change change, final Account account,
-          final PatchSet patchSet, final String reason, final ReviewDb db)
+    public void doChangeAbandonedHook(Change change, Account account,
+          PatchSet patchSet, String reason, ReviewDb db)
           throws OrmException {
-        final ChangeAbandonedEvent event = new ChangeAbandonedEvent();
-        final AccountState owner = accountCache.get(change.getOwner());
+      ChangeAbandonedEvent event = new ChangeAbandonedEvent();
+      AccountState owner = accountCache.get(change.getOwner());
 
-        event.change = eventFactory.asChangeAttribute(change);
-        event.abandoner = eventFactory.asAccountAttribute(account);
-        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
-        event.reason = reason;
-        fireEvent(change, event, db);
+      event.change = eventFactory.asChangeAttribute(change);
+      event.abandoner = eventFactory.asAccountAttribute(account);
+      event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+      event.reason = reason;
+      fireEvent(change, event, db);
 
-        final List<String> args = new ArrayList<>();
-        addArg(args, "--change", event.change.id);
-        addArg(args, "--change-url", event.change.url);
-        addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
-        addArg(args, "--project", event.change.project);
-        addArg(args, "--branch", event.change.branch);
-        addArg(args, "--topic", event.change.topic);
-        addArg(args, "--abandoner", getDisplayName(account));
-        addArg(args, "--commit", event.patchSet.revision);
-        addArg(args, "--reason", reason == null ? "" : reason);
+      List<String> args = new ArrayList<>();
+      addArg(args, "--change", event.change.id);
+      addArg(args, "--change-url", event.change.url);
+      addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
+      addArg(args, "--project", event.change.project);
+      addArg(args, "--branch", event.change.branch);
+      addArg(args, "--topic", event.change.topic);
+      addArg(args, "--abandoner", getDisplayName(account));
+      addArg(args, "--commit", event.patchSet.revision);
+      addArg(args, "--reason", reason == null ? "" : reason);
 
-        runHook(change.getProject(), changeAbandonedHook, args);
+      runHook(change.getProject(), changeAbandonedHook, args);
     }
 
     @Override
-    public void doChangeRestoredHook(final Change change, final Account account,
-          final PatchSet patchSet, final String reason, final ReviewDb db)
+    public void doChangeRestoredHook(Change change, Account account,
+          PatchSet patchSet, String reason, ReviewDb db)
           throws OrmException {
-        final ChangeRestoredEvent event = new ChangeRestoredEvent();
-        final AccountState owner = accountCache.get(change.getOwner());
+      ChangeRestoredEvent event = new ChangeRestoredEvent();
+      AccountState owner = accountCache.get(change.getOwner());
 
-        event.change = eventFactory.asChangeAttribute(change);
-        event.restorer = eventFactory.asAccountAttribute(account);
-        event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
-        event.reason = reason;
-        fireEvent(change, event, db);
+      event.change = eventFactory.asChangeAttribute(change);
+      event.restorer = eventFactory.asAccountAttribute(account);
+      event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
+      event.reason = reason;
+      fireEvent(change, event, db);
 
-        final List<String> args = new ArrayList<>();
-        addArg(args, "--change", event.change.id);
-        addArg(args, "--change-url", event.change.url);
-        addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
-        addArg(args, "--project", event.change.project);
-        addArg(args, "--branch", event.change.branch);
-        addArg(args, "--topic", event.change.topic);
-        addArg(args, "--restorer", getDisplayName(account));
-        addArg(args, "--commit", event.patchSet.revision);
-        addArg(args, "--reason", reason == null ? "" : reason);
+      List<String> args = new ArrayList<>();
+      addArg(args, "--change", event.change.id);
+      addArg(args, "--change-url", event.change.url);
+      addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
+      addArg(args, "--project", event.change.project);
+      addArg(args, "--branch", event.change.branch);
+      addArg(args, "--topic", event.change.topic);
+      addArg(args, "--restorer", getDisplayName(account));
+      addArg(args, "--commit", event.patchSet.revision);
+      addArg(args, "--reason", reason == null ? "" : reason);
 
-        runHook(change.getProject(), changeRestoredHook, args);
+      runHook(change.getProject(), changeRestoredHook, args);
     }
 
     @Override
-    public void doRefUpdatedHook(final Branch.NameKey refName, final RefUpdate refUpdate, final Account account) {
-      doRefUpdatedHook(refName, refUpdate.getOldObjectId(), refUpdate.getNewObjectId(), account);
+    public void doRefUpdatedHook(Branch.NameKey refName, RefUpdate refUpdate,
+        Account account) {
+      doRefUpdatedHook(refName, refUpdate.getOldObjectId(),
+          refUpdate.getNewObjectId(), account);
     }
 
     @Override
-    public void doRefUpdatedHook(final Branch.NameKey refName, final ObjectId oldId, final ObjectId newId, final Account account) {
-      final RefUpdatedEvent event = new RefUpdatedEvent();
+    public void doRefUpdatedHook(Branch.NameKey refName, ObjectId oldId,
+        ObjectId newId, Account account) {
+      RefUpdatedEvent event = new RefUpdatedEvent();
 
       if (account != null) {
         event.submitter = eventFactory.asAccountAttribute(account);
@@ -574,7 +597,7 @@
       event.refUpdate = eventFactory.asRefUpdateAttribute(oldId, newId, refName);
       fireEvent(refName, event);
 
-      final List<String> args = new ArrayList<>();
+      List<String> args = new ArrayList<>();
       addArg(args, "--oldrev", event.refUpdate.oldRev);
       addArg(args, "--newrev", event.refUpdate.newRev);
       addArg(args, "--refname", event.refUpdate.refName);
@@ -587,17 +610,17 @@
     }
 
     @Override
-    public void doReviewerAddedHook(final Change change, final Account account,
-        final PatchSet patchSet, final ReviewDb db) throws OrmException {
-      final ReviewerAddedEvent event = new ReviewerAddedEvent();
-      final AccountState owner = accountCache.get(change.getOwner());
+    public void doReviewerAddedHook(Change change, Account account,
+        PatchSet patchSet, ReviewDb db) throws OrmException {
+      ReviewerAddedEvent event = new ReviewerAddedEvent();
+      AccountState owner = accountCache.get(change.getOwner());
 
       event.change = eventFactory.asChangeAttribute(change);
       event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
       event.reviewer = eventFactory.asAccountAttribute(account);
       fireEvent(change, event, db);
 
-      final List<String> args = new ArrayList<>();
+      List<String> args = new ArrayList<>();
       addArg(args, "--change", event.change.id);
       addArg(args, "--change-url", event.change.url);
       addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
@@ -609,18 +632,18 @@
     }
 
     @Override
-    public void doTopicChangedHook(final Change change, final Account account,
-        final String oldTopic, final ReviewDb db)
+    public void doTopicChangedHook(Change change, Account account,
+        String oldTopic, ReviewDb db)
             throws OrmException {
-      final TopicChangedEvent event = new TopicChangedEvent();
-      final AccountState owner = accountCache.get(change.getOwner());
+      TopicChangedEvent event = new TopicChangedEvent();
+      AccountState owner = accountCache.get(change.getOwner());
 
       event.change = eventFactory.asChangeAttribute(change);
       event.changer = eventFactory.asAccountAttribute(account);
       event.oldTopic = oldTopic;
       fireEvent(change, event, db);
 
-      final List<String> args = new ArrayList<>();
+      List<String> args = new ArrayList<>();
       addArg(args, "--change", event.change.id);
       addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
       addArg(args, "--project", event.change.project);
@@ -655,7 +678,7 @@
 
       fireEvent(change, event, db);
 
-      final List<String> args = new ArrayList<>();
+      List<String> args = new ArrayList<>();
       addArg(args, "--change", event.change.id);
       addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
       addArg(args, "--project", event.change.project);
@@ -682,7 +705,7 @@
     @Override
     public void doClaSignupHook(Account account, ContributorAgreement cla) {
       if (account != null) {
-        final List<String> args = new ArrayList<>();
+        List<String> args = new ArrayList<>();
         addArg(args, "--submitter", getDisplayName(account));
         addArg(args, "--user-id", account.getId().toString());
         addArg(args, "--cla-name", cla.getName());
@@ -692,60 +715,85 @@
     }
 
     @Override
-    public void postEvent(final Change change, final Event event,
-        final ReviewDb db) throws OrmException {
+    public void postEvent(Change change, com.google.gerrit.server.events.Event event,
+        ReviewDb db) throws OrmException {
       fireEvent(change, event, db);
     }
 
     @Override
-    public void postEvent(final Branch.NameKey branchName,
-        final Event event) {
+    public void postEvent(Branch.NameKey branchName, com.google.gerrit.server.events.Event event) {
       fireEvent(branchName, event);
     }
 
-    private void fireEventForUnrestrictedListeners(final Event event) {
+    private void fireEventForUnrestrictedListeners(com.google.gerrit.server.events.Event event) {
       for (EventListener listener : unrestrictedListeners) {
-          listener.onEvent(event);
+        listener.onEvent(event);
       }
     }
 
-    private void fireEvent(final Change change, final Event event,
-        final ReviewDb db) throws OrmException {
+    private void fireEvent(Change change, com.google.gerrit.server.events.Event event,
+        ReviewDb db) throws OrmException {
       for (EventListenerHolder holder : listeners.values()) {
-          if (isVisibleTo(change, holder.user, db)) {
-              holder.listener.onEvent(event);
-          }
-      }
-
-      fireEventForUnrestrictedListeners( event );
-    }
-
-    private void fireEvent(Branch.NameKey branchName, final Event event) {
-      for (EventListenerHolder holder : listeners.values()) {
-          if (isVisibleTo(branchName, holder.user)) {
-              holder.listener.onEvent(event);
-          }
-      }
-
-      fireEventForUnrestrictedListeners( event );
-    }
-
-    private boolean isVisibleTo(Change change, CurrentUser user, ReviewDb db) throws OrmException {
-        final ProjectState pe = projectCache.get(change.getProject());
-        if (pe == null) {
-          return false;
+        if (isVisibleTo(change, holder.user, db)) {
+          holder.listener.onEvent(event);
         }
-        final ProjectControl pc = pe.controlFor(user);
-        return pc.controlFor(change).isVisible(db);
+      }
+
+      fireEventForUnrestrictedListeners( event );
+    }
+
+    private void fireEvent(Project.NameKey project, ProjectCreatedEvent event) {
+      for (EventListenerHolder holder : listeners.values()) {
+        if (isVisibleTo(project, event, holder.user)) {
+          holder.listener.onEvent(event);
+        }
+      }
+
+      fireEventForUnrestrictedListeners(event);
+    }
+
+    private void fireEventForUnrestrictedListeners(ProjectCreatedEvent event) {
+      for (EventListener listener : unrestrictedListeners) {
+        listener.onEvent(event);
+      }
+    }
+
+    private boolean isVisibleTo(Project.NameKey project, ProjectCreatedEvent event, CurrentUser user) {
+      ProjectState pe = projectCache.get(project);
+      if (pe == null) {
+        return false;
+      }
+      ProjectControl pc = pe.controlFor(user);
+      return pc.controlForRef(event.getHeadName()).isVisible();
+    }
+
+    private void fireEvent(Branch.NameKey branchName, com.google.gerrit.server.events.Event event) {
+      for (EventListenerHolder holder : listeners.values()) {
+        if (isVisibleTo(branchName, holder.user)) {
+          holder.listener.onEvent(event);
+        }
+      }
+
+      fireEventForUnrestrictedListeners(event);
+    }
+
+    private boolean isVisibleTo(Change change, CurrentUser user, ReviewDb db)
+        throws OrmException {
+      ProjectState pe = projectCache.get(change.getProject());
+      if (pe == null) {
+        return false;
+      }
+      ProjectControl pc = pe.controlFor(user);
+      return pc.controlFor(change).isVisible(db);
     }
 
     private boolean isVisibleTo(Branch.NameKey branchName, CurrentUser user) {
-        final ProjectState pe = projectCache.get(branchName.getParentKey());
-        if (pe == null) {
-          return false;
-        }
-        final ProjectControl pc = pe.controlFor(user);
-        return pc.controlForRef(branchName).isVisible();
+      ProjectState pe = projectCache.get(branchName.getParentKey());
+      if (pe == null) {
+        return false;
+      }
+      ProjectControl pc = pe.controlFor(user);
+      return pc.controlForRef(branchName).isVisible();
     }
 
     /**
@@ -755,14 +803,14 @@
      */
     private ApprovalAttribute getApprovalAttribute(LabelTypes labelTypes,
             Entry<String, Short> approval) {
-        ApprovalAttribute a = new ApprovalAttribute();
-        a.type = approval.getKey();
-        LabelType lt = labelTypes.byLabel(approval.getKey());
-        if (lt != null) {
-          a.description = lt.getName();
-        }
-        a.value = Short.toString(approval.getValue());
-        return a;
+      ApprovalAttribute a = new ApprovalAttribute();
+      a.type = approval.getKey();
+      LabelType lt = labelTypes.byLabel(approval.getKey());
+      if (lt != null) {
+        a.description = lt.getName();
+      }
+      a.value = Short.toString(approval.getValue());
+      return a;
     }
 
     /**
@@ -771,16 +819,18 @@
      * @param account Account to get name for.
      * @return Name for this account.
      */
-    private String getDisplayName(final Account account) {
-        if (account != null) {
-            String result = (account.getFullName() == null) ? anonymousCowardName : account.getFullName();
-            if (account.getPreferredEmail() != null) {
-                result += " (" + account.getPreferredEmail() + ")";
-            }
-            return result;
+    private String getDisplayName(Account account) {
+      if (account != null) {
+        String result = (account.getFullName() == null)
+            ? anonymousCowardName
+            : account.getFullName();
+        if (account.getPreferredEmail() != null) {
+          result += " (" + account.getPreferredEmail() + ")";
         }
+        return result;
+      }
 
-        return anonymousCowardName;
+      return anonymousCowardName;
     }
 
   /**
@@ -871,18 +921,18 @@
       HookResult result = null;
       try {
 
-        final List<String> argv = new ArrayList<>(1 + args.size());
+        List<String> argv = new ArrayList<>(1 + args.size());
         argv.add(hook.toAbsolutePath().toString());
         argv.addAll(args);
 
-        final ProcessBuilder pb = new ProcessBuilder(argv);
+        ProcessBuilder pb = new ProcessBuilder(argv);
         pb.redirectErrorStream(true);
 
         if (project != null) {
           repo = openRepository(project);
         }
 
-        final Map<String, String> env = pb.environment();
+        Map<String, String> env = pb.environment();
         env.put("GERRIT_SITE", sitePaths.site_path.toAbsolutePath().toString());
 
         if (repo != null) {
@@ -916,7 +966,7 @@
       }
 
       if (result != null) {
-        final int exitValue = result.getExitValue();
+        int exitValue = result.getExitValue();
         if (exitValue == 0) {
           log.debug("hook[" + getName() + "] exitValue:" + exitValue);
         } else {
@@ -990,4 +1040,10 @@
       super.runHook();
     }
   }
+
+  @Override
+  public void onNewProjectCreated(NewProjectCreatedListener.Event event) {
+    Project.NameKey project = new Project.NameKey(event.getProjectName());
+    doProjectCreatedHook(project, event.getHeadName());
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
index 7f7e8b2..b16a8a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
@@ -181,4 +181,12 @@
   public void doHashtagsChangedHook(Change change, Account account,
       Set<String>added, Set<String> removed, Set<String> hashtags,
       ReviewDb db) throws OrmException;
+
+  /**
+   * Fire the project created hook
+   *
+   * @param project The project that was created
+   * @param headName The head name of the created project
+   */
+  public void doProjectCreatedHook(Project.NameKey project, String headName);
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
index 156672e..bed77a7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
@@ -114,6 +114,10 @@
   }
 
   @Override
+  public void doProjectCreatedHook(Project.NameKey project, String headName) {
+  }
+
+  @Override
   public void postEvent(Change change, Event event, ReviewDb db) {
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index d7bb132..f10837f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -317,7 +317,7 @@
       ins.setMessage(cmsg).insert();
 
       try {
-        RevertedSender cm = revertedSenderFactory.create(change);
+        RevertedSender cm = revertedSenderFactory.create(change.getId());
         cm.setFrom(user().getAccountId());
         cm.setChangeMessage(cmsg);
         cm.send();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 57db303..7441a60 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -48,10 +48,10 @@
 import com.google.gerrit.server.change.PostReview;
 import com.google.gerrit.server.change.PublishDraftPatchSet;
 import com.google.gerrit.server.change.Rebase;
+import com.google.gerrit.server.change.RebaseChange;
 import com.google.gerrit.server.change.Reviewed;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.change.Submit;
-import com.google.gerrit.server.changedetail.RebaseChange;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index d0424d9..a73f8ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.base.Strings;
-import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.extensions.api.changes.AbandonInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -123,17 +122,15 @@
     }
     update.commit();
 
-    CheckedFuture<?, IOException> indexFuture =
-        indexer.indexAsync(change.getId());
+    indexer.index(db, change);
     try {
-      ReplyToChangeSender cm = abandonedSenderFactory.create(change);
+      ReplyToChangeSender cm = abandonedSenderFactory.create(change.getId());
       cm.setFrom(caller.getAccountId());
       cm.setChangeMessage(message);
       cm.send();
     } catch (Exception e) {
       log.error("Cannot email update for change " + change.getChangeId(), e);
     }
-    indexFuture.checkedGet();
     hooks.doChangeAbandonedHook(change,
         caller.getAccount(),
         db.patchSets().get(change.currentPatchSetId()),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
index 023173e..180b9ad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -36,13 +36,16 @@
 public class ActionJson {
   private final Revisions revisions;
   private final DynamicMap<RestView<ChangeResource>> changeViews;
+  private final RebaseChange rebaseChange;
 
   @Inject
   ActionJson(
       Revisions revisions,
-      DynamicMap<RestView<ChangeResource>> changeViews) {
+      DynamicMap<RestView<ChangeResource>> changeViews,
+      RebaseChange rebaseChange) {
     this.revisions = revisions;
     this.changeViews = changeViews;
+    this.rebaseChange = rebaseChange;
   }
 
   public Map<String, ActionInfo> format(RevisionResource rsrc) {
@@ -69,7 +72,7 @@
     Provider<CurrentUser> userProvider = Providers.of(ctl.getCurrentUser());
     for (UiAction.Description d : UiActions.from(
         changeViews,
-        new ChangeResource(ctl),
+        new ChangeResource(ctl, rebaseChange),
         userProvider)) {
       out.put(d.getId(), new ActionInfo(d));
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index ff833f4..9bd6697 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -233,10 +233,10 @@
     }
 
     CheckedFuture<?, IOException> f = indexer.indexAsync(change.getId());
-
     if (!messageIsForChange()) {
       commitMessageNotForChange();
     }
+    f.checkedGet();
 
     if (sendMail) {
       Runnable sender = new Runnable() {
@@ -244,7 +244,7 @@
         public void run() {
           try {
             CreateChangeSender cm =
-                createChangeSenderFactory.create(change);
+                createChangeSenderFactory.create(change.getId());
             cm.setFrom(change.getOwner());
             cm.setPatchSet(patchSet, patchSetInfo);
             cm.addReviewers(reviewers);
@@ -266,7 +266,6 @@
         sender.run();
       }
     }
-    f.checkedGet();
 
     gitRefUpdated.fire(change.getProject(), patchSet.getRefName(),
         ObjectId.zeroId(), commit);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 2accdb8..6753582 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -142,6 +142,7 @@
   private final PatchLineCommentsUtil plcUtil;
   private final Provider<ConsistencyChecker> checkerProvider;
   private final ActionJson actionJson;
+  private final RebaseChange rebaseChange;
 
   private AccountLoader accountLoader;
   private FixInput fix;
@@ -163,7 +164,8 @@
       ChangeMessagesUtil cmUtil,
       PatchLineCommentsUtil plcUtil,
       Provider<ConsistencyChecker> checkerProvider,
-      ActionJson actionJson) {
+      ActionJson actionJson,
+      RebaseChange rebaseChange) {
     this.db = db;
     this.labelNormalizer = ln;
     this.userProvider = user;
@@ -180,6 +182,7 @@
     this.plcUtil = plcUtil;
     this.checkerProvider = checkerProvider;
     this.actionJson = actionJson;
+    this.rebaseChange = rebaseChange;
     options = EnumSet.noneOf(ListChangesOption.class);
   }
 
@@ -890,7 +893,7 @@
         && userProvider.get().isIdentifiedUser()) {
 
       actionJson.addRevisionActions(out,
-          new RevisionResource(new ChangeResource(ctl), in));
+          new RevisionResource(new ChangeResource(ctl, rebaseChange), in));
     }
 
     if (has(DRAFT_COMMENTS)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
index 1555cdd..265cb49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
@@ -36,13 +36,16 @@
       new TypeLiteral<RestView<ChangeResource>>() {};
 
   private final ChangeControl control;
+  private final RebaseChange rebaseChange;
 
-  public ChangeResource(ChangeControl control) {
+  public ChangeResource(ChangeControl control, RebaseChange rebaseChange) {
     this.control = control;
+    this.rebaseChange = rebaseChange;
   }
 
   protected ChangeResource(ChangeResource copy) {
     this.control = copy.control;
+    this.rebaseChange = copy.rebaseChange;
   }
 
   public ChangeControl getControl() {
@@ -65,7 +68,8 @@
       .putInt(getChange().getRowVersion())
       .putInt(user.isIdentifiedUser()
           ? ((IdentifiedUser) user).getAccountId().get()
-          : 0);
+          : 0)
+      .putBoolean(rebaseChange != null && rebaseChange.canRebase(this));
     byte[] buf = new byte[20];
     ObjectId noteId;
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
index 1648a5d..6540ef2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -49,6 +49,7 @@
   private final ChangeUtil changeUtil;
   private final CreateChange createChange;
   private final ChangeIndexer changeIndexer;
+  private final RebaseChange rebaseChange;
 
   @Inject
   ChangesCollection(
@@ -58,7 +59,8 @@
       DynamicMap<RestView<ChangeResource>> views,
       ChangeUtil changeUtil,
       CreateChange createChange,
-      ChangeIndexer changeIndexer) {
+      ChangeIndexer changeIndexer,
+      RebaseChange rebaseChange) {
     this.user = user;
     this.changeControlFactory = changeControlFactory;
     this.queryFactory = queryFactory;
@@ -66,6 +68,7 @@
     this.changeUtil = changeUtil;
     this.createChange = createChange;
     this.changeIndexer = changeIndexer;
+    this.rebaseChange = rebaseChange;
   }
 
   @Override
@@ -102,7 +105,7 @@
     } catch (NoSuchChangeException e) {
       throw new ResourceNotFoundException(id);
     }
-    return new ChangeResource(control);
+    return new ChangeResource(control, rebaseChange);
   }
 
   public ChangeResource parse(Change.Id id)
@@ -112,7 +115,7 @@
   }
 
   public ChangeResource parse(ChangeControl control) {
-    return new ChangeResource(control);
+    return new ChangeResource(control, rebaseChange);
   }
 
   @SuppressWarnings("unchecked")
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
index 4dffd67..7320c7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
@@ -33,6 +33,7 @@
 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.ChangeUtil;
 import com.google.gerrit.server.CurrentUser;
@@ -55,7 +56,6 @@
 
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -140,11 +140,7 @@
       }
     }
 
-    String refName = input.branch;
-    if (!refName.startsWith(Constants.R_REFS)) {
-      refName = Constants.R_HEADS + input.branch;
-    }
-
+    String refName = RefNames.fullName(input.branch);
     ProjectResource rsrc = projectsCollection.parse(input.project);
 
     Capable r = rsrc.getControl().canPushToAtLeastOneRef();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
index 6330e34..d75b6c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -129,7 +129,7 @@
         }
       });
 
-      CommentSender cm = commentSenderFactory.create(notify, change);
+      CommentSender cm = commentSenderFactory.create(notify, change.getId());
       cm.setFrom(authorId);
       cm.setPatchSet(patchSet, patchSetInfoFactory.get(change, patchSet));
       cm.setChangeMessage(message);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
index cd57a3e..3ae6f6c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -39,14 +39,17 @@
   private final ActionJson delegate;
   private final Provider<InternalChangeQuery> queryProvider;
   private final Config config;
+  private final RebaseChange rebaseChange;
   @Inject
   GetRevisionActions(
       ActionJson delegate,
       Provider<InternalChangeQuery> queryProvider,
-      @GerritServerConfig Config config) {
+      @GerritServerConfig Config config,
+      RebaseChange rebaseChange) {
     this.delegate = delegate;
     this.queryProvider = queryProvider;
     this.config = config;
+    this.rebaseChange = rebaseChange;
   }
 
   @Override
@@ -65,7 +68,7 @@
     CurrentUser user = rsrc.getControl().getCurrentUser();
     try {
       for (ChangeData c : queryProvider.get().byTopicOpen(topic)) {
-        new ChangeResource(c.changeControl()).prepareETag(h, user);
+        new ChangeResource(c.changeControl(), rebaseChange).prepareETag(h, user);
       }
     } catch (OrmException e){
       throw new OrmRuntimeException(e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index baddd40..6716162 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -293,7 +293,7 @@
         try {
           PatchSetInfo info = patchSetInfoFactory.get(commit, patchSet.getId());
           ReplacePatchSetSender cm =
-              replacePatchSetFactory.create(updatedChange);
+              replacePatchSetFactory.create(c.getId());
           cm.setFrom(user.getAccountId());
           cm.setPatchSet(patchSet, info);
           cm.setChangeMessage(changeMessage);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 8e989b6..436c5e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -22,8 +22,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.CheckedFuture;
-import com.google.common.util.concurrent.Futures;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelType;
@@ -180,11 +178,8 @@
       db.get().rollback();
     }
 
-    CheckedFuture<?, IOException> indexWrite;
     if (dirty) {
-      indexWrite = indexer.indexAsync(change.getId());
-    } else {
-      indexWrite = Futures.<Void, IOException> immediateCheckedFuture(null);
+      indexer.index(db.get(), change);
     }
     if (message != null && input.notify.compareTo(NotifyHandling.NONE) > 0) {
       email.create(
@@ -198,7 +193,6 @@
 
     Output output = new Output();
     output.labels = input.labels;
-    indexWrite.checkedGet();
     if (message != null) {
       fireCommentAddedHook(revision);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index ecfb43c..fc27010 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -255,8 +255,8 @@
           ImmutableList.of(psa)));
     }
     accountLoaderFactory.create(true).fill(result.reviewers);
-    emailReviewers(rsrc.getChange(), added);
     indexFuture.checkedGet();
+    emailReviewers(rsrc.getChange(), added);
     if (!added.isEmpty()) {
       PatchSet patchSet = dbProvider.get().patchSets().get(rsrc.getChange().currentPatchSetId());
       for (PatchSetApproval psa : added) {
@@ -283,7 +283,7 @@
     }
     if (!toMail.isEmpty()) {
       try {
-        AddReviewerSender cm = addReviewerSenderFactory.create(change);
+        AddReviewerSender cm = addReviewerSenderFactory.create(change.getId());
         cm.setFrom(identifiedUser.getAccountId());
         cm.addReviewers(toMail);
         cm.send();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
index 9f77f0e..dd9a44d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -82,13 +81,11 @@
 
     if (!updatedPatchSet.isDraft()
         || updatedChange.getStatus() == Change.Status.NEW) {
-      CheckedFuture<?, IOException> indexFuture =
-          indexer.indexAsync(updatedChange.getId());
+      indexer.index(dbProvider.get(), updatedChange);
       sender.send(rsrc.getNotes(), update,
           rsrc.getChange().getStatus() == Change.Status.DRAFT,
           rsrc.getUser(), updatedChange, updatedPatchSet,
           rsrc.getControl().getLabelTypes());
-      indexFuture.checkedGet();
       hooks.doDraftPublishedHook(updatedChange, updatedPatchSet,
           dbProvider.get());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index f2bda5b8..1e9bf2a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -29,7 +29,6 @@
 import com.google.gerrit.reviewdb.client.PatchSetAncestor;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.changedetail.RebaseChange;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -213,13 +212,19 @@
 
   @Override
   public UiAction.Description getDescription(RevisionResource resource) {
-    return new UiAction.Description()
+    UiAction.Description descr = new UiAction.Description()
       .setLabel("Rebase")
       .setTitle("Rebase onto tip of branch or parent change")
       .setVisible(resource.getChange().getStatus().isOpen()
           && resource.isCurrent()
           && resource.getControl().canRebase()
           && hasOneParent(resource.getPatchSet().getId()));
+    if (descr.isVisible()) {
+      // Disable the rebase button in the RebaseDialog if
+      // the change cannot be rebased.
+      descr.setEnabled(rebaseChange.get().canRebase(resource));
+    }
+    return descr;
   }
 
   public static class CurrentRevision implements
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java
similarity index 96%
rename from gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java
index 92f3c00..5286a4a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChange.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.changedetail;
+package com.google.gerrit.server.change;
 
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.errors.EmailException;
@@ -22,14 +22,13 @@
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetAncestor;
+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.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
-import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeConflictException;
 import com.google.gerrit.server.git.MergeUtil;
@@ -356,10 +355,21 @@
     return objectId;
   }
 
+  public boolean canRebase(ChangeResource r) {
+    Change c = r.getChange();
+    return canRebase(c.getProject(), c.currentPatchSetId(), c.getDest());
+  }
+
   public boolean canRebase(RevisionResource r) {
+    return canRebase(r.getChange().getProject(),
+        r.getPatchSet().getId(), r.getChange().getDest());
+  }
+
+  public boolean canRebase(Project.NameKey project,
+      PatchSet.Id patchSetId, Branch.NameKey branch) {
     Repository git;
     try {
-      git = gitManager.openRepository(r.getChange().getProject());
+      git = gitManager.openRepository(project);
     } catch (RepositoryNotFoundException err) {
       return false;
     } catch (IOException err) {
@@ -367,9 +377,9 @@
     }
     try {
       findBaseRevision(
-          r.getPatchSet().getId(),
+          patchSetId,
           db.get(),
-          r.getChange().getDest(),
+          branch,
           git,
           null,
           null,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index cce362a..46d73d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.base.Strings;
-import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.extensions.api.changes.RestoreInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -122,17 +121,16 @@
     }
     update.commit();
 
-    CheckedFuture<?, IOException> f = indexer.indexAsync(change.getId());
+    indexer.index(db, change);
 
     try {
-      ReplyToChangeSender cm = restoredSenderFactory.create(change);
+      ReplyToChangeSender cm = restoredSenderFactory.create(change.getId());
       cm.setFrom(caller.getAccountId());
       cm.setChangeMessage(message);
       cm.send();
     } catch (Exception e) {
       log.error("Cannot email update for change " + change.getChangeId(), e);
     }
-    f.checkedGet();
     hooks.doChangeRestoredHook(change,
         caller.getAccount(),
         db.patchSets().get(change.currentPatchSetId()),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index cbff3f0..6e4e2ed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -72,7 +72,6 @@
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.ChangeMergeQueue;
-import com.google.gerrit.server.git.GarbageCollection;
 import com.google.gerrit.server.git.GitModule;
 import com.google.gerrit.server.git.MergeQueue;
 import com.google.gerrit.server.git.MergeUtil;
@@ -124,6 +123,7 @@
 import com.google.gerrit.server.ssh.SshAddressesModule;
 import com.google.gerrit.server.tools.ToolsCatalog;
 import com.google.gerrit.server.util.IdGenerator;
+import com.google.gerrit.server.util.SubmoduleSectionParser;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.validators.GroupCreationValidationListener;
 import com.google.gerrit.server.validators.HashtagValidationListener;
@@ -200,7 +200,6 @@
     factory(ProjectState.Factory.class);
     factory(RegisterNewEmailSender.Factory.class);
     factory(ReplacePatchSetSender.Factory.class);
-    factory(GarbageCollection.Factory.class);
     bind(PermissionCollection.Factory.class);
     bind(AccountVisibility.class)
         .toProvider(AccountVisibilityProvider.class)
@@ -296,6 +295,7 @@
     factory(MergeValidators.Factory.class);
     factory(ProjectConfigValidator.Factory.class);
     factory(NotesBranchUtil.Factory.class);
+    factory(SubmoduleSectionParser.Factory.class);
 
     bind(AccountManager.class);
     bind(ChangeUserName.CurrentUser.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java
index f646ea5..b7bb360 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEdit.java
@@ -18,6 +18,7 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.server.IdentifiedUser;
 
@@ -71,7 +72,7 @@
   }
 
   public String getRefName() {
-    return ChangeEditUtil.editRefName(user.getAccountId(), change.getId(),
+    return RefNames.refsEdit(user.getAccountId(), change.getId(),
         basePatchSet.getId());
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index 7aa70db..70107c2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -16,8 +16,6 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.edit.ChangeEditUtil.editRefName;
-import static com.google.gerrit.server.edit.ChangeEditUtil.editRefPrefix;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
 import com.google.common.base.Strings;
@@ -30,6 +28,7 @@
 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.CurrentUser;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
@@ -116,7 +115,7 @@
     }
 
     IdentifiedUser me = (IdentifiedUser) currentUser.get();
-    String refPrefix = editRefPrefix(me.getAccountId(), change.getId());
+    String refPrefix = RefNames.refsEditPrefix(me.getAccountId(), change.getId());
 
     try (Repository repo = gitManager.openRepository(change.getProject())) {
       Map<String, Ref> refs = repo.getRefDatabase().getRefs(refPrefix);
@@ -126,7 +125,7 @@
 
       try (RevWalk rw = new RevWalk(repo)) {
         ObjectId revision = ObjectId.fromString(ps.getRevision().get());
-        String editRefName = editRefName(me.getAccountId(), change.getId(),
+        String editRefName = RefNames.refsEdit(me.getAccountId(), change.getId(),
             ps.getId());
         return update(repo, me, editRefName, rw, ObjectId.zeroId(), revision);
       }
@@ -152,7 +151,7 @@
 
     Change change = edit.getChange();
     IdentifiedUser me = (IdentifiedUser) currentUser.get();
-    String refName = editRefName(me.getAccountId(), change.getId(),
+    String refName = RefNames.refsEdit(me.getAccountId(), change.getId(),
         current.getId());
     try (Repository repo = gitManager.openRepository(change.getProject());
         RevWalk rw = new RevWalk(repo);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index f24340f..79c6593 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -110,7 +109,7 @@
   public Optional<ChangeEdit> byChange(Change change, IdentifiedUser user)
       throws IOException {
     try (Repository repo = gitManager.openRepository(change.getProject())) {
-      String editRefPrefix = editRefPrefix(user.getAccountId(), change.getId());
+      String editRefPrefix = RefNames.refsEditPrefix(user.getAccountId(), change.getId());
       Map<String, Ref> refs = repo.getRefDatabase().getRefs(editRefPrefix);
       if (refs.isEmpty()) {
         return Optional.absent();
@@ -190,34 +189,6 @@
     }
   }
 
-  /**
-   * Returns reference for this change edit with sharded user and change number:
-   * refs/users/UU/UUUU/edit-CCCC/P.
-   *
-   * @param accountId accout id
-   * @param changeId change number
-   * @param psId patch set number
-   * @return reference for this change edit
-   */
-  public static String editRefName(Account.Id accountId, Change.Id changeId,
-      PatchSet.Id psId) {
-    return editRefPrefix(accountId, changeId) + psId.get();
-  }
-
-  /**
-   * Returns reference prefix for this change edit with sharded user and
-   * change number: refs/users/UU/UUUU/edit-CCCC/.
-   *
-   * @param accountId accout id
-   * @param changeId change number
-   * @return reference prefix for this change edit
-   */
-  static String editRefPrefix(Account.Id accountId, Change.Id changeId) {
-    return String.format("%s/edit-%d/",
-        RefNames.refsUsers(accountId),
-        changeId.get());
-  }
-
   private RevCommit squashEdit(RevWalk rw, ObjectInserter inserter,
       RevCommit edit, PatchSet basePatchSet)
       throws IOException, ResourceConflictException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java
index 4d26e13..9b37c38 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java
@@ -35,20 +35,16 @@
     registerClass(new ReviewerAddedEvent());
     registerClass(new PatchSetCreatedEvent());
     registerClass(new TopicChangedEvent());
+    registerClass(new ProjectCreatedEvent());
   }
 
   /** Register an event.
    *
    *  @param event The event to register.
-   *  @throws IllegalArgumentException if the event's type is already
    *  registered.
    **/
   public static void registerClass(Event event) {
     String type = event.getType();
-    if (typesByString.containsKey(type)) {
-      throw new IllegalArgumentException(
-          "Event type already registered: " + type);
-    }
     typesByString.put(type, event.getClass());
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ProjectCreatedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ProjectCreatedEvent.java
new file mode 100644
index 0000000..c1534df
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ProjectCreatedEvent.java
@@ -0,0 +1,35 @@
+// 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.events;
+
+import com.google.gerrit.reviewdb.client.Project;
+
+public class ProjectCreatedEvent extends ProjectEvent {
+  public String projectName;
+  public String headName;
+
+  public ProjectCreatedEvent() {
+    super("project-created");
+  }
+
+  @Override
+  public Project.NameKey getProjectNameKey() {
+    return new Project.NameKey(projectName);
+  }
+
+  public String getHeadName() {
+    return headName;
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionLogFile.java
similarity index 72%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionLogFile.java
index 80975a6..0f7599a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionLogFile.java
@@ -12,39 +12,38 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.pgm.util;
+package com.google.gerrit.server.git;
 
 import com.google.gerrit.common.FileUtil;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GarbageCollection;
 import com.google.gerrit.server.util.SystemLog;
+import com.google.inject.Inject;
 
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.apache.log4j.PatternLayout;
 
-import java.io.IOException;
 import java.nio.file.Path;
 
-public class GarbageCollectionLogFile {
-  public static LifecycleListener start(Path sitePath) throws IOException {
-    Path logdir = FileUtil.mkdirsOrDie(new SitePaths(sitePath).logs_dir,
+public class GarbageCollectionLogFile implements LifecycleListener {
+
+  @Inject
+  public GarbageCollectionLogFile(SitePaths sitePaths) {
+    Path logdir = FileUtil.mkdirsOrDie(sitePaths.logs_dir,
         "Cannot create log directory");
     if (SystemLog.shouldConfigure()) {
       initLogSystem(logdir);
     }
+  }
 
-    return new LifecycleListener() {
-      @Override
-      public void start() {
-      }
+  @Override
+  public void start() {
+  }
 
-      @Override
-      public void stop() {
-        LogManager.getLogger(GarbageCollection.LOG_NAME).removeAllAppenders();
-      }
-    };
+  @Override
+  public void stop() {
+    LogManager.getLogger(GarbageCollection.LOG_NAME).removeAllAppenders();
   }
 
   private static void initLogSystem(Path logdir) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionModule.java
new file mode 100644
index 0000000..aacc738
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionModule.java
@@ -0,0 +1,30 @@
+// 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.git;
+
+import com.google.gerrit.lifecycle.LifecycleModule;
+
+public class GarbageCollectionModule extends LifecycleModule {
+
+  @Override
+  protected void configure() {
+    bind(GarbageCollectionLogFile.class).asEagerSingleton();
+    listener().to(GarbageCollectionLogFile.class);
+
+    bind(GarbageCollectionQueue.class);
+    factory(GarbageCollection.Factory.class);
+    listener().to(GarbageCollectionRunner.Lifecycle.class);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionRunner.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionRunner.java
index 5e3ca31..68d25d9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionRunner.java
@@ -18,12 +18,10 @@
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GcConfig;
 import com.google.gerrit.server.config.ScheduleConfig;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
-import com.google.inject.Module;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,16 +33,6 @@
   private static final Logger gcLog = LoggerFactory
       .getLogger(GarbageCollection.LOG_NAME);
 
-  public static Module module() {
-    return new LifecycleModule() {
-
-      @Override
-      protected void configure() {
-        listener().to(Lifecycle.class);
-      }
-    };
-  }
-
   static class Lifecycle implements LifecycleListener {
     private final WorkQueue queue;
     private final GarbageCollectionRunner gcRunner;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 06819d5..96207fa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -24,7 +24,6 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.TimeUtil;
@@ -1045,7 +1044,7 @@
         }
 
         try {
-          MergedSender cm = mergedSenderFactory.create(changeControl(c));
+          MergedSender cm = mergedSenderFactory.create(c.getId());
           if (from != null) {
             cm.setFrom(from.getAccountId());
           }
@@ -1187,12 +1186,7 @@
       update.commit();
     }
 
-    CheckedFuture<?, IOException> indexFuture;
-    if (change != null) {
-      indexFuture = indexer.indexAsync(change.getId());
-    } else {
-      indexFuture = null;
-    }
+    indexer.index(db, change);
     final PatchSetApproval from = submitter;
     workQueue.getDefaultQueue()
         .submit(requestScopePropagator.wrap(new Runnable() {
@@ -1212,7 +1206,7 @@
         }
 
         try {
-          MergeFailSender cm = mergeFailSenderFactory.create(c);
+          MergeFailSender cm = mergeFailSenderFactory.create(c.getId());
           if (from != null) {
             cm.setFrom(from.getAccountId());
           }
@@ -1230,14 +1224,6 @@
       }
     }));
 
-    if (indexFuture != null) {
-      try {
-        indexFuture.checkedGet();
-      } catch (IOException e) {
-        logError("Failed to index new change message", e);
-      }
-    }
-
     if (submitter != null) {
       try {
         hooks.doMergeFailedHook(c,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 020c94a..99e61d6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -305,6 +305,10 @@
     return notifySections.values();
   }
 
+  public void putNotifyConfig(String name, NotifyConfig nc) {
+    notifySections.put(name, nc);
+  }
+
   public Map<String, LabelType> getLabelSections() {
     return labelSections;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 27930bc..0793d10 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -2007,7 +2007,7 @@
       cmd = new ReceiveCommand(
           ObjectId.zeroId(),
           newCommit,
-          ChangeEditUtil.editRefName(
+          RefNames.refsEdit(
               currentUser.getAccountId(),
               change.getId(),
               newPatchSet.getId()));
@@ -2206,7 +2206,7 @@
       if (cmd.getResult() == NOT_ATTEMPTED) {
         cmd.execute(rp);
       }
-      CheckedFuture<?, IOException> f = indexer.indexAsync(change.getId());
+      indexer.index(db, change);
       if (changeKind != ChangeKind.TRIVIAL_REBASE) {
         workQueue.getDefaultQueue()
             .submit(requestScopePropagator.wrap(new Runnable() {
@@ -2214,7 +2214,7 @@
           public void run() {
             try {
               ReplacePatchSetSender cm =
-                  replacePatchSetFactory.create(change);
+                  replacePatchSetFactory.create(change.getId());
               cm.setFrom(me);
               cm.setPatchSet(newPatchSet, info);
               cm.setChangeMessage(msg);
@@ -2235,7 +2235,6 @@
           }
         }));
       }
-      f.checkedGet();
 
       gitRefUpdated.fire(project.getNameKey(), newPatchSet.getRefName(),
           ObjectId.zeroId(), newCommit);
@@ -2594,12 +2593,13 @@
   }
 
   private void sendMergedEmail(final ReplaceRequest result) {
+    final Change.Id id = result.change.getId();
     workQueue.getDefaultQueue()
         .submit(requestScopePropagator.wrap(new Runnable() {
       @Override
       public void run() {
         try {
-          final MergedSender cm = mergedSenderFactory.create(result.changeCtl);
+          final MergedSender cm = mergedSenderFactory.create(id);
           cm.setFrom(currentUser.getAccountId());
           cm.setPatchSet(result.newPatchSet, result.info);
           cm.send();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index 3cf9fed..f05a1d4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -93,17 +93,25 @@
   private final Set<Branch.NameKey> updatedSubscribers;
   private final Account account;
   private final ChangeHooks changeHooks;
+  private final SubmoduleSectionParser.Factory subSecParserFactory;
 
   @Inject
-  public SubmoduleOp(@Assisted final Branch.NameKey destBranch,
-      @Assisted RevCommit mergeTip, @Assisted RevWalk rw,
-      @CanonicalWebUrl @Nullable final Provider<String> urlProvider,
-      final SchemaFactory<ReviewDb> sf, @Assisted Repository db,
-      @Assisted Project destProject, @Assisted List<Change> submitted,
-      @Assisted final Map<Change.Id, CodeReviewCommit> commits,
-      @GerritPersonIdent final PersonIdent myIdent,
-      GitRepositoryManager repoManager, GitReferenceUpdated gitRefUpdated,
-      @Nullable @Assisted Account account, ChangeHooks changeHooks) {
+  public SubmoduleOp(@Assisted Branch.NameKey destBranch,
+      @Assisted RevCommit mergeTip,
+      @Assisted RevWalk rw,
+      @CanonicalWebUrl @Nullable Provider<String> urlProvider,
+      SchemaFactory<ReviewDb> sf,
+      @Assisted Repository db,
+      @Assisted Project destProject,
+      @Assisted List<Change> submitted,
+      @Assisted Map<Change.Id,
+      CodeReviewCommit> commits,
+      @GerritPersonIdent PersonIdent myIdent,
+      GitRepositoryManager repoManager,
+      GitReferenceUpdated gitRefUpdated,
+      @Nullable @Assisted Account account,
+      ChangeHooks changeHooks,
+      SubmoduleSectionParser.Factory subSecParserFactory) {
     this.destBranch = destBranch;
     this.mergeTip = mergeTip;
     this.rw = rw;
@@ -118,6 +126,7 @@
     this.gitRefUpdated = gitRefUpdated;
     this.account = account;
     this.changeHooks = changeHooks;
+    this.subSecParserFactory = subSecParserFactory;
 
     updatedSubscribers = new HashSet<>();
   }
@@ -162,8 +171,8 @@
         final Set<SubmoduleSubscription> oldSubscriptions =
             new HashSet<>(schema.submoduleSubscriptions()
                 .bySuperProject(destBranch).toList());
-        final List<SubmoduleSubscription> newSubscriptions =
-            new SubmoduleSectionParser(bbc, thisServer, target, repoManager)
+        List<SubmoduleSubscription> newSubscriptions =
+            subSecParserFactory.create(bbc, thisServer, target)
                 .parseAllSections();
 
         final Set<SubmoduleSubscription> alreadySubscribeds = new HashSet<>();
@@ -279,7 +288,9 @@
     PersonIdent author = null;
 
     final StringBuilder msgbuf = new StringBuilder();
-    msgbuf.append("Updated " + subscriber.getParentKey().get());
+    msgbuf.append("Updated ")
+      .append(subscriber.getParentKey().get())
+      .append('\n');
     Repository pdb = null;
     RevWalk recRw = null;
 
@@ -351,6 +362,7 @@
       commit.setCommitter(myIdent);
       commit.setMessage(msgbuf.toString());
       oi.insert(commit);
+      oi.flush();
 
       ObjectId commitId = oi.idFor(Constants.OBJ_COMMIT, commit.build());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index acd32c7..e6fff19 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -20,7 +20,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
-import com.google.gerrit.server.changedetail.RebaseChange;
+import com.google.gerrit.server.change.RebaseChange;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.CommitMergeStatus;
 import com.google.gerrit.server.git.MergeConflictException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index ac65f8d..ffe351b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -20,7 +20,7 @@
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.changedetail.RebaseChange;
+import com.google.gerrit.server.change.RebaseChange;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.MergeException;
 import com.google.gerrit.server.git.MergeUtil;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index 0040762..43b8c8d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -146,18 +146,26 @@
         }
       };
 
+  @Deprecated
   /** Topic, a short annotation on the branch. */
-  public static final FieldDef<ChangeData, String> TOPIC =
+  public static final FieldDef<ChangeData, String> LEGACY_TOPIC =
       new FieldDef.Single<ChangeData, String>(
           "topic2", FieldType.EXACT, false) {
         @Override
         public String get(ChangeData input, FillArgs args)
             throws OrmException {
-          Change c = input.change();
-          if (c == null) {
-            return null;
-          }
-          return firstNonNull(c.getTopic(), "");
+          return getTopic(input);
+        }
+      };
+
+  /** Topic, a short annotation on the branch. */
+  public static final FieldDef<ChangeData, String> TOPIC =
+      new FieldDef.Single<ChangeData, String>(
+          "topic3", FieldType.PREFIX, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return getTopic(input);
         }
       };
 
@@ -523,6 +531,14 @@
         }
       };
 
+  private static String getTopic(ChangeData input) throws OrmException {
+    Change c = input.change();
+    if (c == null) {
+      return null;
+    }
+    return firstNonNull(c.getTopic(), "");
+  }
+
   private static <T> List<byte[]> toProtos(ProtobufCodec<T> codec, Collection<T> objs)
       throws OrmException {
     List<byte[]> result = Lists.newArrayListWithCapacity(objs.size());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index ef6d626..46c8297 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -38,7 +38,7 @@
         ChangeField.PROJECT,
         ChangeField.PROJECTS,
         ChangeField.REF,
-        ChangeField.TOPIC,
+        ChangeField.LEGACY_TOPIC,
         ChangeField.UPDATED,
         ChangeField.FILE_PART,
         ChangeField.PATH,
@@ -68,7 +68,7 @@
       ChangeField.PROJECT,
       ChangeField.PROJECTS,
       ChangeField.REF,
-      ChangeField.TOPIC,
+      ChangeField.LEGACY_TOPIC,
       ChangeField.UPDATED,
       ChangeField.FILE_PART,
       ChangeField.PATH,
@@ -88,6 +88,7 @@
       ChangeField.DELTA,
       ChangeField.HASHTAG);
 
+  @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V14 = schema(
       ChangeField.LEGACY_ID,
       ChangeField.ID,
@@ -95,7 +96,7 @@
       ChangeField.PROJECT,
       ChangeField.PROJECTS,
       ChangeField.REF,
-      ChangeField.TOPIC,
+      ChangeField.LEGACY_TOPIC,
       ChangeField.UPDATED,
       ChangeField.FILE_PART,
       ChangeField.PATH,
@@ -115,6 +116,7 @@
       ChangeField.DELTA,
       ChangeField.HASHTAG);
 
+  @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V15 = schema(
       ChangeField.LEGACY_ID,
       ChangeField.ID,
@@ -122,6 +124,34 @@
       ChangeField.PROJECT,
       ChangeField.PROJECTS,
       ChangeField.REF,
+      ChangeField.LEGACY_TOPIC,
+      ChangeField.UPDATED,
+      ChangeField.FILE_PART,
+      ChangeField.PATH,
+      ChangeField.OWNER,
+      ChangeField.REVIEWER,
+      ChangeField.COMMIT,
+      ChangeField.TR,
+      ChangeField.LABEL,
+      ChangeField.REVIEWED,
+      ChangeField.COMMIT_MESSAGE,
+      ChangeField.COMMENT,
+      ChangeField.CHANGE,
+      ChangeField.APPROVAL,
+      ChangeField.MERGEABLE,
+      ChangeField.ADDED,
+      ChangeField.DELETED,
+      ChangeField.DELTA,
+      ChangeField.HASHTAG,
+      ChangeField.COMMENTBY);
+
+  static final Schema<ChangeData> V16 = schema(
+      ChangeField.LEGACY_ID,
+      ChangeField.ID,
+      ChangeField.STATUS,
+      ChangeField.PROJECT,
+      ChangeField.PROJECTS,
+      ChangeField.REF,
       ChangeField.TOPIC,
       ChangeField.UPDATED,
       ChangeField.FILE_PART,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
index adcf242..409c155 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -25,12 +26,13 @@
   public static interface Factory extends
       ReplyToChangeSender.Factory<AbandonedSender> {
     @Override
-    AbandonedSender create(Change change);
+    AbandonedSender create(Change.Id change);
   }
 
   @Inject
-  public AbandonedSender(EmailArguments ea, @Assisted Change c) {
-    super(ea, c, "abandon");
+  public AbandonedSender(EmailArguments ea, @Assisted Change.Id id)
+      throws OrmException {
+    super(ea, "abandon", newChangeData(ea, id));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
index c181a9c..7a6d204 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
@@ -16,18 +16,20 @@
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
 /** Asks a user to review a change. */
 public class AddReviewerSender extends NewChangeSender {
   public static interface Factory {
-    AddReviewerSender create(Change change);
+    AddReviewerSender create(Change.Id id);
   }
 
   @Inject
-  public AddReviewerSender(EmailArguments ea, @Assisted Change c) {
-    super(ea, c);
+  public AddReviewerSender(EmailArguments ea, @Assisted Change.Id id)
+      throws OrmException {
+    super(ea, newChangeData(ea, id));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 5642ec1..689c596 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -55,6 +55,10 @@
 public abstract class ChangeEmail extends NotificationEmail {
   private static final Logger log = LoggerFactory.getLogger(ChangeEmail.class);
 
+  protected static ChangeData newChangeData(EmailArguments ea, Change.Id id) {
+    return ea.changeDataFactory.create(ea.db.get(), id);
+  }
+
   protected final Change change;
   protected final ChangeData changeData;
   protected PatchSet patchSet;
@@ -65,10 +69,11 @@
   protected Set<Account.Id> authors;
   protected boolean emailOnlyAuthors;
 
-  protected ChangeEmail(EmailArguments ea, Change c, String mc) {
-    super(ea, mc, c.getProject(), c.getDest());
-    change = c;
-    changeData = ea.changeDataFactory.create(ea.db.get(), c);
+  protected ChangeEmail(EmailArguments ea, String mc, ChangeData cd)
+      throws OrmException {
+    super(ea, mc, cd.change().getDest());
+    changeData = cd;
+    change = cd.change();
     emailOnlyAuthors = false;
   }
 
@@ -305,7 +310,8 @@
 
   @Override
   protected final Watchers getWatchers(NotifyType type) throws OrmException {
-    ProjectWatch watch = new ProjectWatch(args, project, projectState, changeData);
+    ProjectWatch watch = new ProjectWatch(
+        args, branch.getParentKey(), projectState, changeData);
     return watch.getWatchers(type);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index b587791..fc6eadd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -50,7 +50,7 @@
       .getLogger(CommentSender.class);
 
   public static interface Factory {
-    public CommentSender create(NotifyHandling notify, Change change);
+    public CommentSender create(NotifyHandling notify, Change.Id id);
   }
 
   private final NotifyHandling notify;
@@ -59,10 +59,10 @@
 
   @Inject
   public CommentSender(EmailArguments ea,
+      PatchLineCommentsUtil plcUtil,
       @Assisted NotifyHandling notify,
-      @Assisted Change c,
-      PatchLineCommentsUtil plcUtil) {
-    super(ea, c, "comment");
+      @Assisted Change.Id id) throws OrmException {
+    super(ea, "comment", newChangeData(ea, id));
     this.notify = notify;
     this.plcUtil = plcUtil;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
index f570ac8..29895d9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
@@ -33,12 +33,13 @@
       LoggerFactory.getLogger(CreateChangeSender.class);
 
   public static interface Factory {
-    public CreateChangeSender create(Change change);
+    public CreateChangeSender create(Change.Id id);
   }
 
   @Inject
-  public CreateChangeSender(EmailArguments ea, @Assisted Change c) {
-    super(ea, c);
+  public CreateChangeSender(EmailArguments ea, @Assisted Change.Id id)
+      throws OrmException {
+    super(ea, newChangeData(ea, id));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
index 46e151b..592fbb0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.mail;
 
+import com.google.common.base.MoreObjects;
+
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
@@ -23,6 +25,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 
 public abstract class EmailHeader {
   public abstract boolean isEmpty();
@@ -30,7 +33,7 @@
   public abstract void write(Writer w) throws IOException;
 
   public static class String extends EmailHeader {
-    private java.lang.String value;
+    private final java.lang.String value;
 
     public String(java.lang.String v) {
       value = v;
@@ -53,6 +56,22 @@
         w.write(value);
       }
     }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return (o instanceof String)
+          && Objects.equals(value, ((String) o).value);
+    }
+
+    @Override
+    public java.lang.String toString() {
+      return MoreObjects.toStringHelper(this).addValue(value).toString();
+    }
   }
 
   static boolean needsQuotedPrintable(java.lang.String value) {
@@ -113,7 +132,7 @@
   }
 
   public static class Date extends EmailHeader {
-    private java.util.Date value;
+    private final java.util.Date value;
 
     public Date(java.util.Date v) {
       value = v;
@@ -135,6 +154,22 @@
       fmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
       w.write(fmt.format(value));
     }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return (o instanceof Date)
+          && Objects.equals(value, ((Date) o).value);
+    }
+
+    @Override
+    public java.lang.String toString() {
+      return MoreObjects.toStringHelper(this).addValue(value).toString();
+    }
   }
 
   public static class AddressList extends EmailHeader {
@@ -191,5 +226,21 @@
         needComma = true;
       }
     }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(list);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return (o instanceof AddressList)
+          && Objects.equals(list, ((AddressList) o).list);
+    }
+
+    @Override
+    public java.lang.String toString() {
+      return MoreObjects.toStringHelper(this).addValue(list).toString();
+    }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
index 3a5f7eb..ba75723 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergeFailSender.java
@@ -16,18 +16,20 @@
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
 /** Send notice about a change failing to merged. */
 public class MergeFailSender extends ReplyToChangeSender {
   public static interface Factory {
-    public MergeFailSender create(Change change);
+    public MergeFailSender create(Change.Id id);
   }
 
   @Inject
-  public MergeFailSender(EmailArguments ea, @Assisted Change c) {
-    super(ea, c, "merge-failed");
+  public MergeFailSender(EmailArguments ea, @Assisted Change.Id id)
+      throws OrmException {
+    super(ea, "merge-failed", newChangeData(ea, id));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
index 5cb1ba1..7fbcf8d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -22,8 +22,8 @@
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
@@ -31,15 +31,16 @@
 /** Send notice about a change successfully merged. */
 public class MergedSender extends ReplyToChangeSender {
   public static interface Factory {
-    public MergedSender create(ChangeControl change);
+    public MergedSender create(Change.Id id);
   }
 
   private final LabelTypes labelTypes;
 
   @Inject
-  public MergedSender(EmailArguments ea, @Assisted ChangeControl c) {
-    super(ea, c.getChange(), "merged");
-    labelTypes = c.getLabelTypes();
+  public MergedSender(EmailArguments ea, @Assisted Change.Id id)
+      throws OrmException {
+    super(ea, "merged", newChangeData(ea, id));
+    labelTypes = changeData.changeControl().getLabelTypes();
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
index 0dbcbe0..e18c7e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
@@ -16,7 +16,8 @@
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -29,8 +30,9 @@
   private final Set<Account.Id> reviewers = new HashSet<>();
   private final Set<Account.Id> extraCC = new HashSet<>();
 
-  protected NewChangeSender(EmailArguments ea, Change c) {
-    super(ea, c, "newchange");
+  protected NewChangeSender(EmailArguments ea, ChangeData cd)
+      throws OrmException {
+    super(ea, "newchange", cd);
   }
 
   public void addReviewers(final Collection<Account.Id> cc) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java
index f0c43d3..de338ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.mail.ProjectWatch.Watchers;
 import com.google.gwtorm.server.OrmException;
 
@@ -33,14 +32,11 @@
   private static final Logger log =
       LoggerFactory.getLogger(NotificationEmail.class);
 
-  protected Project.NameKey project;
   protected Branch.NameKey branch;
 
   protected NotificationEmail(EmailArguments ea,
-      String mc, Project.NameKey project, Branch.NameKey branch) {
+      String mc, Branch.NameKey branch) {
     super(ea, mc);
-
-    this.project = project;
     this.branch = branch;
   }
 
@@ -104,7 +100,7 @@
   @Override
   protected void setupVelocityContext() {
     super.setupVelocityContext();
-    velocityContext.put("projectName", project.get());
+    velocityContext.put("projectName", branch.getParentKey().get());
     velocityContext.put("branch", branch);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
index 53efa1e..80a5d24 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
@@ -100,7 +100,8 @@
             updatedPatchSet, info, recipients.getReviewers(),
             Collections.<Account.Id> emptySet());
         try {
-          CreateChangeSender cm = createChangeSenderFactory.create(updatedChange);
+          CreateChangeSender cm =
+              createChangeSenderFactory.create(updatedChange.getId());
           cm.setFrom(me);
           cm.setPatchSet(updatedPatchSet, info);
           cm.addReviewers(recipients.getReviewers());
@@ -119,7 +120,8 @@
                 updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
         msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
         try {
-          ReplacePatchSetSender cm = replacePatchSetFactory.create(updatedChange);
+          ReplacePatchSetSender cm =
+              replacePatchSetFactory.create(updatedChange.getId());
           cm.setFrom(me);
           cm.setPatchSet(updatedPatchSet, info);
           cm.setChangeMessage(msg);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
index 63709c6..95b0219 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
@@ -87,10 +87,9 @@
           try {
             add(matching, nc);
           } catch (QueryParseException e) {
-            log.warn(String.format(
-                "Project %s has invalid notify %s filter \"%s\"",
+            log.warn("Project {} has invalid notify {} filter \"{}\": {}",
                 state.getProject().getName(), nc.getName(),
-                nc.getFilter()), e);
+                nc.getFilter(), e.getMessage());
           }
         }
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
index 8412d22..05c7933 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -30,15 +31,16 @@
 /** Send notice of new patch sets for reviewers. */
 public class ReplacePatchSetSender extends ReplyToChangeSender {
   public static interface Factory {
-    public ReplacePatchSetSender create(Change change);
+    public ReplacePatchSetSender create(Change.Id id);
   }
 
   private final Set<Account.Id> reviewers = new HashSet<>();
   private final Set<Account.Id> extraCC = new HashSet<>();
 
   @Inject
-  public ReplacePatchSetSender(EmailArguments ea, @Assisted Change c) {
-    super(ea, c, "newpatchset");
+  public ReplacePatchSetSender(EmailArguments ea, @Assisted Change.Id id)
+      throws OrmException {
+    super(ea, "newpatchset", newChangeData(ea, id));
   }
 
   public void addReviewers(final Collection<Account.Id> cc) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
index 42ac917..62a6c72 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
@@ -16,15 +16,18 @@
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
 
 /** Alert a user to a reply to a change, usually commentary made during review. */
 public abstract class ReplyToChangeSender extends ChangeEmail {
   public static interface Factory<T extends ReplyToChangeSender> {
-    public T create(Change change);
+    public T create(Change.Id id);
   }
 
-  protected ReplyToChangeSender(EmailArguments ea, Change c, String mc) {
-    super(ea, c, mc);
+  protected ReplyToChangeSender(EmailArguments ea, String mc, ChangeData cd)
+      throws OrmException {
+    super(ea, mc, cd);
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
index 4f65ab4..a43c7b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -25,12 +26,13 @@
   public static interface Factory extends
       ReplyToChangeSender.Factory<RestoredSender> {
     @Override
-    RestoredSender create(Change change);
+    RestoredSender create(Change.Id id);
   }
 
   @Inject
-  public RestoredSender(EmailArguments ea, @Assisted Change c) {
-    super(ea, c, "restore");
+  public RestoredSender(EmailArguments ea, @Assisted Change.Id id)
+      throws OrmException {
+    super(ea, "restore", newChangeData(ea, id));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java
index d1389fb..dda68ab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java
@@ -17,18 +17,20 @@
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
 /** Send notice about a change being reverted. */
 public class RevertedSender extends ReplyToChangeSender {
   public static interface Factory {
-    RevertedSender create(Change change);
+    RevertedSender create(Change.Id id);
   }
 
   @Inject
-  public RevertedSender(EmailArguments ea, @Assisted Change c) {
-    super(ea, c, "revert");
+  public RevertedSender(EmailArguments ea, @Assisted Change.Id id)
+      throws OrmException {
+    super(ea, "revert", newChangeData(ea, id));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
index 133993a..41d4920 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -56,9 +57,8 @@
   public BranchResource parse(ProjectResource parent, IdString id)
       throws ResourceNotFoundException, IOException, BadRequestException {
     String branchName = id.get();
-    if (!branchName.startsWith(Constants.R_REFS)
-        && !branchName.equals(Constants.HEAD)) {
-      branchName = Constants.R_HEADS + branchName;
+    if (!branchName.equals(Constants.HEAD)) {
+      branchName = RefNames.fullName(branchName);
     }
     List<BranchInfo> branches = list.get().apply(parent);
     for (BranchInfo b : branches) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index 224b809..cb94063 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -14,8 +14,7 @@
 
 package com.google.gerrit.server.project;
 
-import static org.eclipse.jgit.lib.RefDatabase.ALL;
-
+import com.google.common.collect.Iterables;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.errors.InvalidRevisionException;
 import com.google.gerrit.extensions.api.projects.BranchInfo;
@@ -26,6 +25,7 @@
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.reviewdb.client.Branch;
 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.IdentifiedUser;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -42,6 +42,7 @@
 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.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.ObjectWalk;
@@ -52,6 +53,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.Collections;
 
 public class CreateBranch implements RestModifyView<ProjectResource, Input> {
   private static final Logger log = LoggerFactory.getLogger(CreateBranch.class);
@@ -104,9 +106,7 @@
     while (ref.startsWith("/")) {
       ref = ref.substring(1);
     }
-    if (!ref.startsWith(Constants.R_REFS)) {
-      ref = Constants.R_HEADS + ref;
-    }
+    ref = RefNames.fullName(ref);
     if (!Repository.isValidRefName(ref)) {
       throw new BadRequestException("invalid branch name \"" + ref + "\"");
     }
@@ -225,7 +225,15 @@
       } catch (IncorrectObjectTypeException err) {
         throw new InvalidRevisionException();
       }
-      for (final Ref r : repo.getRefDatabase().getRefs(ALL).values()) {
+      RefDatabase refDb = repo.getRefDatabase();
+      Iterable<Ref> refs = Iterables.concat(
+          refDb.getRefs(Constants.R_HEADS).values(),
+          refDb.getRefs(Constants.R_TAGS).values());
+      Ref rc = refDb.getRef(RefNames.REFS_CONFIG);
+      if (rc != null) {
+        refs = Iterables.concat(refs, Collections.singleton(rc));
+      }
+      for (Ref r : refs) {
         try {
           rw.markUninteresting(rw.parseAny(r.getObjectId()));
         } catch (MissingObjectException err) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
index ac21646..07c0162 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.SetHead.Input;
@@ -71,10 +72,7 @@
     if (input == null || Strings.isNullOrEmpty(input.ref)) {
       throw new BadRequestException("ref required");
     }
-    String ref = input.ref;
-    if (!ref.startsWith(Constants.R_REFS)) {
-      ref = Constants.R_HEADS + ref;
-    }
+    String ref = RefNames.fullName(input.ref);
 
     Repository repo = null;
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 94fe742..d14a223 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -15,11 +15,11 @@
 package com.google.gerrit.server.query.change;
 
 import static com.google.gerrit.server.query.change.ChangeData.asChanges;
+import static com.google.gerrit.server.query.change.InternalChangeQuery.schema;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Optional;
 import com.google.common.collect.Lists;
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NotSignedInException;
 import com.google.gerrit.reviewdb.client.Account;
@@ -43,7 +43,6 @@
 import com.google.gerrit.server.index.ChangeIndex;
 import com.google.gerrit.server.index.FieldDef;
 import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ListChildProjects;
@@ -467,9 +466,9 @@
   @Operator
   public Predicate<ChangeData> topic(String name) {
     if (name.startsWith("^")) {
-      return new RegexTopicPredicate(name);
+      return new RegexTopicPredicate(schema(args.indexes), name);
     }
-    return new TopicPredicate(name);
+    return new TopicPredicate(schema(args.indexes), name);
   }
 
   @Operator
@@ -864,9 +863,4 @@
   private Account.Id self() throws QueryParseException {
     return args.getIdentifiedUser().getAccountId();
   }
-
-  private static Schema<ChangeData> schema(@Nullable IndexCollection indexes) {
-    ChangeIndex index = indexes != null ? indexes.getSearchIndex() : null;
-    return index != null ? index.getSchema() : null;
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index c552a7b..573bc77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -17,9 +17,13 @@
 import static com.google.gerrit.server.query.Predicate.and;
 import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
 
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.IndexCollection;
+import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
@@ -55,19 +59,18 @@
     return new ChangeStatusPredicate(status);
   }
 
-  private static Predicate<ChangeData> topic(String topic) {
-    return new TopicPredicate(topic);
-  }
-
   private static Predicate<ChangeData> commit(AbbreviatedObjectId id) {
     return new CommitPredicate(id);
   }
 
   private final QueryProcessor qp;
+  private final IndexCollection indexes;
 
   @Inject
-  InternalChangeQuery(QueryProcessor queryProcessor) {
+  InternalChangeQuery(QueryProcessor queryProcessor,
+      IndexCollection indexes) {
     qp = queryProcessor.enforceVisibility(false);
+    this.indexes = indexes;
   }
 
   public InternalChangeQuery setLimit(int n) {
@@ -80,6 +83,10 @@
     return this;
   }
 
+  private Predicate<ChangeData> topic(String topic) {
+    return new TopicPredicate(schema(indexes), topic);
+  }
+
   public List<ChangeData> byKey(Change.Key key) throws OrmException {
     return byKeyPrefix(key.get());
   }
@@ -145,4 +152,9 @@
       throw new OrmException(e);
     }
   }
+
+  static Schema<ChangeData> schema(@Nullable IndexCollection indexes) {
+    ChangeIndex index = indexes != null ? indexes.getSearchIndex() : null;
+    return index != null ? index.getSchema() : null;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
index 3a9604f..7d5f1dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.RegexPredicate;
+import com.google.gerrit.server.index.Schema;
 import com.google.gwtorm.server.OrmException;
 
 import dk.brics.automaton.RegExp;
@@ -25,8 +25,8 @@
 class RegexTopicPredicate extends RegexPredicate<ChangeData> {
   private final RunAutomaton pattern;
 
-  RegexTopicPredicate(String re) {
-    super(ChangeField.TOPIC, re);
+  RegexTopicPredicate(Schema<ChangeData> schema, String re) {
+    super(TopicPredicate.topicField(schema), re);
 
     if (re.startsWith("^")) {
       re = re.substring(1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java
deleted file mode 100644
index 0947fae..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java
+++ /dev/null
@@ -1,117 +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.query.change;
-
-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.RevId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.query.OperatorPredicate;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
-
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-/**
- * Predicate which creates Repository, RevWalk objects and properly
- * closes them. Git based operators should extend this predicate.
- *
- */
-public abstract class RevWalkPredicate extends OperatorPredicate<ChangeData> {
-  private static final Logger log =
-      LoggerFactory.getLogger(RevWalkPredicate.class);
-
-  public static class Arguments {
-    public final PatchSet patchSet;
-    public final RevId revision;
-    public final AnyObjectId objectId;
-    public final Change change;
-    public final Project.NameKey projectName;
-
-    public Arguments(PatchSet patchSet,
-        RevId revision,
-        AnyObjectId objectId,
-        Change change,
-        Project.NameKey projectName) {
-      this.patchSet = patchSet;
-      this.revision = revision;
-      this.objectId = objectId;
-      this.change = change;
-      this.projectName = projectName;
-    }
-  }
-
-  public final Provider<ReviewDb> db;
-  public final GitRepositoryManager repoManager;
-
-  public RevWalkPredicate(Provider<ReviewDb> db,
-      GitRepositoryManager repoManager, String operator, String ref) {
-    super(operator, ref);
-    this.db = db;
-    this.repoManager = repoManager;
-  }
-
-  @Override
-  public boolean match(ChangeData object) throws OrmException {
-    final PatchSet patchSet = object.currentPatchSet();
-    if (patchSet == null) {
-      return false;
-    }
-
-    final RevId revision = patchSet.getRevision();
-    if (revision == null) {
-      return false;
-    }
-
-    final AnyObjectId objectId = ObjectId.fromString(revision.get());
-    if (objectId == null) {
-      return false;
-    }
-
-    Change change = object.change();
-    if (change == null) {
-      return false;
-    }
-
-    final Project.NameKey projectName = change.getProject();
-    if (projectName == null) {
-      return false;
-    }
-
-    Arguments args = new Arguments(patchSet, revision, objectId, change, projectName);
-
-    try (Repository repo = repoManager.openRepository(projectName);
-        RevWalk rw = new RevWalk(repo)) {
-      return match(repo, rw, args);
-    } catch (RepositoryNotFoundException e) {
-      log.error("Repository \"" + projectName.get() + "\" unknown.", e);
-    } catch (IOException e) {
-      log.error(projectName.get() + " cannot be read as a repository", e);
-    }
-    return false;
-  }
-
-  public abstract boolean match(Repository repo, RevWalk rw, Arguments args);
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
index 07a6714..7196c9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
@@ -18,12 +18,26 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.FieldDef;
 import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.Schema;
 import com.google.gwtorm.server.OrmException;
 
 class TopicPredicate extends IndexPredicate<ChangeData> {
-  TopicPredicate(String topic) {
-    super(ChangeField.TOPIC, topic);
+  @SuppressWarnings("deprecation")
+  static FieldDef<ChangeData, ?> topicField(Schema<ChangeData> schema) {
+    if (schema == null) {
+      return ChangeField.LEGACY_TOPIC;
+    }
+    FieldDef<ChangeData, ?> f = schema.getFields().get(TOPIC.getName());
+    if (f != null) {
+      return f;
+    }
+    return schema.getFields().get(ChangeField.LEGACY_TOPIC.getName());
+  }
+
+  TopicPredicate(Schema<ChangeData> schema, String topic) {
+    super(topicField(schema), topic);
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
index 8970425..45aa31b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
@@ -17,7 +17,9 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.lib.BlobBasedConfig;
 import org.eclipse.jgit.lib.Constants;
@@ -45,18 +47,26 @@
  * </pre>
  */
 public class SubmoduleSectionParser {
+
+  public interface Factory {
+    SubmoduleSectionParser create(BlobBasedConfig bbc, String thisServer,
+        Branch.NameKey superProjectBranch);
+  }
+
+  private final ProjectCache projectCache;
   private final BlobBasedConfig bbc;
   private final String thisServer;
   private final Branch.NameKey superProjectBranch;
-  private final GitRepositoryManager repoManager;
 
-  public SubmoduleSectionParser(final BlobBasedConfig bbc,
-      final String thisServer, final Branch.NameKey superProjectBranch,
-      final GitRepositoryManager repoManager) {
+  @Inject
+  public SubmoduleSectionParser(ProjectCache projectCache,
+      @Assisted BlobBasedConfig bbc,
+      @Assisted String thisServer,
+      @Assisted Branch.NameKey superProjectBranch) {
+    this.projectCache = projectCache;
     this.bbc = bbc;
     this.thisServer = thisServer;
     this.superProjectBranch = superProjectBranch;
-    this.repoManager = repoManager;
   }
 
   public List<SubmoduleSubscription> parseAllSections() {
@@ -106,12 +116,10 @@
               projectName = projectName.substring(0, //
                   projectName.length() - Constants.DOT_GIT_EXT.length());
             }
-
-            if (repoManager.list().contains(new Project.NameKey(projectName))) {
-              return new SubmoduleSubscription(
-                  superProjectBranch,
-                  new Branch.NameKey(new Project.NameKey(projectName), branch),
-                  path);
+            Project.NameKey projectKey = new Project.NameKey(projectName);
+            if (projectCache.get(projectKey) != null) {
+              return new SubmoduleSubscription(superProjectBranch,
+                  new Branch.NameKey(projectKey, branch), path);
             }
           }
         }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
index 721992f..0d81ee2 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
@@ -299,8 +299,8 @@
     update.commit();
 
     ChangeControl ctl = stubChangeControl(change);
-    revRes1 = new RevisionResource(new ChangeResource(ctl), ps1);
-    revRes2 = new RevisionResource(new ChangeResource(ctl), ps2);
+    revRes1 = new RevisionResource(new ChangeResource(ctl, null), ps1);
+    revRes2 = new RevisionResource(new ChangeResource(ctl, null), ps2);
   }
 
   private ChangeControl stubChangeControl(Change c) throws OrmException {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java
index 426fb93..8c963bd 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/edit/ChangeEditTest.java
@@ -19,6 +19,7 @@
 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.RefNames;
 
 import org.junit.Test;
 
@@ -28,7 +29,7 @@
     Account.Id accountId = new Account.Id(1000042);
     Change.Id changeId = new Change.Id(56414);
     PatchSet.Id psId = new PatchSet.Id(changeId, 50);
-    String refName = ChangeEditUtil.editRefName(accountId, changeId, psId);
+    String refName = RefNames.refsEdit(accountId, changeId, psId);
     assertEquals("refs/users/42/1000042/edit-56414/50", refName);
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java
index 1073522..7eed35f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/events/EventTypesTest.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.events;
 
 import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
 
 import org.junit.Test;
 
@@ -26,12 +25,6 @@
     }
   }
 
-  public static class TestEvent2 extends Event {
-    public TestEvent2() {
-      super("test-event"); // Intentionally same as in TestEvent
-    }
-  }
-
   public static class AnotherTestEvent extends Event {
     public AnotherTestEvent() {
       super("another-test-event");
@@ -45,20 +38,6 @@
     assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
     assertThat(EventTypes.getClass("another-test-event"))
       .isEqualTo(AnotherTestEvent.class);
-
-    try {
-      EventTypes.registerClass(new TestEvent());
-      fail("Expected IllegalArgumentException");
-    } catch (IllegalArgumentException e) {
-      assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
-    }
-
-    try {
-      EventTypes.registerClass(new TestEvent2());
-      fail("Expected IllegalArgumentException");
-    } catch (IllegalArgumentException e) {
-      assertThat(EventTypes.getClass("test-event")).isEqualTo(TestEvent.class);
-    }
   }
 
   @Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
index 356ce8b..181ba15 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
@@ -43,8 +43,10 @@
 import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.testutil.InMemoryDatabase;
 import com.google.gerrit.testutil.InMemoryModule;
+import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
+import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.lib.Repository;
 import org.junit.After;
@@ -58,7 +60,7 @@
   @Inject private AccountManager accountManager;
   @Inject private AllProjectsName allProjects;
   @Inject private GitRepositoryManager repoManager;
-  @Inject private IdentifiedUser.RequestFactory userFactory;
+  @Inject private IdentifiedUser.GenericFactory userFactory;
   @Inject private InMemoryDatabase schemaFactory;
   @Inject private LabelNormalizer norm;
   @Inject private MetaDataUpdate.User metaDataUpdateFactory;
@@ -73,16 +75,17 @@
 
   @Before
   public void setUpInjector() throws Exception {
-    lifecycle = new LifecycleManager();
-    Injector injector = InMemoryModule.createInjector(lifecycle);
+    Injector injector = Guice.createInjector(new InMemoryModule());
     injector.injectMembers(this);
+    lifecycle = new LifecycleManager();
+    lifecycle.add(injector);
     lifecycle.start();
 
     db = schemaFactory.open();
     schemaCreator.create(db);
     userId = accountManager.authenticate(AuthRequest.forUser("user"))
         .getAccountId();
-    user = userFactory.create(userId);
+    user = userFactory.create(Providers.of(db), userId);
 
     configureProject();
     setUpChange();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
deleted file mode 100644
index c2f3f6f..0000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
+++ /dev/null
@@ -1,1017 +0,0 @@
-// Copyright (C) 2011 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.git;
-
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.junit.Assert.assertEquals;
-
-import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.SubmoduleSubscriptionAccess;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gwtorm.client.KeyUtil;
-import com.google.gwtorm.server.ListResultSet;
-import com.google.gwtorm.server.ResultSet;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.gwtorm.server.StandardKeyEncoder;
-import com.google.inject.Provider;
-
-import org.easymock.Capture;
-import org.easymock.EasyMock;
-import org.easymock.IMocksControl;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
-import org.eclipse.jgit.dircache.DirCacheEntry;
-import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
-import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-public class SubmoduleOpTest extends LocalDiskRepositoryTestCase {
-  static {
-    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
-  }
-
-  private static final String newLine = System.getProperty("line.separator");
-
-  private IMocksControl mockMaker;
-  private SchemaFactory<ReviewDb> schemaFactory;
-  private SubmoduleSubscriptionAccess subscriptions;
-  private ReviewDb schema;
-  private Provider<String> urlProvider;
-  private GitRepositoryManager repoManager;
-  private GitReferenceUpdated gitRefUpdated;
-  private ChangeHooks changeHooks;
-
-  @SuppressWarnings("unchecked")
-  @Override
-  @Before
-  public void setUp() throws Exception {
-    super.setUp();
-
-    mockMaker = EasyMock.createStrictControl();
-    schemaFactory = mockMaker.createMock(SchemaFactory.class);
-    schema = mockMaker.createMock(ReviewDb.class);
-    subscriptions = mockMaker.createMock(SubmoduleSubscriptionAccess.class);
-    urlProvider = mockMaker.createMock(Provider.class);
-    repoManager = mockMaker.createMock(GitRepositoryManager.class);
-    gitRefUpdated = mockMaker.createMock(GitReferenceUpdated.class);
-    changeHooks = mockMaker.createMock(ChangeHooks.class);
-  }
-
-  private void doReplay() {
-    mockMaker.replay();
-  }
-
-  private void doVerify() {
-    mockMaker.verify();
-  }
-
-  /**
-   * It tests Submodule.update in the scenario a merged commit is an empty one
-   * (it does not have a .gitmodules file) and the project the commit was merged
-   * is not a submodule of other project.
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testEmptyCommit() throws Exception {
-    expect(schemaFactory.open()).andReturn(schema);
-
-    try (Repository realDb = createWorkRepository()) {
-      // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
-      @SuppressWarnings("resource")
-      final Git git = new Git(realDb);
-
-      final RevCommit mergeTip = git.commit().setMessage("test").call();
-
-      final Branch.NameKey branchNameKey =
-          new Branch.NameKey(new Project.NameKey("test-project"), "test-branch");
-
-      expect(urlProvider.get()).andReturn("http://localhost:8080");
-
-      expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
-      final ResultSet<SubmoduleSubscription> emptySubscriptions =
-          new ListResultSet<>(new ArrayList<SubmoduleSubscription>());
-      expect(subscriptions.bySubmodule(branchNameKey)).andReturn(
-          emptySubscriptions);
-
-      schema.close();
-
-      doReplay();
-
-      final SubmoduleOp submoduleOp =
-          new SubmoduleOp(branchNameKey, mergeTip, new RevWalk(realDb), urlProvider,
-              schemaFactory, realDb, null, new ArrayList<Change>(), null, null,
-              null, null, null, null);
-
-      submoduleOp.update();
-
-      doVerify();
-    }
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering:
-   * <ul>
-   * <li>no subscriptions existing to destination project</li>
-   * <li>a commit is merged to "dest-project"</li>
-   * <li>commit contains .gitmodules file with content</li>
-   * </ul>
-   *
-   * <pre>
-   *     [submodule "source"]
-   *       path = source
-   *       url = http://localhost:8080/source
-   *       branch = .
-   * </pre>
-   * <p>
-   * It expects to insert a new row in subscriptions table. The row inserted
-   * specifies:
-   * <ul>
-   * <li>target "dest-project" on branch "refs/heads/master"</li>
-   * <li>source "a" on branch "refs/heads/master"</li>
-   * <li>path "a"</li>
-   * </ul>
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testNewSubscriptionToDotBranchValue() throws Exception {
-    doOneSubscriptionInsert(buildSubmoduleSection("source", "source",
-        "http://localhost:8080/source", ".").toString(), "refs/heads/master");
-
-    doVerify();
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering:
-   * <ul>
-   * <li>no subscriptions existing to destination project</li>
-   * <li>a commit is merged to "dest-project"</li>
-   * <li>commit contains .gitmodules file with content</li>
-   * </ul>
-   *
-   * <pre>
-   *     [submodule "source"]
-   *       path = source
-   *       url = http://localhost:8080/source
-   *       branch = refs/heads/master
-   * </pre>
-   *
-   * <p>
-   * It expects to insert a new row in subscriptions table. The row inserted
-   * specifies:
-   * <ul>
-   * <li>target "dest-project" on branch "refs/heads/master"</li>
-   * <li>source "source" on branch "refs/heads/master"</li>
-   * <li>path "source"</li>
-   * </ul>
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testNewSubscriptionToSameBranch() throws Exception {
-    doOneSubscriptionInsert(buildSubmoduleSection("source", "source",
-        "http://localhost:8080/source", "refs/heads/master").toString(),
-        "refs/heads/master");
-
-    doVerify();
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering:
-   * <ul>
-   * <li>no subscriptions existing to destination project</li>
-   * <li>a commit is merged to "dest-project"</li>
-   * <li>commit contains .gitmodules file with content</li>
-   * </ul>
-   *
-   * <pre>
-   *     [submodule "source"]
-   *       path = source
-   *       url = http://localhost:8080/source
-   *       branch = refs/heads/test
-   * </pre>
-   * <p>
-   * It expects to insert a new row in subscriptions table. The row inserted
-   * specifies:
-   * <ul>
-   * <li>target "dest-project" on branch "refs/heads/master"</li>
-   * <li>source "source" on branch "refs/heads/test"</li>
-   * <li>path "source"</li>
-   * </ul>
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testNewSubscriptionToDifferentBranch() throws Exception {
-    doOneSubscriptionInsert(buildSubmoduleSection("source", "source",
-        "http://localhost:8080/source", "refs/heads/test").toString(),
-        "refs/heads/test");
-
-    doVerify();
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering:
-   * <ul>
-   * <li>no subscriptions existing to destination project</li>
-   * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li>
-   * <li>commit contains .gitmodules file with content</li>
-   * </ul>
-   *
-   * <pre>
-   *     [submodule "source-a"]
-   *       path = source-a
-   *       url = http://localhost:8080/source-a
-   *       branch = .
-   *
-   *     [submodule "source-b"]
-   *       path = source-b
-   *       url = http://localhost:8080/source-b
-   *       branch = .
-   * </pre>
-   * <p>
-   * It expects to insert new rows in subscriptions table. The rows inserted
-   * specifies:
-   * <ul>
-   * <li>target "dest-project" on branch "refs/heads/master"</li>
-   * <li>source "source-a" on branch "refs/heads/master" with "source-a" path</li>
-   * <li>source "source-b" on branch "refs/heads/master" with "source-b" path</li>
-   * </ul>
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testNewSubscriptionsWithDotBranchValue() throws Exception {
-    final StringBuilder sb =
-        buildSubmoduleSection("source-a", "source-a",
-            "http://localhost:8080/source-a", ".");
-    sb.append(buildSubmoduleSection("source-b", "source-b",
-        "http://localhost:8080/source-b", "."));
-
-    final Branch.NameKey mergedBranch =
-        new Branch.NameKey(new Project.NameKey("dest-project"),
-            "refs/heads/master");
-
-    List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>();
-    subscriptionsToInsert
-        .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(
-            new Project.NameKey("source-a"), "refs/heads/master"), "source-a"));
-    subscriptionsToInsert
-        .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(
-            new Project.NameKey("source-b"), "refs/heads/master"), "source-b"));
-
-    doOnlySubscriptionInserts(sb.toString(), mergedBranch,
-        subscriptionsToInsert);
-
-    doVerify();
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering:
-   * <ul>
-   * <li>no subscriptions existing to destination project</li>
-   * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li>
-   * <li>commit contains .gitmodules file with content</li>
-   * </ul>
-   *
-   * <pre>
-   *     [submodule "source-a"]
-   *       path = source-a
-   *       url = http://localhost:8080/source-a
-   *       branch = .
-   *
-   *     [submodule "source-b"]
-   *       path = source-b
-   *       url = http://localhost:8080/source-b
-   *       branch = refs/heads/master
-   * </pre>
-   * <p>
-   * It expects to insert new rows in subscriptions table. The rows inserted
-   * specifies:
-   * <ul>
-   * <li>target "dest-project" on branch "refs/heads/master"</li>
-   * <li>source "source-a" on branch "refs/heads/master" with "source-a" path</li>
-   * <li>source "source-b" on branch "refs/heads/master" with "source-b" path</li>
-   * </ul>
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testNewSubscriptionsDotAndSameBranchValues() throws Exception {
-    final StringBuilder sb =
-        buildSubmoduleSection("source-a", "source-a",
-            "http://localhost:8080/source-a", ".");
-    sb.append(buildSubmoduleSection("source-b", "source-b",
-        "http://localhost:8080/source-b", "refs/heads/master"));
-
-    final Branch.NameKey mergedBranch =
-        new Branch.NameKey(new Project.NameKey("dest-project"),
-            "refs/heads/master");
-
-    List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>();
-    subscriptionsToInsert
-        .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(
-            new Project.NameKey("source-a"), "refs/heads/master"), "source-a"));
-    subscriptionsToInsert
-        .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(
-            new Project.NameKey("source-b"), "refs/heads/master"), "source-b"));
-
-    doOnlySubscriptionInserts(sb.toString(), mergedBranch,
-        subscriptionsToInsert);
-
-    doVerify();
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering:
-   * <ul>
-   * <li>no subscriptions existing to destination project</li>
-   * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li>
-   * <li>commit contains .gitmodules file with content</li>
-   *
-   * <pre>
-   *     [submodule "source-a"]
-   *       path = source-a
-   *       url = http://localhost:8080/source-a
-   *       branch = refs/heads/test-a
-   *
-   *     [submodule "source-b"]
-   *       path = source-b
-   *       url = http://localhost:8080/source-b
-   *       branch = refs/heads/test-b
-   * </pre>
-   *
-   * <p>
-   * It expects to insert new rows in subscriptions table. The rows inserted
-   * specifies:
-   * <ul>
-   * <li>target "dest-project" on branch "refs/heads/master"</li>
-   * <li>source "source-a" on branch "refs/heads/test-a" with "source-a" path</li>
-   * <li>source "source-b" on branch "refs/heads/test-b" with "source-b" path</li>
-   * </ul>
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testNewSubscriptionsSpecificBranchValues() throws Exception {
-    final StringBuilder sb =
-        buildSubmoduleSection("source-a", "source-a",
-            "http://localhost:8080/source-a", "refs/heads/test-a");
-    sb.append(buildSubmoduleSection("source-b", "source-b",
-        "http://localhost:8080/source-b", "refs/heads/test-b"));
-
-    final Branch.NameKey mergedBranch =
-        new Branch.NameKey(new Project.NameKey("dest-project"),
-            "refs/heads/master");
-
-    List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>();
-    subscriptionsToInsert
-        .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(
-            new Project.NameKey("source-a"), "refs/heads/test-a"), "source-a"));
-    subscriptionsToInsert
-        .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(
-            new Project.NameKey("source-b"), "refs/heads/test-b"), "source-b"));
-
-    doOnlySubscriptionInserts(sb.toString(), mergedBranch,
-        subscriptionsToInsert);
-
-    doVerify();
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering:
-   * <ul>
-   * <li>one subscription existing to destination project/branch</li>
-   * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li>
-   * <li>commit contains .gitmodules file with content</li>
-   * </ul>
-   *
-   * <pre>
-   *     [submodule "source"]
-   *       path = source
-   *       url = http://localhost:8080/source
-   *       branch = refs/heads/master
-   * </pre>
-   * <p>
-   * It expects to insert a new row in subscriptions table. The rows inserted
-   * specifies:
-   * <ul>
-   * <li>target "dest-project" on branch "refs/heads/master"</li>
-   * <li>source "source" on branch "refs/heads/master" with "source" path</li>
-   * </ul>
-   * </p>
-   * <p>
-   * It also expects to remove the row in subscriptions table specifying another
-   * project/branch subscribed to merged branch. This one to be removed is:
-   * <ul>
-   * <li>target "dest-project" on branch "refs/heads/master"</li>
-   * <li>source "old-source" on branch "refs/heads/master" with "old-source"
-   * path</li>
-   * </ul>
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testSubscriptionsInsertOneRemoveOne() throws Exception {
-    final Branch.NameKey mergedBranch =
-        new Branch.NameKey(new Project.NameKey("dest-project"),
-            "refs/heads/master");
-
-    List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>();
-    subscriptionsToInsert.add(new SubmoduleSubscription(mergedBranch,
-        new Branch.NameKey(new Project.NameKey("source"), "refs/heads/master"),
-        "source"));
-
-    List<SubmoduleSubscription> oldOnesToMergedBranch = new ArrayList<>();
-    oldOnesToMergedBranch.add(new SubmoduleSubscription(mergedBranch,
-        new Branch.NameKey(new Project.NameKey("old-source"),
-            "refs/heads/master"), "old-source"));
-
-    doOnlySubscriptionTableOperations(buildSubmoduleSection("source", "source",
-        "http://localhost:8080/source", "refs/heads/master").toString(),
-        mergedBranch, subscriptionsToInsert, oldOnesToMergedBranch);
-
-    doVerify();
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering:
-   * <ul>
-   * <li>one subscription existing to destination project/branch with a source
-   * called old on refs/heads/master branch</li>
-   * <li>a commit is merged to "dest-project" in "refs/heads/master" branch</li>
-   * <li>
-   * commit contains .gitmodules file with content</li>
-   * </ul>
-   *
-   * <pre>
-   *     [submodule "new"]
-   *       path = new
-   *       url = http://localhost:8080/new
-   *       branch = refs/heads/master
-   *
-   *     [submodule "old"]
-   *       path = old
-   *       url = http://localhost:8080/old
-   *       branch = refs/heads/master
-   * </pre>
-   * <p>
-   * It expects to insert a new row in subscriptions table. It should not remove
-   * any row. The rows inserted specifies:
-   * <ul>
-   * <li>target "dest-project" on branch "refs/heads/master"</li>
-   * <li>source "new" on branch "refs/heads/master" with "new" path</li>
-   * </ul>
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testSubscriptionAddedAndMantainPreviousOne() throws Exception {
-    final StringBuilder sb =
-        buildSubmoduleSection("new", "new", "http://localhost:8080/new",
-            "refs/heads/master");
-    sb.append(buildSubmoduleSection("old", "old", "http://localhost:8080/old",
-        "refs/heads/master"));
-
-    final Branch.NameKey mergedBranch =
-        new Branch.NameKey(new Project.NameKey("dest-project"),
-            "refs/heads/master");
-
-    final SubmoduleSubscription old =
-        new SubmoduleSubscription(mergedBranch, new Branch.NameKey(new Project.NameKey(
-            "old"), "refs/heads/master"), "old");
-
-    List<SubmoduleSubscription> extractedsubscriptions = new ArrayList<>();
-    extractedsubscriptions.add(new SubmoduleSubscription(mergedBranch,
-        new Branch.NameKey(new Project.NameKey("new"), "refs/heads/master"),
-        "new"));
-    extractedsubscriptions.add(old);
-
-    List<SubmoduleSubscription> oldOnesToMergedBranch = new ArrayList<>();
-    oldOnesToMergedBranch.add(old);
-
-    doOnlySubscriptionTableOperations(sb.toString(), mergedBranch,
-        extractedsubscriptions, oldOnesToMergedBranch);
-
-    doVerify();
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering an empty .gitmodules
-   * file is part of a commit to a destination project/branch having two sources
-   * subscribed.
-   * <p>
-   * It expects to remove the subscriptions to destination project/branch.
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testRemoveSubscriptions() throws Exception {
-    final Branch.NameKey mergedBranch =
-        new Branch.NameKey(new Project.NameKey("dest-project"),
-            "refs/heads/master");
-
-    List<SubmoduleSubscription> extractedsubscriptions = new ArrayList<>();
-    List<SubmoduleSubscription> oldOnesToMergedBranch = new ArrayList<>();
-    oldOnesToMergedBranch
-        .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(
-            new Project.NameKey("source-a"), "refs/heads/master"), "source-a"));
-    oldOnesToMergedBranch
-        .add(new SubmoduleSubscription(mergedBranch, new Branch.NameKey(
-            new Project.NameKey("source-b"), "refs/heads/master"), "source-b"));
-
-    doOnlySubscriptionTableOperations("", mergedBranch, extractedsubscriptions,
-        oldOnesToMergedBranch);
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering no .gitmodules file
-   * in a merged commit to a destination project/branch that is a source one to
-   * one called "target-project".
-   * <p>
-   * It expects to update the git link called "source-project" to be in target
-   * repository.
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testOneSubscriberToUpdate() throws Exception {
-    expect(schemaFactory.open()).andReturn(schema);
-
-    try (Repository sourceRepository = createWorkRepository();
-        Repository targetRepository = createWorkRepository()) {
-      // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
-      @SuppressWarnings("resource")
-      final Git sourceGit = new Git(sourceRepository);
-      // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
-      @SuppressWarnings("resource")
-      final Git targetGit = new Git(targetRepository);
-
-      addRegularFileToIndex("file.txt", "test content", sourceRepository);
-
-      final RevCommit sourceMergeTip =
-          sourceGit.commit().setMessage("test").call();
-
-      final Branch.NameKey sourceBranchNameKey =
-          new Branch.NameKey(new Project.NameKey("source-project"),
-              "refs/heads/master");
-
-      final CodeReviewCommit codeReviewCommit =
-          new CodeReviewCommit(sourceMergeTip.toObjectId());
-      final Change submittedChange = new Change(
-          new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1),
-          new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs());
-
-      final Map<Change.Id, CodeReviewCommit> mergedCommits = new HashMap<>();
-      mergedCommits.put(submittedChange.getId(), codeReviewCommit);
-
-      final List<Change> submitted = new ArrayList<>();
-      submitted.add(submittedChange);
-
-      addGitLinkToIndex("a", sourceMergeTip.copy(), targetRepository);
-
-      targetGit.commit().setMessage("test").call();
-
-      final Branch.NameKey targetBranchNameKey =
-          new Branch.NameKey(new Project.NameKey("target-project"),
-              sourceBranchNameKey.get());
-
-      expect(urlProvider.get()).andReturn("http://localhost:8080");
-
-      expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
-      final ResultSet<SubmoduleSubscription> subscribers =
-          new ListResultSet<>(Collections
-              .singletonList(new SubmoduleSubscription(targetBranchNameKey,
-                  sourceBranchNameKey, "source-project")));
-      expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn(
-          subscribers);
-
-      expect(repoManager.openRepository(targetBranchNameKey.getParentKey()))
-          .andReturn(targetRepository).anyTimes();
-
-      Capture<RefUpdate> ruCapture = new Capture<>();
-      gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()),
-          capture(ruCapture));
-      changeHooks.doRefUpdatedHook(eq(targetBranchNameKey),
-          anyObject(RefUpdate.class), EasyMock.<Account>isNull());
-
-      expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
-      final ResultSet<SubmoduleSubscription> emptySubscriptions =
-          new ListResultSet<>(new ArrayList<SubmoduleSubscription>());
-      expect(subscriptions.bySubmodule(targetBranchNameKey)).andReturn(
-          emptySubscriptions);
-
-      schema.close();
-
-      final PersonIdent myIdent =
-          new PersonIdent("test-user", "test-user@email.com");
-
-      doReplay();
-
-      final SubmoduleOp submoduleOp =
-          new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk(
-              sourceRepository), urlProvider, schemaFactory, sourceRepository,
-              new Project(sourceBranchNameKey.getParentKey()), submitted,
-              mergedCommits, myIdent, repoManager, gitRefUpdated, null,
-              changeHooks);
-
-      submoduleOp.update();
-
-      doVerify();
-      RefUpdate ru = ruCapture.getValue();
-      assertEquals(ru.getName(), targetBranchNameKey.get());
-    }
-  }
-
-  /**
-   * It tests SubmoduleOp.update in a scenario considering established circular
-   * reference in submodule_subscriptions table.
-   * <p>
-   * In the tested scenario there is no .gitmodules file in a merged commit to a
-   * destination project/branch that is a source one to one called
-   * "target-project".
-   * <p>
-   * submodule_subscriptions table will be incorrect due source appearing as a
-   * subscriber or target-project: according to database target-project has as
-   * source the source-project, and source-project has as source the
-   * target-project.
-   * <p>
-   * It expects to update the git link called "source-project" to be in target
-   * repository and ignoring the incorrect row in database establishing the
-   * circular reference.
-   * </p>
-   *
-   * @throws Exception If an exception occurs.
-   */
-  @Test
-  public void testAvoidingCircularReference() throws Exception {
-    expect(schemaFactory.open()).andReturn(schema);
-
-    try (Repository sourceRepository = createWorkRepository();
-        Repository targetRepository = createWorkRepository()) {
-      // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
-      @SuppressWarnings("resource")
-      final Git sourceGit = new Git(sourceRepository);
-      // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
-      @SuppressWarnings("resource")
-      final Git targetGit = new Git(targetRepository);
-
-      addRegularFileToIndex("file.txt", "test content", sourceRepository);
-
-      final RevCommit sourceMergeTip =
-          sourceGit.commit().setMessage("test").call();
-
-      final Branch.NameKey sourceBranchNameKey =
-          new Branch.NameKey(new Project.NameKey("source-project"),
-              "refs/heads/master");
-
-      final CodeReviewCommit codeReviewCommit =
-          new CodeReviewCommit(sourceMergeTip.toObjectId());
-      final Change submittedChange = new Change(
-          new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1),
-          new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs());
-
-      final Map<Change.Id, CodeReviewCommit> mergedCommits = new HashMap<>();
-      mergedCommits.put(submittedChange.getId(), codeReviewCommit);
-
-      final List<Change> submitted = new ArrayList<>();
-      submitted.add(submittedChange);
-
-      addGitLinkToIndex("a", sourceMergeTip.copy(), targetRepository);
-
-      targetGit.commit().setMessage("test").call();
-
-      final Branch.NameKey targetBranchNameKey =
-          new Branch.NameKey(new Project.NameKey("target-project"),
-              sourceBranchNameKey.get());
-
-      expect(urlProvider.get()).andReturn("http://localhost:8080");
-
-      expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
-      final ResultSet<SubmoduleSubscription> subscribers =
-          new ListResultSet<>(Collections
-              .singletonList(new SubmoduleSubscription(targetBranchNameKey,
-                  sourceBranchNameKey, "source-project")));
-      expect(subscriptions.bySubmodule(sourceBranchNameKey)).andReturn(
-          subscribers);
-
-      expect(repoManager.openRepository(targetBranchNameKey.getParentKey()))
-          .andReturn(targetRepository).anyTimes();
-
-      Capture<RefUpdate> ruCapture = new Capture<>();
-      gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()),
-          capture(ruCapture));
-      changeHooks.doRefUpdatedHook(eq(targetBranchNameKey),
-            anyObject(RefUpdate.class), EasyMock.<Account>isNull());
-
-      expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
-      final ResultSet<SubmoduleSubscription> incorrectSubscriptions =
-          new ListResultSet<>(Collections
-              .singletonList(new SubmoduleSubscription(sourceBranchNameKey,
-                  targetBranchNameKey, "target-project")));
-      expect(subscriptions.bySubmodule(targetBranchNameKey)).andReturn(
-          incorrectSubscriptions);
-
-      schema.close();
-
-      final PersonIdent myIdent =
-          new PersonIdent("test-user", "test-user@email.com");
-
-      doReplay();
-
-      final SubmoduleOp submoduleOp =
-          new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk(
-              sourceRepository), urlProvider, schemaFactory, sourceRepository,
-              new Project(sourceBranchNameKey.getParentKey()), submitted,
-              mergedCommits, myIdent, repoManager, gitRefUpdated, null, changeHooks);
-
-      submoduleOp.update();
-
-      doVerify();
-      RefUpdate ru = ruCapture.getValue();
-      assertEquals(ru.getName(), targetBranchNameKey.get());
-    }
-  }
-
-  /**
-   * It calls SubmoduleOp.update considering only one insert on Subscriptions
-   * table.
-   * <p>
-   * It considers a commit containing a .gitmodules file was merged in
-   * refs/heads/master of a dest-project.
-   * </p>
-   * <p>
-   * The .gitmodules file content should indicate a source project called
-   * "source".
-   * </p>
-   *
-   * @param gitModulesFileContent The .gitmodules file content. During the test
-   *        this file is created, so the commit containing it.
-   * @param sourceBranchName The branch name of source project "pointed by"
-   *        .gitmodules file.
-   * @throws Exception If an exception occurs.
-   */
-  private void doOneSubscriptionInsert(final String gitModulesFileContent,
-      final String sourceBranchName) throws Exception {
-    final Branch.NameKey mergedBranch =
-        new Branch.NameKey(new Project.NameKey("dest-project"),
-            "refs/heads/master");
-
-    List<SubmoduleSubscription> subscriptionsToInsert = new ArrayList<>();
-    subscriptionsToInsert.add(new SubmoduleSubscription(mergedBranch,
-        new Branch.NameKey(new Project.NameKey("source"), sourceBranchName),
-        "source"));
-
-    doOnlySubscriptionInserts(gitModulesFileContent, mergedBranch,
-        subscriptionsToInsert);
-  }
-
-  /**
-   * It calls SubmoduleOp.update method considering scenario only inserting new
-   * subscriptions.
-   * <p>
-   * In this test a commit is created and considered merged to
-   * {@code mergedBranch} branch.
-   * </p>
-   * <p>
-   * The destination project the commit was merged is not considered to be a
-   * source of another project (no subscribers found to this project).
-   * </p>
-   *
-   * @param gitModulesFileContent The .gitmodules file content.
-   * @param mergedBranch The {@code Branch.NameKey} instance representing the
-   *        project/branch the commit was merged.
-   * @param extractedSubscriptions The subscription rows extracted from
-   *        gitmodules file.
-   * @throws Exception If an exception occurs.
-   */
-  private void doOnlySubscriptionInserts(final String gitModulesFileContent,
-      final Branch.NameKey mergedBranch,
-      final List<SubmoduleSubscription> extractedSubscriptions) throws Exception {
-    doOnlySubscriptionTableOperations(gitModulesFileContent, mergedBranch,
-        extractedSubscriptions, new ArrayList<SubmoduleSubscription>());
-  }
-
-  /**
-   * It calls SubmoduleOp.update method considering scenario only updating
-   * Subscriptions table.
-   * <p>
-   * In this test a commit is created and considered merged to
-   * {@code mergedBranch} branch.
-   * </p>
-   * <p>
-   * The destination project the commit was merged is not considered to be a
-   * source of another project (no subscribers found to this project).
-   * </p>
-   *
-   * @param gitModulesFileContent The .gitmodules file content.
-   * @param mergedBranch The {@code Branch.NameKey} instance representing the
-   *        project/branch the commit was merged.
-   * @param extractedSubscriptions The subscription rows extracted from
-   *        gitmodules file.
-   * @param previousSubscriptions The subscription rows to be considering as
-   *        existing and pointing as target to the {@code mergedBranch}
-   *        before updating the table.
-   * @throws Exception If an exception occurs.
-   */
-  private void doOnlySubscriptionTableOperations(
-      final String gitModulesFileContent, final Branch.NameKey mergedBranch,
-      final List<SubmoduleSubscription> extractedSubscriptions,
-      final List<SubmoduleSubscription> previousSubscriptions) throws Exception {
-    expect(schemaFactory.open()).andReturn(schema);
-
-    try (Repository realDb = createWorkRepository()) {
-      // TODO(dborowitz): Use try/finally when this doesn't double-close the repo.
-      @SuppressWarnings("resource")
-      final Git git = new Git(realDb);
-
-      addRegularFileToIndex(".gitmodules", gitModulesFileContent, realDb);
-
-      final RevCommit mergeTip = git.commit().setMessage("test").call();
-
-      expect(urlProvider.get()).andReturn("http://localhost:8080").times(2);
-
-      expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
-      expect(subscriptions.bySuperProject(mergedBranch)).andReturn(
-          new ListResultSet<>(previousSubscriptions));
-
-      SortedSet<Project.NameKey> existingProjects = new TreeSet<>();
-
-      for (SubmoduleSubscription extracted : extractedSubscriptions) {
-        existingProjects.add(extracted.getSubmodule().getParentKey());
-      }
-
-      for (int index = 0; index < extractedSubscriptions.size(); index++) {
-        expect(repoManager.list()).andReturn(existingProjects);
-      }
-
-      final Set<SubmoduleSubscription> alreadySubscribeds = new HashSet<>();
-      for (SubmoduleSubscription s : extractedSubscriptions) {
-        if (previousSubscriptions.contains(s)) {
-          alreadySubscribeds.add(s);
-        }
-      }
-
-      final Set<SubmoduleSubscription> subscriptionsToRemove =
-          new HashSet<>(previousSubscriptions);
-      final List<SubmoduleSubscription> subscriptionsToInsert =
-          new ArrayList<>(extractedSubscriptions);
-
-      subscriptionsToRemove.removeAll(subscriptionsToInsert);
-      subscriptionsToInsert.removeAll(alreadySubscribeds);
-
-      if (!subscriptionsToRemove.isEmpty()) {
-        expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
-        subscriptions.delete(subscriptionsToRemove);
-      }
-
-      expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
-      subscriptions.insert(subscriptionsToInsert);
-
-      expect(schema.submoduleSubscriptions()).andReturn(subscriptions);
-      expect(subscriptions.bySubmodule(mergedBranch)).andReturn(
-          new ListResultSet<>(new ArrayList<SubmoduleSubscription>()));
-
-      schema.close();
-
-      doReplay();
-
-      final SubmoduleOp submoduleOp =
-          new SubmoduleOp(mergedBranch, mergeTip, new RevWalk(realDb),
-              urlProvider, schemaFactory, realDb, new Project(mergedBranch
-                  .getParentKey()), new ArrayList<Change>(), null, null,
-              repoManager, null, null, null);
-
-      submoduleOp.update();
-    }
-  }
-
-  /**
-   * It creates and adds a regular file to git index of a repository.
-   *
-   * @param fileName The file name.
-   * @param content File content.
-   * @param repository The Repository instance.
-   * @throws IOException If an I/O exception occurs.
-   */
-  private void addRegularFileToIndex(final String fileName,
-      final String content, final Repository repository) throws IOException {
-    final ObjectInserter oi = repository.newObjectInserter();
-    AnyObjectId objectId =
-        oi.insert(Constants.OBJ_BLOB, Constants.encode(content));
-    oi.flush();
-    addEntryToIndex(fileName, FileMode.REGULAR_FILE, objectId, repository);
-  }
-
-  /**
-   * It creates and adds a git link to git index of a repository.
-   *
-   * @param fileName The file name.
-   * @param objectId The sha-1 value of git link.
-   * @param repository The Repository instance.
-   * @throws IOException If an I/O exception occurs.
-   */
-  private void addGitLinkToIndex(final String fileName,
-      final AnyObjectId objectId, final Repository repository)
-      throws IOException {
-    addEntryToIndex(fileName, FileMode.GITLINK, objectId, repository);
-  }
-
-  /**
-   * It adds an entry to index.
-   *
-   * @param path The entry path.
-   * @param fileMode The entry file mode.
-   * @param objectId The ObjectId value of the entry.
-   * @param repository The repository instance.
-   * @throws IOException If an I/O exception occurs.
-   */
-  private void addEntryToIndex(final String path, final FileMode fileMode,
-      final AnyObjectId objectId, final Repository repository)
-      throws IOException {
-    final DirCacheEntry e = new DirCacheEntry(path);
-    e.setFileMode(fileMode);
-    e.setObjectId(objectId);
-
-    final DirCacheBuilder dirCacheBuilder = repository.lockDirCache().builder();
-    dirCacheBuilder.add(e);
-    dirCacheBuilder.commit();
-  }
-
-  private static StringBuilder buildSubmoduleSection(final String name,
-      final String path, final String url, final String branch) {
-    final StringBuilder sb = new StringBuilder();
-
-    sb.append("[submodule \"");
-    sb.append(name);
-    sb.append("\"]");
-    sb.append(newLine);
-
-    sb.append("\tpath = ");
-    sb.append(path);
-    sb.append(newLine);
-
-    sb.append("\turl = ");
-    sb.append(url);
-    sb.append(newLine);
-
-    sb.append("\tbranch = ");
-    sb.append(branch);
-    sb.append(newLine);
-
-    return sb;
-  }
-}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
index 2639cd2..0bd4f51 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/ProjectControlTest.java
@@ -33,8 +33,10 @@
 import com.google.gerrit.testutil.InMemoryDatabase;
 import com.google.gerrit.testutil.InMemoryModule;
 import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
+import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
@@ -48,7 +50,7 @@
 /** Unit tests for {@link ProjectControl}. */
 public class ProjectControlTest {
   @Inject private AccountManager accountManager;
-  @Inject private IdentifiedUser.RequestFactory userFactory;
+  @Inject private IdentifiedUser.GenericFactory userFactory;
   @Inject private InMemoryDatabase schemaFactory;
   @Inject private InMemoryRepositoryManager repoManager;
   @Inject private ProjectControl.GenericFactory projectControlFactory;
@@ -62,16 +64,17 @@
 
   @Before
   public void setUp() throws Exception {
-    lifecycle = new LifecycleManager();
-    Injector injector = InMemoryModule.createInjector(lifecycle);
+    Injector injector = Guice.createInjector(new InMemoryModule());
     injector.injectMembers(this);
+    lifecycle = new LifecycleManager();
+    lifecycle.add(injector);
     lifecycle.start();
 
     db = schemaFactory.open();
     schemaCreator.create(db);
     Account.Id userId = accountManager.authenticate(AuthRequest.forUser("user"))
         .getAccountId();
-    user = userFactory.create(userId);
+    user = userFactory.create(Providers.of(db), userId);
 
     Project.NameKey name = new Project.NameKey("project");
     InMemoryRepository inMemoryRepo = repoManager.createRepository(name);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 00712eb..9ad896e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -93,7 +93,7 @@
   @Inject protected AccountManager accountManager;
   @Inject protected ChangeInserter.Factory changeFactory;
   @Inject protected GerritApi gApi;
-  @Inject protected IdentifiedUser.RequestFactory userFactory;
+  @Inject protected IdentifiedUser.GenericFactory userFactory;
   @Inject protected InMemoryDatabase schemaFactory;
   @Inject protected InMemoryRepositoryManager repoManager;
   @Inject protected NotesMigration notesMigration;
@@ -109,12 +109,13 @@
 
   private String systemTimeZone;
 
-  protected abstract Injector createInjector(LifecycleManager lifecycle);
+  protected abstract Injector createInjector();
 
   @Before
   public void setUpInjector() throws Exception {
     lifecycle = new LifecycleManager();
-    Injector injector = createInjector(lifecycle);
+    Injector injector = createInjector();
+    lifecycle.add(injector);
     injector.injectMembers(this);
     lifecycle.start();
 
@@ -125,12 +126,13 @@
     Account userAccount = db.accounts().get(userId);
     userAccount.setPreferredEmail("user@example.com");
     db.accounts().update(ImmutableList.of(userAccount));
-    user = userFactory.create(userId);
+    user = userFactory.create(Providers.of(db), userId);
     requestContext.setContext(newRequestContext(userAccount.getId()));
   }
 
   private RequestContext newRequestContext(Account.Id requestUserId) {
-    final CurrentUser requestUser = userFactory.create(requestUserId);
+    final CurrentUser requestUser =
+        userFactory.create(Providers.of(db), requestUserId);
     return new RequestContext() {
       @Override
       public CurrentUser getCurrentUser() {
@@ -423,7 +425,8 @@
     assertQuery("topic:foo");
     assertQuery("topic:feature1", change1);
     assertQuery("topic:feature2", change2);
-    assertQuery("topic:\"\"", change3);
+    assertQuery("topic:feature", change2, change1);
+    assertQuery("topic:\"\"", change3, change2, change1);
   }
 
   @Test
@@ -1062,8 +1065,9 @@
 
     Change change = new Change(new Change.Key(key), id, ownerId,
         new Branch.NameKey(project, branch), TimeUtil.nowTs());
+    IdentifiedUser user = userFactory.create(Providers.of(db), ownerId);
     return changeFactory.create(
-        projectControlFactory.controlFor(project, userFactory.create(ownerId)),
+        projectControlFactory.controlFor(project, user),
         change,
         commit);
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
index 9888254f..5627d33 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.testutil.InMemoryModule;
+import com.google.inject.Guice;
 import com.google.inject.Injector;
 
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
@@ -27,10 +27,10 @@
 
 public class LuceneQueryChangesTest extends AbstractQueryChangesTest {
   @Override
-  protected Injector createInjector(LifecycleManager lifecycle) {
+  protected Injector createInjector() {
     Config luceneConfig = new Config(config);
     InMemoryModule.setDefaults(luceneConfig);
-    return InMemoryModule.createInjector(lifecycle, luceneConfig);
+    return Guice.createInjector(new InMemoryModule(luceneConfig));
   }
 
   @Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java
index ce5310a..0feb800 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV14Test.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.testutil.InMemoryModule;
+import com.google.inject.Guice;
 import com.google.inject.Injector;
 
 import org.eclipse.jgit.lib.Config;
@@ -24,12 +24,12 @@
 
 public class LuceneQueryChangesV14Test extends LuceneQueryChangesTest {
   @Override
-  protected Injector createInjector(LifecycleManager lifecycle) {
+  protected Injector createInjector() {
     Config luceneConfig = new Config(config);
     InMemoryModule.setDefaults(luceneConfig);
     // Latest version with a Lucene 4 index.
     luceneConfig.setInt("index", "lucene", "testVersion", 14);
-    return InMemoryModule.createInjector(lifecycle, luceneConfig);
+    return Guice.createInjector(new InMemoryModule(luceneConfig));
   }
 
   @Override
@@ -45,4 +45,11 @@
   public void byFrom() {
     // Ignore.
   }
+
+  @Override
+  @Ignore
+  @Test
+  public void byTopic() {
+    // Ignore.
+  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
index 970638e..d4398cd 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
@@ -62,7 +62,7 @@
   @Before
   public void setUp() throws Exception {
     lifecycle = new LifecycleManager();
-    InMemoryModule.createInjector(lifecycle).injectMembers(this);
+    new InMemoryModule().inject(this);
     lifecycle.start();
   }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
index d87888f..2b8f522 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.util;
 
+import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.createStrictMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
@@ -23,7 +24,8 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
 
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.lib.BlobBasedConfig;
@@ -33,16 +35,14 @@
 
 import java.net.URI;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
-import java.util.TreeSet;
 
 public class SubmoduleSectionParserTest extends LocalDiskRepositoryTestCase {
   private static final String THIS_SERVER = "localhost";
-  private GitRepositoryManager repoManager;
+  private ProjectCache projectCache;
   private BlobBasedConfig bbc;
 
   @Override
@@ -50,16 +50,16 @@
   public void setUp() throws Exception {
     super.setUp();
 
-    repoManager = createStrictMock(GitRepositoryManager.class);
+    projectCache = createStrictMock(ProjectCache.class);
     bbc = createStrictMock(BlobBasedConfig.class);
   }
 
   private void doReplay() {
-    replay(repoManager, bbc);
+    replay(projectCache, bbc);
   }
 
   private void doVerify() {
-    verify(repoManager, bbc);
+    verify(projectCache, bbc);
   }
 
   @Test
@@ -214,13 +214,12 @@
                 projectNameCandidate.length() - Constants.DOT_GIT_EXT.length());
           }
           if (projectNameCandidate.equals(reposToBeFound.get(id))) {
-            expect(repoManager.list()).andReturn(
-                new TreeSet<>(Collections.singletonList(
-                    new Project.NameKey(projectNameCandidate))));
+            expect(projectCache.get(new Project.NameKey(projectNameCandidate)))
+                .andReturn(createNiceMock(ProjectState.class));
             break;
           } else {
-            expect(repoManager.list()).andReturn(
-                new TreeSet<>(Collections.<Project.NameKey> emptyList()));
+            expect(projectCache.get(new Project.NameKey(projectNameCandidate)))
+                .andReturn(null);
           }
         }
       }
@@ -229,8 +228,8 @@
     doReplay();
 
     final SubmoduleSectionParser ssp =
-        new SubmoduleSectionParser(bbc, THIS_SERVER, superProjectBranch,
-            repoManager);
+        new SubmoduleSectionParser(projectCache, bbc, THIS_SERVER,
+            superProjectBranch);
 
     List<SubmoduleSubscription> returnedSubscriptions = ssp.parseAllSections();
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
new file mode 100644
index 0000000..b8d5cd2
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
@@ -0,0 +1,119 @@
+// 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.testutil;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.EmailHeader;
+import com.google.gerrit.server.mail.EmailSender;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Email sender implementation that records messages in memory.
+ * <p>
+ * This class is mostly threadsafe. The only exception is that not all {@link
+ * EmailHeader} subclasses are immutable. In particular, if a caller holds a
+ * reference to an {@code AddressList} and mutates it after sending, the message
+ * returned by {@link #getMessages()} may or may not reflect mutations.
+ */
+@Singleton
+public class FakeEmailSender implements EmailSender {
+  private static final Logger log =
+      LoggerFactory.getLogger(FakeEmailSender.class);
+
+  public static class Module extends AbstractModule {
+    @Override
+    public void configure() {
+      bind(EmailSender.class).to(FakeEmailSender.class);
+    }
+  }
+
+  @AutoValue
+  public abstract static class Message {
+    private static Message create(Address from, Collection<Address> rcpt,
+        Map<String, EmailHeader> headers, String body) {
+      return new AutoValue_FakeEmailSender_Message(from,
+          ImmutableList.copyOf(rcpt), ImmutableMap.copyOf(headers), body);
+    }
+
+    public abstract Address from();
+    public abstract ImmutableList<Address> rcpt();
+    public abstract ImmutableMap<String, EmailHeader> headers();
+    public abstract String body();
+  }
+
+  private final WorkQueue workQueue;
+  private final List<Message> messages;
+
+  @Inject
+  FakeEmailSender(WorkQueue workQueue) {
+    this.workQueue = workQueue;
+    messages = Collections.synchronizedList(new ArrayList<Message>());
+  }
+
+  @Override
+  public boolean isEnabled() {
+    return true;
+  }
+
+  @Override
+  public boolean canEmail(String address) {
+    return true;
+  }
+
+  @Override
+  public void send(Address from, Collection<Address> rcpt,
+      Map<String, EmailHeader> headers, String body) throws EmailException {
+    messages.add(Message.create(from, rcpt, headers, body));
+  }
+
+  public ImmutableList<Message> getMessages() {
+    waitForEmails();
+    synchronized (messages) {
+      return ImmutableList.copyOf(messages);
+    }
+  }
+
+  private void waitForEmails() {
+    // TODO(dborowitz): This is brittle; consider forcing emails to use
+    // a single thread in tests (tricky because most callers just use the
+    // default executor).
+    for (WorkQueue.Task<?> task : workQueue.getTasks()) {
+      if (task.toString().contains("send-email")) {
+        try {
+          task.get();
+        } catch (ExecutionException | InterruptedException e) {
+          log.warn("error finishing email task", e);
+        }
+      }
+    }
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
index ed1b0df..1f5b6cd 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
@@ -26,7 +26,9 @@
 import com.google.gwtorm.jdbc.SimpleDataSource;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Guice;
 import com.google.inject.Inject;
+import com.google.inject.Injector;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
@@ -47,8 +49,9 @@
  */
 public class InMemoryDatabase implements SchemaFactory<ReviewDb> {
   public static InMemoryDatabase newDatabase(LifecycleManager lifecycle) {
-    return InMemoryModule.createInjector(lifecycle)
-        .getInstance(InMemoryDatabase.class);
+    Injector injector = Guice.createInjector(new InMemoryModule());
+    lifecycle.add(injector);
+    return injector.getInstance(InMemoryDatabase.class);
   }
 
   private static int dbCnt;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 2ae16f6..c89df6b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -17,16 +17,13 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.inject.Scopes.SINGLETON;
 
-import com.google.common.net.InetAddresses;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.DisabledChangeHooks;
-import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.GerritPersonIdentProvider;
-import com.google.gerrit.server.RemotePeer;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
@@ -43,13 +40,13 @@
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.config.TrackingFootersProvider;
 import com.google.gerrit.server.git.EmailReviewCommentsExecutor;
+import com.google.gerrit.server.git.GarbageCollection;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.PerThreadRequestScope;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.index.ChangeSchemas;
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
-import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.patch.DiffExecutor;
 import com.google.gerrit.server.schema.DataSourceType;
 import com.google.gerrit.server.schema.SchemaCreator;
@@ -74,8 +71,6 @@
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.concurrent.ExecutorService;
@@ -93,7 +88,6 @@
     cfg.setString("gerrit", null, "allProjects", "Test-Projects");
     cfg.setString("user", null, "name", "Gerrit Code Review");
     cfg.setString("user", null, "email", "gerrit@localhost");
-    cfg.setBoolean("sendemail", null, "enable", false);
     cfg.setString("cache", null, "directory", null);
     cfg.setString("index", null, "type", "lucene");
     cfg.setBoolean("index", "lucene", "testInmemory", true);
@@ -101,33 +95,13 @@
         ChangeSchemas.getLatest().getVersion());
   }
 
-  public static Injector createInjector(LifecycleManager lifecycle) {
-    return createInjector(lifecycle, newDefaultConfig());
-  }
-
-  public static Injector createInjector(LifecycleManager lifecycle,
-      Config cfg) {
-    Injector sysInjector = Guice.createInjector(new InMemoryModule(cfg));
-    Injector testInjector = sysInjector.createChildInjector(
-        new AbstractModule() {
-          @Override
-          public void configure() {
-            // These bindings must be in a child injector, because
-            // ChangeMergeQueue creates its own child injector with conflicting
-            // bindings.
-            InetSocketAddress addr = new InetSocketAddress(
-                InetAddresses.forString("127.0.0.1"), 1234);
-            bind(SocketAddress.class).annotatedWith(RemotePeer.class)
-                .toInstance(addr);
-          }
-        });
-    lifecycle.add(sysInjector, testInjector);
-    return testInjector;
-  }
-
   private final Config cfg;
 
-  private InMemoryModule(Config cfg) {
+  public InMemoryModule() {
+    this(newDefaultConfig());
+  }
+
+  public InMemoryModule(Config cfg) {
     this.cfg = cfg;
   }
 
@@ -137,6 +111,13 @@
 
   @Override
   protected void configure() {
+    // Do NOT bind @RemotePeer, as it is bound in a child injector of
+    // ChangeMergeQueue (bound via GerritGlobalModule below), so there cannot be
+    // a binding in the parent injector. If you need @RemotePeer, you must bind
+    // it in a child injector of the one containing InMemoryModule. But unless
+    // you really need to test something request-scoped, you likely don't
+    // actually need it.
+
     // For simplicity, don't create child injectors, just use this one to get a
     // few required modules.
     Injector cfgInjector = Guice.createInjector(new AbstractModule() {
@@ -147,6 +128,7 @@
       }
     });
     install(cfgInjector.getInstance(GerritGlobalModule.class));
+    factory(GarbageCollection.Factory.class);
 
     bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
 
@@ -197,7 +179,7 @@
       }
     });
     install(new DefaultCacheFactory.Module());
-    install(new SmtpEmailSender.Module());
+    install(new FakeEmailSender.Module());
     install(new SignedTokenEmailTokenVerifier.Module());
 
     IndexType indexType = null;
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 5da154a..ad242eb 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -39,7 +39,7 @@
 import com.google.gerrit.server.config.SitePath;
 import com.google.gerrit.server.contact.ContactStoreModule;
 import com.google.gerrit.server.contact.HttpContactStoreConnection;
-import com.google.gerrit.server.git.GarbageCollectionRunner;
+import com.google.gerrit.server.git.GarbageCollectionModule;
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
 import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
 import com.google.gerrit.server.git.WorkQueue;
@@ -320,7 +320,7 @@
         bind(GerritOptions.class).toInstance(new GerritOptions(false, false));
       }
     });
-    modules.add(GarbageCollectionRunner.module());
+    modules.add(new GarbageCollectionModule());
     return cfgInjector.createChildInjector(modules);
   }