Merge "Highlight dependencies to abandoned changes by red background"
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 7027562..879d1ac 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -446,7 +446,7 @@
 
 [[category_abandon]]
 Abandon
-~~~~
+~~~~~~~
 
 This category controls whether users are allowed to abandon changes
 to projects in Gerrit. It can give permission to abandon a specific
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index fd49ae5..3895b90 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -129,6 +129,9 @@
 link:cmd-plugin-install.html[gerrit plugin add]::
     Alias for 'gerrit plugin install'.
 
+link:cmd-plugin-enable.html[gerrit plugin enable]::
+    Enable plugins.
+
 link:cmd-plugin-install.html[gerrit plugin install]::
     Install/Add a plugin.
 
diff --git a/Documentation/cmd-plugin-enable.txt b/Documentation/cmd-plugin-enable.txt
new file mode 100644
index 0000000..da651ca
--- /dev/null
+++ b/Documentation/cmd-plugin-enable.txt
@@ -0,0 +1,44 @@
+plugin enable
+=============
+
+NAME
+----
+plugin enable - Enable plugins.
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit plugin enable'
+  <NAME> ...
+
+DESCRIPTION
+-----------
+Enable plugins currently disabled. The plugins will be enabled by renaming
+the plugin jars in the site path's `plugins` directory from
+`<plugin-jar-name>.disabled` to `<plugin-jar-name>`.
+
+ACCESS
+------
+Caller must be a member of the privileged 'Administrators' group.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+OPTIONS
+-------
+<NAME>::
+	Name of the plugin that should be enabled.  Multiple names of
+	plugins that should be enabled may be specified.
+
+EXAMPLES
+--------
+Enable a plugin:
+
+====
+	ssh -p 29418 localhost gerrit plugin enable my-plugin
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-plugin-reload.txt b/Documentation/cmd-plugin-reload.txt
index 8c22afd..3932e30 100644
--- a/Documentation/cmd-plugin-reload.txt
+++ b/Documentation/cmd-plugin-reload.txt
@@ -13,8 +13,13 @@
 
 DESCRIPTION
 -----------
-Reload/Restart plugins. Whether a plugin is reloaded or restarted
-is defined by the plugin's link:dev-plugins.html#reload_method[reload method].
+Reload/Restart plugins.
+
+Whether a plugin is reloaded or restarted is defined by the plugin's
+link:dev-plugins.html#reload_method[reload method].
+
+E.g. a plugin needs to be reloaded if its configuration is modified to
+make the new configuration data become active.
 
 ACCESS
 ------
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index fdb5273..513bc6e 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -53,15 +53,18 @@
 --force-message::
 	Option which allows Gerrit to publish the --message, even
 	when the labels could not be applied due to the change being
-	closed).
+	closed.
 +
 Used by some scripts/CI-systems, where the results (or links
 to the result) are posted as a message after completion of a
 build (often together with a label-change, indicating the success
 of the build).
 +
-If the message is posted successfully, the cmd will return
+If the message is posted successfully, the command will return
 successfully, even if the label could not be changed.
++
+This option will not force the message to be posted if the command
+fails because the user is not permitted to change the label.
 
 --help::
 -h::
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 8610dba..5606768 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1796,6 +1796,18 @@
 +
 Defaults to the number of available CPUs according to the Java runtime.
 
+[[receive.changeUpdateThreads]]receive.changeUpdateThreads::
++
+Number of threads to perform change creation or patch set updates
+concurrently. Each thread uses its own database connection from
+the database connection pool, and if all threads are busy then
+main receive thread will also perform a change creation or patch
+set update.
++
+Defaults to 1, using only the main receive thread. This feature is for
+databases with very high latency that can benfit from concurrent
+operations when multiple changes are impacted at once.
+
 [[receive.timeout]]receive.timeout::
 +
 Overall timeout on the time taken to process the change data in
@@ -2255,7 +2267,7 @@
 Background color used for patch outdated messages.  The value must be
 a valid HTML hex color code, or standard color name.
 +
-By default a shade of red, `FF0000`.
+By default a shade of red, `F08080`.
 
 [[theme.tableOddRowColor]]theme.tableOddRowColor::
 +
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index a19d85e..b2bf011 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -70,6 +70,7 @@
 * Change Save as to be Local file.
 
 
+[[hosted-mode]]
 Running Hosted Mode
 ~~~~~~~~~~~~~~~~~~~
 
@@ -100,6 +101,7 @@
 * Change Save as to be Local file.
 
 
+[[known-problems]]
 Known problems
 --------------
 
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 040a370..dd0c44c 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -7,12 +7,14 @@
 Depending on how tightly the extension code is coupled with the Gerrit
 server code, there is a distinction between `plugins` and `extensions`.
 
+[[plugin]]
 A `plugin` in Gerrit is tightly coupled code that runs in the same
 JVM as Gerrit. It has full access to all server internals. Plugins
 are tightly coupled to a specific major.minor server version and
 may require source code changes to compile against a different
 server version.
 
+[[extension]]
 An `extension` in Gerrit runs inside of the same JVM as Gerrit
 in the same way as a plugin, but has limited visibility to the
 server's internals. The limited visibility reduces the extension's
@@ -21,17 +23,55 @@
 
 Most of this documentation refers to either type as a plugin.
 
-Requirements
-------------
+[[getting-started]]
+Getting started
+---------------
 
-To start development, clone the sample Maven project:
+To get started with the development of a plugin there are two
+recommended ways:
 
+. use the Gerrit Plugin Maven archetype to create a new plugin project:
++
+With the Gerrit Plugin Maven archetype you can create a skeleton for a
+plugin project.
++
+----
+mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
+    -DarchetypeArtifactId=gerrit-plugin-archetype \
+    -DarchetypeVersion=2.5-SNAPSHOT \
+    -DgroupId=com.google.gerrit \
+    -DartifactId=testPlugin
+----
++
+Maven will ask for additional properties and then create the plugin in
+the current directory. To change the default property values answer 'n'
+when Maven asks to confirm the properties configuration. It will then
+ask again for all properties including those with predefined default
+values.
+
+. clone the sample helloworld plugin:
++
+This is a Maven project that adds an SSH command to Gerrit to print
+out a hello world message. It can be taken as an example to develop
+an own plugin.
++
 ----
 $ git clone https://gerrit.googlesource.com/plugins/helloworld
 ----
++
+When starting from this example one should take care to adapt the
+`Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which
+the plugin is developed. If the plugin is developed for a released
+Gerrit version (no `SNAPSHOT` version) then the URL for the
+`gerrit-api-repository` in the `pom.xml` needs to be changed to
+`https://gerrit-api.commondatastorage.googleapis.com/release/`.
 
-This project includes the dependencies file that matches the war file to
-develop against. Dependencies are offered in two different formats:
+[[API]]
+API
+---
+
+There are two different API formats offered against which plugins can
+be developed:
 
 gerrit-extension-api.jar::
   A stable but thin interface. Suitable for extensions that need
@@ -136,6 +176,7 @@
 To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
 command can be used.
 
+[[classpath]]
 Classpath
 ---------
 
@@ -151,6 +192,7 @@
 to package additional dependencies. Relocating (or renaming) classes
 should not be necessary due to the ClassLoader isolation.
 
+[[ssh]]
 SSH Commands
 ------------
 
@@ -205,6 +247,7 @@
 $ ssh -p 29418 review.example.com helloworld print
 ----
 
+[[http]]
 HTTP Servlets
 -------------
 
@@ -231,9 +274,10 @@
   }
 ====
 
-If explicit registration is being used, a Guice ServletModule must
-be supplied to register the HTTP servlets, and the module must be
-declared in the manifest with the `Gerrit-HttpModule` attribute:
+The auto registration only works for standard servlet mappings like
+`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
+to register the HTTP servlets and declare it explicitly in the manifest
+with the `Gerrit-HttpModule` attribute:
 
 ====
   import com.google.inject.servlet.ServletModule;
@@ -252,6 +296,24 @@
 $ curl http://review.example.com/plugins/helloworld/print
 ----
 
+[[data-directory]]
+Data Directory
+--------------
+
+Plugins can request a data directory with a `@PluginData` File
+dependency. A data directory will be created automatically by the
+server in `$site_path/data/$plugin_name` and passed to the plugin.
+
+Plugins can use this to store any data they want.
+
+====
+  @Inject
+  MyType(@PluginData java.io.File myDir) {
+    new FileInputStream(new File(myDir, "my.config"));
+  }
+====
+
+[[documentation]]
 Documentation
 -------------
 
@@ -270,6 +332,7 @@
 if the file name ends with `.md`. Gerrit will automatically convert
 Markdown to HTML if accessed with extension `.html`.
 
+[[macros]]
 Within the Markdown documentation files macros can be used that allow
 to write documentation with reasonably accurate examples that adjust
 automatically based on the installation.
@@ -291,6 +354,7 @@
 Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
 even if there is an expansion for `KEEP` in the future.
 
+[[auto-index]]
 Automatic Index
 ~~~~~~~~~~~~~~~
 
@@ -331,6 +395,7 @@
 |API Version | Gerrit-ApiVersion
 |===================================================
 
+[[deployment]]
 Deployment
 ----------
 
@@ -347,6 +412,9 @@
 For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
 command can be used.
 
+Disabled plugins can be re-enabled using the
+link:cmd-plugin-enable.html[plugin enable] command.
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-release-subproject.txt b/Documentation/dev-release-subproject.txt
index f686d0c..799ff2d 100644
--- a/Documentation/dev-release-subproject.txt
+++ b/Documentation/dev-release-subproject.txt
@@ -1,11 +1,11 @@
-Making a Gerrit Sub Project Release
-===================================
+Making a Release of a Gerrit Subproject / Core Plugin
+=====================================================
 
-Preparing a New Gerrit Subproject Snapshot for Publishing
----------------------------------------------------------
+Preparing a New Snapshot for Publishing
+---------------------------------------
 
-* You will need to have the following in the pom.xml to make it
-  deployable to the gerrit-maven storage bucket:
+* You will need to have the following in the `pom.xml` to make it
+  deployable to the `gerrit-maven` storage bucket:
 
 ----
   <distributionManagement>
@@ -19,7 +19,7 @@
 ----
 
 
-* Add this to the pom.xml to enable the wagon provider:
+* Add this to the `pom.xml` to enable the wagon provider:
 
 ----
   <build>
@@ -34,7 +34,7 @@
 ----
 
 
-* Add your username and password to your ~/.m2/settings.xml file.
+* Add your username and password to your `~/.m2/settings.xml` file.
   These need to come from the link:https://code.google.com/apis/console/[API Console].
 
 ----
@@ -52,11 +52,14 @@
 ----
 
 
-Making a Gerrit Subproject Snapshot
------------------------------------
+Making a Snapshot
+-----------------
 
-* First build and deploy the latest snapshot and ensure that Gerrit builds
-with this snapshot
+* Only for plugins: in the `pom.xml` update the Gerrit version under
+`properties` > `Gerrit-ApiVersion` to the version of the new Gerrit
+release
+* First build and deploy the latest snapshot and ensure that Gerrit
+builds/runs with this snapshot
 
 * Deploy the snapshot:
 
@@ -65,15 +68,17 @@
 ====
 
 
-Making a Gerrit Subproject Release
-----------------------------------
+Making a Release
+----------------
 
-* First deploy (and test) the latest snapshot for the subproject
+* First deploy (and test) the latest snapshot for the subproject/plugin
 
-* Update the top level pom.xml in the subproject to reflect the new project
-version (the exact value of the tag you will create below)
+* Update the top level `pom.xml` in the subproject/plugin to reflect
+the new project version (the exact value of the tag you will create
+below)
 
-* Commit the pom change and push to the project's repo refs/for/<master/stable>
+* Commit the pom change and push to the project's repo
+`refs/for/<master/stable>`
 
 * Tag the version you just pushed (and push the tag)
 
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index e3a93cc..5ea3042 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -78,16 +78,19 @@
 Prepare Gerrit
 ~~~~~~~~~~~~~~
 
-* In the 'stable-2.5' branch: Update the top level pom in Gerrit to ensure that
-none of the Subprojects point to snapshot releases
+* Create a `stable-2.5` branch for making the new release
 
-* In the 'master' branch: Update the poms for the Gerrit version, push for
+* In the `master` branch: Update the poms for the Gerrit version, push for
 review, get merged
 
 ====
  tools/version.sh --snapshot=2.5
 ====
 
+* Checkout the `stable-2.5` branch
+* Update the top level `pom.xml` in Gerrit to ensure that none of the
+Subprojects point to snapshot releases
+
 * Tag
 
 ====
@@ -95,14 +98,36 @@
  git tag -a -m "gerrit 2.5" v2.5
 ====
 
-* Build
+* Build (without plugins)
 
 ====
  ./tools/release.sh
 ====
 
-* Sanity check WAR
+[[plugin-api]]
+Publish the Plugin API JAR File
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+* Push JAR to `commondatastorage.googleapis.com`
+** Run `tools/deploy_api.sh`
+
+Prepare the Core Plugins
+~~~~~~~~~~~~~~~~~~~~~~~~
+* link:dev-release-subproject.html[Release and publish] the core plugins
+
+Package Gerrit with Plugins
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* Ensure that the core plugins listed in `gerrit-package-plugins/pom.xml`
+point to the latest release version (no dependency to snapshot versions)
+* Include core plugins into WAR
+====
+ $ ./tools/version.sh --release && mvn clean package -f gerrit-package-plugins/pom.xml
+ $ ./tools/version.sh --reset
+====
+
+* Find WAR that includes the core plugins at
+`gerrit-package-plugins\target\gerrit-full-v2.5.war`
+* Sanity check WAR
 
 Publish to the Project Locations
 --------------------------------
@@ -118,12 +143,6 @@
 ** new war: [release-candidate], featured...
 ** old war: deprecated
 
-Plugin API JAR File
-~~~~~~~~~~~~~~~~~~~
-
-* Push JAR to commondatastorage.googleapis.com
-** Run tools/deploy_api.sh
-
 Tag
 ~~~
 
diff --git a/Documentation/index.txt b/Documentation/index.txt
index c99d26c..cde9d50 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -19,8 +19,10 @@
 * link:access-control.html[Access Controls]
 * link:error-messages.html[Error Messages]
 * link:rest-api.html[REST API]
+* link:user-custom-dashboards.html[Custom Dashboards]
 * link:user-notify.html[Subscribing to Email Notifications]
 * link:user-submodules.html[Subscribing to Git Submodules]
+* link:refs-notes-review.html[The `refs/notes/review` namespace]
 
 Installation
 ------------
diff --git a/Documentation/json.txt b/Documentation/json.txt
index 32bfed5..58e8e8f 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -124,6 +124,16 @@
 
 refName:: Ref name within project.
 
+[[queryLimit]]
+queryLimit
+----------
+Information about the link:access-control.html#capability_queryLimit[queryLimit]
+of a user.
+
+min:: lower limit
+
+max:: upper limit
+
 SEE ALSO
 --------
 
diff --git a/Documentation/pgm-ExportReviewNotes.txt b/Documentation/pgm-ExportReviewNotes.txt
index b7670db..1b00213 100644
--- a/Documentation/pgm-ExportReviewNotes.txt
+++ b/Documentation/pgm-ExportReviewNotes.txt
@@ -3,7 +3,7 @@
 
 NAME
 ----
-ExportReviewNotes - Export successful reviews to refs/notes/review
+ExportReviewNotes - Export successful reviews to link:refs-notes-review.html[refs/notes/review]
 
 SYNOPSIS
 --------
diff --git a/Documentation/refs-notes-review.txt b/Documentation/refs-notes-review.txt
new file mode 100644
index 0000000..632f567
--- /dev/null
+++ b/Documentation/refs-notes-review.txt
@@ -0,0 +1,111 @@
+The refs/notes/review namespace
+===============================
+
+Summary
+-------
+
+`refs/notes/review` is a special reference that Gerrit creates on repositories
+to store information about code reviews.
+
+When a repository is cloned from Gerrit, the `refs/notes/review` reference is
+not included by default.  It has to be manually fetched:
+
+====
+  $ git fetch origin refs/notes/review:refs/notes/review
+====
+
+It is also possible to
+link:http://www.kernel.org/pub/software/scm/git/docs/git-config.html[configure git]
+to always fetch `refs/notes/review`:
+
+====
+  $ git config --add remote.origin.fetch refs/notes/review:refs/notes/review
+  $ git fetch
+====
+
+When `refs/notes/review` is fetched on a repository, the Gerrit review
+information can be included in the git log output:
+
+====
+   $ git log --show-notes=review
+====
+
+Content of refs/notes/review
+----------------------------
+
+For each commit, Gerrit stores the following review information in
+`refs/notes/review`:
+
+[[submitted_by]]
+Submitted-by
+~~~~~~~~~~~~
+
+The name and email address of the Gerrit user that submitted the change in
+link:http://www.ietf.org/rfc/rfc2822.txt[RFC 2822] format.
+
+====
+  Submitted-by: Random J Developer <random@developer.example.org>
+====
+
+[[submitted_at]]
+Submitted-at
+~~~~~~~~~~~~
+
+The time the commit was submitted in RFC 2822 time stamp format.
+
+====
+  Submitted-at: Mon, 25 Jun 2012 16:15:57 +0200
+====
+
+[[reviewed_on]]
+Reviewed-on
+~~~~~~~~~~~
+
+The URL to the change on the Gerrit server.
+
+====
+  Reviewed-on: http://path.to.gerrit/12345
+====
+
+[[review_scores]]
+Review Labels and Scores
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Review label and score, and the name and email address of the Gerrit user that
+gave it in RFC 2822 format:
+
+====
+  Code-Review+2: A. N. Other <another@developer.example.org>
+  Verified+1: A. N. Other <another@developer.example.org>
+====
+
+Commonly used review labels are "Code-Review" and "Verified", but any label
+configured in Gerrit can be included.
+
+All review labels and scores present on the change at the time of submit are
+included.
+
+[[project]]
+Project
+~~~~~~~
+
+The name of the project in which the commit was made.
+
+====
+  Project: kernel/common
+====
+
+[[branch]]
+Branch
+~~~~~~
+
+The name of the branch on which the commit was made.
+
+====
+  Branch: refs/heads/master
+====
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index 1a359ac..2f9d03f 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -8,6 +8,7 @@
 Protocol Details
 ----------------
 
+[[authentication]]
 Authentication
 ~~~~~~~~~~~~~~
 By default all REST endpoints assume anonymous access and filter
@@ -20,6 +21,7 @@
 prefix the endpoint URL with `/a/`. For example to authenticate to
 `/projects/` request URL `/a/projects/`.
 
+[[output]]
 Output Format
 ~~~~~~~~~~~~~
 Most APIs return text format by default. JSON can be requested
@@ -58,8 +60,8 @@
 [[accounts_self_capabilities]]
 /accounts/self/capabilities (Account Capabilities)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Returns the global capabilities (such as createProject or
-createGroup) that are enabled for the calling user. This can be used
+Returns the global capabilities (such as `createProject` or
+`createGroup`) that are enabled for the calling user. This can be used
 by UI tools to discover if administrative features are available
 to the caller, so they can hide (or show) relevant UI actions.
 
@@ -99,6 +101,25 @@
   }
 ----
 
+To filter the set of global capabilities the `q` parameter can be used.
+Filtering may decrease the response time by avoiding looking at every
+possible alternative for the caller.
+
+----
+  GET /a/accounts/self/capabilities?format=JSON&q=createAccount&q=createGroup HTTP/1.0
+  Authorization: Digest username="admin", realm="Gerrit Code Review", nonce="...
+
+  )]}'
+  {
+    "createAccount": true,
+    "createGroup": true
+  }
+----
+
+Most results are boolean, and a field is only present when its value
+is `true`. link:json.html#queryLimit[`queryLimit`] is a range and is
+presented as a nested JSON object with `min` and `max` members.
+
 [[projects]]
 /projects/ (List Projects)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -128,6 +149,244 @@
   }
 ----
 
+[[suggest-projects]]
+The `/projects/` URL also accepts a prefix string as part of the URL.
+This limits the results to those projects that start with the specified
+prefix.
+List all projects that start with `platform/`:
+----
+GET /projects/platform/?format=JSON HTTP/1.0
+HTTP/1.1 200 OK
+Content-Disposition: attachment
+Content-Type: application/json;charset=UTF-8
+)]}'
+{
+"platform/drivers": {},
+"platform/tools": {}
+}
+----
+E.g. this feature can be used by suggestion client UI's to limit results.
+
+[[changes]]
+/changes/ (Query Changes)
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Queries changes visible to the caller. The query string must be
+provided by the `q` parameter. The `n` parameter can be used to limit
+the returned results.
+
+Query for open changes of watched projects:
+----
+  GET /changes/?format=JSON&q=status:open+is:watched&n=2 HTTP/1.0
+
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "project": "demo",
+    "branch": "master",
+    "id": "Idaf5e098d70898b7119f6f4af5a6c13343d64b57",
+    "subject": "One change",
+    "status": "NEW",
+    "created": "2012-07-17 07:18:30.854000000",
+    "updated": "2012-07-17 07:19:27.766000000",
+    "reviewed": true,
+    "_sortkey": "001e7057000006dc",
+    "_number": 1756,
+    "owner": {
+      "name": "John Doe"
+    },
+  },
+  {
+    "project": "demo",
+    "branch": "master",
+    "id": "I09c8041b5867d5b33170316e2abc34b79bbb8501",
+    "subject": "Another change",
+    "status": "NEW",
+    "created": "2012-07-17 07:18:30.884000000",
+    "updated": "2012-07-17 07:18:30.885000000",
+    "_sortkey": "001e7056000006dd",
+    "_number": 1757,
+    "owner": {
+      "name": "John Doe"
+    },
+    "_more_changes": true
+  }
+----
+
+The change output is sorted by the last update time, most recently
+updated to oldest update.
+
+If the `n` query parameter is supplied and additional changes exist
+that match the query beyond the end, the last change object has a
+`_more_changes: true` JSON field set. Callers can resume a query with
+the `n` query parameter, supplying the last change's `_sortkey` field
+as the value. When going in the reverse direction with the `p` query
+parameter a `_more_changes: true` is put in the first change object if
+there are results *before* the first change returned.
+
+Clients are allowed to specify more than one query by setting the `q`
+parameter multiple times. In this case the result is an array of
+arrays, one per query in the same order the queries were given in.
+
+Query that retrieves changes for a user's dashboard:
+----
+  GET /changes/?format=JSON&q=is:open+owner:self&q=is:open+reviewer:self+-owner:self&q=is:closed+owner:self+limit:5&o=LABELS HTTP/1.0
+
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  [
+    [
+      {
+        "project": "demo",
+        "branch": "master",
+        "id": "Idaf5e098d70898b7119f6f4af5a6c13343d64b57",
+        "subject": "One change",
+        "status": "NEW",
+        "created": "2012-07-17 07:18:30.854000000",
+        "updated": "2012-07-17 07:19:27.766000000",
+        "reviewed": true,
+        "_sortkey": "001e7057000006dc",
+        "_number": 1756,
+        "owner": {
+          "name": "John Doe"
+        },
+        "labels": {
+          "Verified": {},
+          "Code-Review": {}
+        }
+      }
+    ],
+    [],
+    []
+  ]
+----
+
+Additional fields can be obtained by adding `o` parameters, each
+option requires more database lookups and slows down the query
+response time to the client so they are generally disabled by
+default. Optional fields are:
+
+* `LABELS`: a summary of each label required for submit, and
+  approvers that have granted (or rejected) with that label.
+
+* `CURRENT_REVISION`: describe the current revision (patch set)
+  of the change, including the commit SHA-1 and URLs to fetch from.
+
+* `ALL_REVISIONS`: describe all revisions, not just current.
+
+* `CURRENT_COMMIT`: parse and output all header fields from the
+  commit object, including message. Only valid when the current
+  revision or all revisions are selected.
+
+* `ALL_COMMITS`: parse and output all header fields from the
+  output revisions. If only `CURRENT_REVISION` was requested
+  then only the current revision's commit data will be output.
+
+* `CURRENT_FILES`: list files modified by the commit, including
+  basic line counts inserted/deleted per file. Only valid when
+  the current revision or all revisions are selected.
+
+* `ALL_FILES`: list files modified by the commit, including
+  basic line counts inserted/deleted per file. If only the
+  `CURRENT_REVISION` was requested the only that commit's
+  modified files will be output.
+
+----
+  GET /changes/?q=97&format=JSON&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES HTTP/1.0
+
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  [
+    {
+      "project": "gerrit",
+      "branch": "master",
+      "id": "I7ea46d2e2ee5c64c0d807677859cfb7d90b8966a",
+      "subject": "Use an EventBus to manage star icons",
+      "status": "NEW",
+      "created": "2012-04-25 00:52:25.580000000",
+      "updated": "2012-04-25 00:52:25.586000000",
+      "_sortkey": "001c9bf400000061",
+      "_number": 97,
+      "owner": {
+        "name": "Shawn Pearce"
+      },
+      "current_revision": "184ebe53805e102605d11f6b143486d15c23a09c",
+      "revisions": {
+        "184ebe53805e102605d11f6b143486d15c23a09c": {
+          "_number": 1,
+          "fetch": {
+            "git": {
+              "url": "git://localhost/gerrit",
+              "ref": "refs/changes/97/97/1"
+            },
+            "http": {
+              "url": "http://127.0.0.1:8080/gerrit",
+              "ref": "refs/changes/97/97/1"
+            }
+          },
+          "commit": {
+            "parents": [
+              {
+                "commit": "1eee2c9d8f352483781e772f35dc586a69ff5646",
+                "subject": "Migrate contributor agreements to All-Projects."
+              }
+            ],
+            "author": {
+              "name": "Shawn O. Pearce",
+              "email": "sop@google.com",
+              "date": "2012-04-24 18:08:08.000000000",
+              "tz": -420
+            },
+            "committer": {
+              "name": "Shawn O. Pearce",
+              "email": "sop@google.com",
+              "date": "2012-04-24 18:08:08.000000000",
+              "tz": -420
+            },
+            "subject": "Use an EventBus to manage star icons",
+            "message": "Use an EventBus to manage star icons\n\nImage widgets that need to ..."
+          },
+          "files": {
+            "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java": {
+              "lines_deleted": 8
+            },
+            "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java": {
+              "lines_inserted": 1
+            },
+            "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java": {
+              "lines_inserted": 11,
+              "lines_deleted": 19
+            },
+            "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java": {
+              "lines_inserted": 23,
+              "lines_deleted": 20
+            },
+            "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java": {
+              "status": "D",
+              "lines_deleted": 139
+            },
+            "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java": {
+              "status": "A",
+              "lines_inserted": 204
+            },
+            "gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java": {
+              "lines_deleted": 9
+            }
+          }
+        }
+      }
+    }
+  ]
+----
+
 
 GERRIT
 ------
diff --git a/Documentation/user-custom-dashboards.txt b/Documentation/user-custom-dashboards.txt
new file mode 100644
index 0000000..a015e4c
--- /dev/null
+++ b/Documentation/user-custom-dashboards.txt
@@ -0,0 +1,48 @@
+Gerrit Code Review - Custom Dashboards
+======================================
+
+Description
+-----------
+
+A custom dashboard is shown in a layout similar to the per-user
+dashboard, but the sections are entirely configured from the URL.
+Because of this custom dashboards are stateless on the server side.
+Users or projects can simply trade URLs using an external system like
+a project wiki, or site administrators can put the links into the
+site's `GerritHeader.html` or `GerritFooter.html`.
+
+Dashboards are available via URLs like:
+----
+  /#/dashboard/?title=Custom+View&To+Review=reviewer:john.doe@example.com&Pending+In+myproject=project:myproject+is:open
+----
+This opens a view showing the title "Custom View" with two sections,
+"To Review" and "Pending in myproject":
+----
+  Custom View
+
+  To Review
+
+    Results of `reviewer:john.doe@example.com`
+
+  Pending In myproject
+
+    Results of `project:myproject is:open`
+----
+
+The dashboard URLs are easy to configure. All keys and values in the
+URL are encoded as query parameters. Set the page and window title
+using an optional `title=Text` parameter.
+
+Each section's title is defined by the parameter name, the section
+display order is defined by the order the parameters appear in the
+URL, and the query results are defined by the parameter value. To
+limit the number of rows in a query use `limit:N`, otherwise the
+entire result set will be shown (up to the user's query limit).
+
+Parameters may be separated from each other using any of the following
+characters, as some users may find one more readable than another:
+`&` or `;` or `,`
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/ReleaseNotes/ReleaseNotes-2.5.txt b/ReleaseNotes/ReleaseNotes-2.5.txt
index 60c4f08..3065b63 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.txt
@@ -5,9 +5,26 @@
 
 link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.5.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.5.war]
 
+Gerrit 2.5 includes the bug fixes done with
+link:ReleaseNotes-2.4.1.html[Gerrit 2.4.1] and
+link:ReleaseNotes-2.4.2.html[Gerrit 2.4.2]. These bug fixes are *not*
+listed in these release notes.
+
+Schema Change
+-------------
+*WARNING:* This release contains schema changes.  To upgrade:
+----
+  java -jar gerrit.war init -d site_path
+----
+
+*WARNING:* Upgrading to 2.5.x requires the server be first upgraded to 2.1.7 (or
+a later 2.1.x version), and then to 2.5.x.  If you are upgrading from 2.2.x.x or
+newer, you may ignore this warning and upgrade directly to 2.5.x.
+
 Upgrade Warnings
 ----------------
 
+[[replication]]
 Replication
 ~~~~~~~~~~~
 
@@ -53,3 +70,1461 @@
 servers account and permission data, or the `ldap_groups` cache, where
 updates are often made to the source without telling Gerrit to reload
 the cache.
+
+New Features
+------------
+
+Plugins
+~~~~~~~
+
+The Gerrit server functionality can be extended by installing plugins.
+Depending on how tightly the extension code is coupled with the Gerrit
+server code, there is a distinction between
+link:../Documentation/dev-plugins.html#plugin[plugins] and
+link:../Documentation/dev-plugins.html#extension[extensions].
+
+* link:#replication[Move replication logic to replication plugin]
++
+This splits all of the replication code out of the core server
+and moves it into a standard plugin.
+
+* link:../Documentation/dev-plugins.html[Documentation about
+  plugin development] including instructions for:
+** link:../Documentation/dev-plugins.html#getting-started[how to get
+   started with plugin development]
+** link:../Documentation/dev-plugins.html#deployment[plugin
+   deployment/installation]
+
+* link:../Documentation/dev-plugins.html#API[API for plugins and
+  extensions]
+
+* Support for link:../Documentation/dev-plugins.html#ssh[SSH command
+  plugins]
++
+Allows plugin developers to declare additional SSH commands.
+
+* Enable link:#ssh-alias[aliases for SSH commands]
++
+Site administrators can alias SSH commands from a plugin into the
+`gerrit` namespace.
++
+The aliases are configured statically at server startup, but are
+resolved dynamically at invocation time to the currently loaded
+version of the plugin. If the plugin is not loaded, or does not
+define the command, "not found" is returned to the user.
+
+* Support for link:../Documentation/dev-plugins.html#http[HTTP
+  plugins]
++
+Plugins may contribute to the /plugins/NAME/ URL space.
+
+* Automatic registration of plugin bindings
++
+If a plugin has no modules declared in the manifest, automatically
+generate the modules for the plugin based on the class files that
+appear in the plugin and the `@Export` annotations that appear on
+these concrete classes.
++
+For any non-abstract command that extends SshCommand, plugins may
+declare the command with `@Export("name")` to
+link:../Documentation/dev-plugins.html#ssh[bind the implementation
+as that SSH command].
++
+Likewise link:../Documentation/dev-plugins.html#http[HTTP servlets
+can also be bound to URLs].
+
+* link:../Documentation/dev-plugins.html#data-directory[Support a data
+  directory for plugins on demand]
+
+* Support serving static/ and Documentation/ from plugins
++
+The static/ and Documentation/ resource directories of a plugin can be
+served over HTTP for any loaded and running plugin, even if it has no
+other HTTP handlers. This permits a plugin to supply icons or other
+graphics for the web UI, or documentation content to help users learn
+how to use the plugin.
+
+* link:../Documentation/dev-plugins.html#documentation[Auto-formatting
+  of plugin HTTP pages from Markdown files]
++
+If Gerrit detects that a requested plugin resource does not exist, but
+instead a file with a `.md` extension does exist, Gerrit opens the
+`.md` file and reformats it as html.
+
+* Support of link:../Documentation/dev-plugins.html#macros[macros in
+  Markdown plugin documentation]
+
+* link:../Documentation/dev-plugins.html#auto-index[Automatic
+  generation of an index for the plugin documentation]
+
+* Support for audit plugins
++
+Plugins can implement an `AuditListener` to be informed about auditable
+actions:
++
+----
+  @Listener
+  public class MyAuditTrail extends AuditListener
+----
++
+The plugin must define a plugin module that binds the implementation of
+the audit listener in the `configure()` method:
++
+----
+  DynamicSet.bind(binder(), AuditListener.class).to(MyAuditTrail.class);
+----
+
+* Web UI for plugins
++
+Administrators can see the list of installed plugins in the WebUI
+under `Admin` > `Plugins`. For each plugin the plugin status is shown
+and it is possible to navigate to the plugin documentation.
+
+* Servlet to list plugins
++
+Administrators can retrieve plugin information from a REST interface
+by loading `<server-url>/a/plugins/`.
+
+* Support SSH commands to
+** link:../Documentation/cmd-plugin-ls.html[list the installed
+   plugins]
+** link:../Documentation/cmd-plugin-install.html[install plugins]
+** link:../Documentation/cmd-plugin-enable.html[enable plugins]
+** link:../Documentation/cmd-plugin-remove.html[disable plugins]
+** link:../Documentation/cmd-plugin-reload.html[reload plugins]
+
+* Support installation of core plugin on site initialization
+
+* Automatically load/unload/reload plugins
++
+The PluginScanner thread runs every 1 minute by default and loads any
+newly created plugins, unloads any deleted plugins, and reloads any
+plugins that have been modified.
++
+The check frequency can be configured by setting
+link:../Documentation/config-gerrit.html#plugins.checkFrequency[
+plugins.checkFrequency] in the Gerrit config file. By configuration
+the scanner can also be disabled.
+
+* link:../Documentation/dev-plugins.html#classpath[Loading of plugins
+  in own ClassLoader]
+
+* Plugin cleanup in the background
++
+When a plugin is stopped, schedule a Plugin Cleaner task to run
+1 minute later to try and clean out the garbage and release the
+JAR from `$site_path/tmp`.
+
+* Export `LifecycleListener` as extension point
++
+Extensions may need to know when they are starting or stopping.
+Export the interface that they can use to learn this information.
+
+* Support injection of `ServerInformation` into extensions and plugins
++
+Plugins can take this value by injection and learn the current
+server state during their own LifecycleListener. This enables a
+plugin to determine if it is loading as part of server startup, or
+because it was dynamically installed or reloaded by an administrator.
+
+* link:../Documentation/dev-plugins.html#getting-started[Maven
+  archetype for creating gerrit plugin projects]
+
+REST API
+~~~~~~~~
+Gerrit now supports a REST like API available over HTTP. The API is
+suitable for automated tools to build upon, as well as supporting some
+ad-hoc scripting use cases.
+
+* link:../Documentation/rest-api.html[Documentation of the REST API]
+
+* Support REST endpoints to
+** link:../Documentation/rest-api.html#changes[query changes]
+** link:../Documentation/rest-api.html#projects[list projects]
+** link:../Documentation/rest-api.html#suggest-projects[suggest
+   projects]
+** link:../Documentation/rest-api.html#accounts_self_capabilities[query
+   the global capabilities of the calling user]
+
+* Support link:../Documentation/rest-api.html#authentication[anonymous
+  and authenticated access] to the REST endpoints
+
+* Support link:../Documentation/rest-api.html#output[JSON output
+  format] for the REST endpoints
+
+The new REST API is used from the Gerrit WebUI.
+
+Some of the methods from the old internal JSON-RPC interface were
+completely replaced by the new REST API and got deleted:
+
+* `ProjectAdminService.visibleProjects(AsyncCallback<ProjectList>)`
+* `ProjectAdminService.suggestParentCandidates(AsyncCallback<List<Project>>)`
+* `ChangeListService.myStarredChangeIds(AsyncCallback<Set<Change.Id>>)`
+* `ChangeListService.allQueryNext(String, String, int, AsyncCallback<SingleListChangeInfo>)`
+* `ChangeListService.allQueryPrev(String, String, int, AsyncCallback<SingleListChangeInfo>)`
+* `ChangeListService.forAccount(Account.Id, AsyncCallback<AccountDashboardInfo>)`
+
+[[query-deprecation]]
+In addition the `/query` API has been deprecated. By default it is
+still available but server administrators may disable it by setting
+the link:../Documentation/config-gerrit.html#site.enableDeprecatedQuery[
+`site.enableDeprecatedQuery`] parameter in the Gerrit config file. This
+allows to enforce tools to move to the new API.
+
+Web
+~~~
+
+Change Screen
+^^^^^^^^^^^^^
+
+* Display commit message in a box
++
+The commit message on the change screen is now placed in a box with a
+title and emphasis on the commit summary. The star icon and the
+permalink are displayed in the box header. The header from the change
+screen is removed as it only held duplicate information.
+
+* Open the dependency section automatically when the change is needed
+  by an open change
+
+* Only show a change as needed by if its current patch set depends on
+  the change
+
+* Show only changes of the same project in the 'Depends On' section
++
+If two projects share the same history it can happen that the same
+commit is pushed for both projects, resulting in two changes. If now
+a successor commit is pushed for one of the projects, the resulting
+successor change was wrongly listing both changes in the 'Depends On'
+section. Now only the predecessor change of the own project is listed.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1383[issue 1383]:
+  Display the approval table on the PublishCommentsScreen.
++
+So far the approval table that shows the reviewers and their current
+votes was only shown on the ChangeScreen. Now it is also shown on the
+PublishCommentScreen. This allows the reviewer to see all existing
+votes and reviewers when doing their own voting and publishing of
+comments. Seeing the existing votes helps the reviewer in
+understanding which votes are still required before the change can be
+submitted.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1380[issue 1380]:
+  Display time next to change comments
++
+When a comment was posted yesterday, or any time older than 1 day but
+less than 1 year ago, display the time too. Display "May 2 17:37" rather
+than just "May 2".
+
+* Only show "Can Merge" when the change is new or draft
+
+* Disable submit button if merge will fail
++
+Only if link:../Documentation/config-gerrit.html#changeMerge[
+`changeMerge.test = true`].
+
+* Allow auto suggesting reviewers to draft changes
++
+Auto completing users for draft changes did't work as the other
+users didn't have access to the drafts. The visibility check for
+the reviewer suggestion is now skipped.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1294[issue 1294]:
+  Shorten subject of parent commit for displaying in the UI
++
+If the parent commit has a very long subject (> 80 characters) shorten
+the subject for displaying it in the Gerrit web UI on the change screen.
+This avoids that the 'Parent(s)' cell for the patch set becomes very
+wide.
+
+* If subject is shortened for displaying in the UI indicate this by '...'
++
+If a commit has a very long subject line (> 80 characters) it is
+shortened when it is displayed in the Gerrit Web UI. Indicate to the
+user that the subject was shortened by appending '...' to the shortened
+subject.
++
+Also the subject is now cropped after a whitespace if possible.
+
+* Insert Change-Id for revert commits
++
+The 'Revert Change' action on a merged change allows to create a new
+change that reverts the merged change. The commit message of the revert
+commit now contains a Change-Id.
++
+It is convenient if a Change-Id is automatically created and inserted
+into the commit message of the revert commit since it makes rebasing of
+the revert commit easier.
+
+* Use more gentle shade of red to highlight outdated dependencies
+
+Patch Screens
+^^^^^^^^^^^^^
+
+* New patch screen header
++
+A new patch screen header was added that is displayed above both the
+side-by-side and unified views. The new header contains actual links to
+the available patchsets and shows which patchset is being currently
+displayed.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1192[issue 1192]:
+  Add download links to the unified diff view
+
+* Improvement of the side-by-side viewer table
++
+The line number column for the right side was moved to be on the far
+right of the table, so that the layout now looks like this:
++
+----
+  1 |  foo       |       bar   | 1
+  2 |  hello     |       hello | 2
+----
++
+This looks nicer when reading a lot of code, as the line numbers are
+less relevant than the code itself which is now in the center of the
+UI.
++
+Line numbers are still links to create comment editors, but they
+use a light shade of gray and skip the underline decoration, making
+them less visually distracting.
++
+Skip lines now use a paler shade of blue and also hide the fact they
+contain anchors, until you hover over them and the anchor shows up.
++
+The expand before and after are changed to be arrows showing in
+which direction the lines will appear above or below the skip
+line.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=626[issue 626]:
+  Option to display line endings
++
+There is a new user preference that allows to display Windows EOL/Cr-Lf.
+'\r' is shown in a dotted-line box (similar to how '\r' is displayed in
+GitWeb).
+
+* Streamlined review workflow
++
+A link was added next to the "Reviewed" checkbox that marks the current
+patch as reviewed and goes to the next unreviewed patch.
+
+User Dashboard
+^^^^^^^^^^^^^^
+* Support for link:../Documentation/user-custom-dashboards.html[custom
+  dashboards]
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1407[issue 1407]:
+  Improve highlighting of unreviewed changes in the user's dashboard
++
+A change will be highlighted as unreviewed if
++
+. the user is reviewer of the change but hasn't published any change
+  message for the current patch set
+. the user has published a change message for the current patch set,
+  but afterwards the change owner has published a change message on
+  the change
+
+* Sort outgoing reviews in the user dashboard by created date
+
+* Sort incoming reviews in the user dashboard by updated date
++
+Sorting the incoming reviews by last updated date, descending, places
+the most recently updated reviews at the top of the list for a user,
+and the oldest stale at the bottom. This may help users to identify
+items to take immediate action on, as they appear closer to the top.
+
+Access Rights Screen
+^^^^^^^^^^^^^^^^^^^^
+
+* Display error if modifying access rights for a ref is forbidden
++
+If a user is owner of at least one ref he is able to edit the access
+rights on a project. If he adds access rights for other refs, these
+access rights were silently ignored on save. Instead of this now an
+error message is displayed to inform the user that he doesn't have
+permissions to do the update for these refs.
++
+In case of such an error the project access screen stays in the edit
+mode so that the unsaved modifications are not lost. The user may now
+propose the changes to the access rights through code review.
+
+* Allow to propose changes to access rights through code review
++
+Users that are able to upload changes for code review for the
+`refs/meta/config` branch can now propose changes to the project access
+rights through code review directly from the ProjectAccessScreen.
++
+When editing the project access rights there is a new button
+'Save for Review' which will create a new change for the access
+rights modifications. Project owners are automatically added as
+reviewer to this change. If a project owner agrees to the access rights
+modifications he can simply approve and submit the change.
+
+* Show all access rights in WebUI if user can read `refs/meta/config`
++
+Users who can read the `refs/meta/config` branch, can see all access
+rights by fetching this branch and looking at the `project.config`
+file. Now they can see the same information in the web UI.
+
+* Allow extra group suggestions for project owners
++
+When suggesting groups to a user, only groups that are visible to the
+user are suggested. These are those group that the user is member of.
+For project owners now also groups to which they are not a member are
+suggested when editing the access rights of the project.
+
+Other
+^^^^^
+
+* Ask user to login if change is not found
++
+Accessing a change URL was failing with 'Application Error - The page
+you requested was not found, or you do not have permission to view this
+page' if the user was not signed in and the change was not visible to
+`Anonymous Users`. Instead Gerrit now asks the user to login and
+afterwards shows the change to the user if it exists and is visible.
+If the change doesn't exist or is not visible, the user will still get
+the NotFoundScreen after sign in.
+
+* Link to owner query from user names
++
+Instead of linking from a user name to the user's dashboards, link to
+a search for changes owned by that user.
+
+* On project creation allow choosing the parent project from a popup
++
+In the create project UI a user can now browse all projects and select
+one as parent for the new project.
+
+* Check for open changes on branch deletion
++
+Check for open changes when deleting a branch in the Gerrit WebUI.
+Delete a branch only if there are no open changes for this branch.
+This makes users aware of open changes when deleting a branch.
+
+* Enable ProjectBranchesScreen for the `All-Projects` project
++
+This allows to see the branches of the `All-Projects` project in the
+web UI.
+
+* Move form for group creation to own screen
++
+Move the form for the group creation from the GroupListScreen to an
+own new CreateGroupScreen and add a link to this screen at the
+beginning of the GroupListScreen. The link to the CreateGroupScreen is
+only visible if the user has the permission to create new groups.
+
+* When adding a user to a group create an account for the user if needed
++
+Trying to add a user to a group that doesn't have an account fails with
+'... is not a registered user.'. Now adding a user to a group does not
+immediately fail if there is no account for the user, but it tries to
+authenticate the user and if the authentication is successful a user
+account is automatically created, so that the user can be added to the
+group. This only works if LDAP is used as user backend.
++
+This allows to add users to groups that did not log in into Gerrit
+before.
+
+* Differentiate between draft changes and draft comments
++
+Show the draft changes of the user when he clicks on `My` > `Drafts`.
+The user's draft comments are now available under `My` >
+`Draft Comments`.
+
+* Show NotFoundScreen if a user that can't create projects tries to
+  access the ProjectCreationScreen
+
+* Add Edit, Reload next to non-editable Full Name field
++
+If the user database is actually an external system users might need go
+to another server to edit their account data, and then re-import their
+account data by going through a login cycle. This is highly similiar to
+LDAP where the directory provides account data and its refreshed every
+time the user visits the `/login/` URL handler.
++
+The URL for the external system can be configured for the
+link:#custom-extension[`CUSTOM_EXTENSION`] auth type.
+
+Access Rights
+~~~~~~~~~~~~~
+
+* Restrict rebasing of a change in the web UI to the change owner and
+  the submitter
+
+* Add a new link:../Documentation/access-control.html#category_rebase[
+  access right to permit rebasing changes in the web UI]
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=930[issue 930]:
+  Add new link:../Documentation/access-control.html#category_abandon[
+  access right for abandoning changes]
+
+* Check if user can upload in order to restore
++
+Restoring a change is similar to uploading a new change. If a branch
+gets closed by removing the access rights to upload new changes it
+shouldn't be possible to restore changes for this branch.
+
+[[hide-config]]
+* Make read access to `refs/meta/config` by default exclusive to
+  project owners
++
+When initializing a new site a set of default access rights is
+configured on the `All-Projects` project. These default access rights
+include read access on `refs/*` for `Anonymous Users` and read access
+on `refs/meta/config` for `Project Owners`. Since the read access on
+`refs/meta/config` for `Project Owners` was not exclusive,
+`Anonymous users` were able to access the `refs/meta/config` branch
+which by default should only be accessible by the project owners.
+
+Search
+~~~~~~
+* Offer suggestions for the search operators in the search panel
++
+There are many search operators and it's difficult to remember all of
+them. Now the search operators are suggested as the user types the
+query.
+
+* Support alias `self` in queries
++
+Writing an expression like "owner:self status:open" will now identify
+changes that the caller owns and are still open. This `self` alias
+is valid in contexts where a user is expected as an argument to a
+query operator.
+
+* Add parent(s) revision information to output of query command
+
+* Add owner username to output of query command
+
+* `/query` API has been link:#query-deprecation[deprecated]
+
+SSH
+~~~
+* link:http://code.google.com/p/gerrit/issues/detail?id=1095[issue 1095]
+  link:../Documentation/cmd-set-account.html[SSH command to manage
+  accounts]
+
+* link:../Documentation/cmd-set-project.html[SSH command to manage
+  project settings]
+
+* link:../Documentation/cmd-ban-commit.html[SSH command to ban
+  commits]
+
+[[ssh-alias]]
+* Enable aliases for SSH commands
++
+Site administrators can define aliases for SSH commands in the
+link:../Documentation/config-gerrit.html#ssh-alias[`ssh-alias` section]
+of the Gerrit configuration.
+
+* Support JSON output format for the
+  link:../Documentation/cmd-ls-projects.html[ls-projects] SSH command
+
+* Support creation of multiple branches in
+  link:../Documentation/cmd-create-project.html[create-project] SSH
+  command
++
+In case if a project has some kind of waterfall automerging
+a->b->c it is convenient to create all these branches at the
+project creation time.
++
+e.g. '.. gerrit create-project -b master -b foo -b bar ...'
+
+* Add verbose output option to
+  link:../Documentation/cmd-ls-groups.html[ls-groups] command
++
+The verbose mode enabled by the new option makes the ls-groups
+command output a tab-separated table containing all available
+information about each group (though not its members).
+
+Documentation
+~~~~~~~~~~~~~
+
+Commands
+^^^^^^^^
+
+* document for the link:../documentation/cmd-create-group.html[`create-group`]
+  command that for unknown users an account is automatically created if
+  the LDAP authentication succeeds
+
+* Update documentation and help text for the
+  link:../documentation/cmd-review.html[`review`] SSH command
++
+The review command can be applied to multiple changes, but the
+help text was written in singular tense.
++
+Add a paragraph in the documentation explaining that the
+`--force-message` option will not be effective if the `review` command
+fails because the user is not permitted to change the label.
+
+* Clarify that `init --batch` doesn't drop old database objects
+
+* Update the list of unsupported slave commands
+
+* Fix link:../Documentation/cmd-stream-events.html[`stream-events`]
+  documentation
++
+Some attributes contained in the events were not described, for a few
+others the name was given in a wrong case.
+
+* Fix and complete synopsis of commands
+
+Access Control
+^^^^^^^^^^^^^^
+
+* Clarify the ref format for
+  link:../Documentation/access-control.html#category_push_merge[`Push
+  Merge Commit`]
++
+Elaborate on the required format of the ref used for `Push Merge Commit`
+access right entries to avoid user confusion when granting access to
+`refs/heads/*` still doesn't allow them to push any merge commits.
+
+* Document the
+  link:../Documentation/access-control.html#capability_emailReviewers[
+  `emailReviewers`] capability
+
+Error
+^^^^^
+* Improve documentation of link:../Documentation/error-change-closed.html[
+  `change closed` error]
++
+The `change closed` error can also occur when trying to submit a
+review label with the SSH review command onto a change that has
+been closed (submitted and merged, or abandoned) or onto a patchset
+that has been replaced by a newer patchset.
+
+* Correct documentation of `invalid author` and `invalid committer`
+  errors
++
+The error messages `you are not committer ...` and `you are not
+author ...` were replaced with `invalid author` and `invalid
+committer`.
+
+Dev
+^^^
+
+* Update push URL in link:../SUBMITTING_PATCHES[SUBMITTING_PATCHES]
++
+Pushes are now accepted at the same address as clone/fetch/pull.
+
+* Update link:../Documentation/dev-contributing.html[contributor
+  document]
++
+We now prefer to use Guava (previously known as Google Collections).
+
+* Fixed broken link to source code
++
+Updated the documentation source code links to point to:
+http://code.google.com/p/gerrit/source/checkout
+
+* State link:../Documentation/dev-eclipse.html#known-problems[known issues]
+  when debugging Gerrit with Eclipse
+
+* Improved the section on
+  link:../Documentation/dev-eclipse.html#hosted-mode[hosted mode
+  debugging]
++
+The existing section on hosted mode debugging left out a couple of
+steps, and the requirement to use `DEVELOPMENT_BECOME_ANY_ACCOUNT`
+instead of `OpenID` was not mentioned anywhere.
+
+* Add a link:../Documentation/dev-release.html[release preparation
+  document]
++
+Document what it takes to make a Gerrit stable or stable-fix release,
+and how to release Gerrit subprojects.
+
+Other
+^^^^^
+
+* Specify output file for curl commands in documentation
++
+For downloading the `commit-msg` hook and the `gerrit-cherry-pick`
+script users can either use scp or curl. Specify the output file for
+each curl command so that the result is equal to the matching scp
+command.
+
+* Document that user must be in repository root to install `commit-msg`
+  hook
+
+* Add some clarifications to the
+  link:../Documentation/install-quick.html[quick installation guide]
+
+* Add missing documentation about
+  link:../Documentation/config-gerrit.html#hooks[hook configuration]
++
+Add documentation of hook config for `change-restored`, `ref-updated`
+and `cla-signed` hooks.
+
+* Mention that also MySQL supports replication, not just Postgres
+
+* Make sorting of release notes consistent so that the release notes
+  for the newest release is always on top
+
+* Various corrections
++
+Correct typos, spelling mistakes, and grammatical errors.
+
+Dev
+~~~
+* Add link:../Documentation/dev-release.html#plugin-api[script for
+  releasing plugin API jars]
+
+* Pushes are now accepted at the same address as clone/fetch/pull
++
+To submit patches commits can be pushed to
+https://gerrit.googlesource.com/gerrit
+
+* Add `-Pchrome`, `-Pwebkit`, `-Pfirefox` aliases for building
++
+This makes it easier to build for the browser you want to
+test on, rather than remembering what its GWT name is.
+
+* Disable assertions for KeyCommandSet when running in gwtdebug mode
++
+The assertions in the KeyCommandSet class cause exceptions when a
+KeyCommand is registered several times.
+
+* Add the run profiles to the favorites menu
+
+* Add Intellij IDEA files to ignore list
+
+* Move local Maven repository to Google Cloud Storage
+
+* Make sure asciidoc uses unix line endings in generated HTML.
++
+Use an explicit asciidoc attribute to make sure the produced HTML will
+always contain unix line endings.  This will help in producing build
+results that are better comparable by size.
+
+* Remove timestamp from all `org.eclipse.core.resources.prefs` files
++
+Eclipse overwrites these files when we import projects using m2e.
+Eclipse 3 writes a timestamp at the top of these files making the Git
+working tree dirty.  Eclipse 4 (Juno) still overwrites these files but
+doesn't write the timestamp.  This should help to keep the working tree
+clean.  However, since the timestamp is currently present in these
+files, Eclispe 4 would still make them dirty by overwriting and
+effectively removing the timestamp.
++
+This change removes the timestamp from these files. This helps those
+using Eclipse 4 and doesn't make it worse for those still using Eclispe
+3.
+
+* Add Maven profile to skip build of plugin modules
++
+Building the plugin modules ('Plugin API' and 'Plugin Archetype') may
+take a significant amount of time (since many jars are downloaded).
+During development it is not needed to build the plugin modules. A new
+Maven profile was added that skips the build of the plugin modules,
+so that developers have a faster turnaround. This profile is called
+`no-plugins` and it's active by default. To include the plugin modules
+into the build activate the `all` profile:
++
+----
+  mvn clean package -P all
+----
++
+The script to make release builds has been adapted to activate the
+`all` profile so that the plugin modules are always built for release
+builds.
+
+Mail
+~~~~
+
+* Add unified diff to newchange mail template
++
+Add `$email.UnifiedDiff` as new macro to the `NewChange.vm` mail
+template. This macro is expanded to a unified diff of the patch.
+
+* link:../Documentation/config-gerrit.html#sendemail.includeDiff[
+  sendemail.includeDiff]: Enable `$email.UnifiedDiff` in `NewChange.vm`
++
+Instead of making site administrators hack the email template, allow
+admins to enable the diff feature by setting a configuration variable
+in `gerrit.config`.
+
+* link:../Documentation/config-gerrit.html#sendemail.maximumDiffSize[
+  sendemail.maximumDiffSize]: Limit the size of diffs sent by email
++
+If a unified diff included in an email will exceed the limit configured
+by the system administrator, only the affected file paths are listed in
+the email instead. This gives interested parties some context on the
+size and scope of the change, without killing their inbox.
+
+* Catch all exceptions when emailing change update
+
+* Allow unique from address generation
++
+Allow the from email address to be a ParameterizedString that handles
+the `${userHash}` variable. The value of the variable is the md5 hash
+of the user name. This allows unique generation of email addresses, so
+GMAIL threads names of users in conversations correctly. For example,
+the from pattern for gerrit-review defined in the Gerrit configuration
+looks like this:
++
+----
+  [sendemail]
+    from = ${user} <noreply-gerritcodereview+${userHash}@google.com>
+----
+
+* Show new change URLs in the body of the new change email
++
+Some email clients hide the signature section of an email
+automatically.  If there are no reviewers listed on a new change,
+such as when a change is pushed over HTTP and a notification is
+automatically sent out to any subscribed watchers, the URL was
+hidden inside of the signature and not readily available.
++
+Show the URL right away in the body.
+
+Miscellaneous
+~~~~~~~~~~~~~
+* Back in-memory caches with Guava, disk caches with H2
++
+Instead of using Ehcache for in-memory caches, use Guava. The Guava
+cache code has been more completely tested by Google in high load
+production environments, and it tends to have fewer bugs. It enables
+caches to be built at any time, rather than only at server startup.
++
+By creating a Guava cache as soon as it is declared, rather than
+during the LifecycleListener.start() for the CachePool, we can promise
+any downstream consumer of the cache that the cache is ready to
+execute requests the moment it is supplied by Guice. This fixes a
+startup ordering problem in the GroupCache and the ProjectCache, where
+code wants to use one of these caches during startup to resolve a
+group or project by name.
++
+Tracking the Gauva backend caches with a DynamicMap makes it possible
+for plugins to define their own in-memory caches using CacheModule's
+cache() function to declare the cache. It allows the core server to
+make the cache available to administrators over SSH with the gerrit
+show-caches and gerrit `flush-caches` commands.
++
+Persistent caches store in a private H2 database per cache, with a
+simple one-table schema that stores each entry in a table row as a
+pair of serialized objects (key and value). Database reads are gated
+by a BloomFilter, to reduce the number of calls made to H2 during
+cache misses. In theory less than 3% of cache misses will reach H2 and
+find nothing. Stores happen on a background thread quickly after the
+put is made to the cache, reducing the risk that a diff or web_session
+record is lost during an ungraceful shutdown.
++
+Cache databases are capped around 128M worth of stored data by running
+a prune cycle each day at 1 AM local server time. Records are removed
+from the database by ordering on the last access time, where last
+accessed is the last time the record was moved from disk to memory.
+
+* Add OpenID SSO support.
++
+Setting `OPENID_SSO` for
+link:../Documentation/config-gerrit.html#auth.type[`auth.type`] in the
+`gerrit.config` will allow the admin to specify an SSO entry point URL
+so that users clicking on "Sign In" are sent directly to that URL.
+
+* Git over HTTP BasicAuth against Gerrit basic auth.
++
+Allows the configuration of native Gerrit username/password
+authentication scheme used for Git over HTTP BasicAuth, as alternative
+of the default DigestAuth scheme against the random generated password
+on Gerrit DB.
++
+Example setting for link:../Documentation/config-gerrit.html#auth.type[
+`auth.type`] and link:../Documentation/config-gerrit.html#auth.gitBasicAuth[
+`auth.gitBasicAuth`]:
++
+----
+  [auth]
+    type = LDAP
+    gitBasicAuth = true
+----
++
+With this configuration Git over HTTP protocol will be authenticated using
+`HTTP-BasicAuth` and credentials checked on LDAP.
+
+* Migrate existing internal LDAP groups
++
+Previously, LDAP groups were mirrored in the AccountGroup table and
+given an Id and UUID the same as internal groups. Update these groups
+to be backed by only a GroupReference, with a special "ldap:" UUID
+prefix. Migrate all existing references to the UUID in ownerGroupUUID
+and any `project.config`.
++
+This made the LDAP group type obsolete and it was removed.
+
+* Abstract group systems into GroupBackend interface
+
+* Add more link:../Documentation/config-gerrit.html#theme[theme color
+  options]
++
+** Add a theme option to change outdated background color
+** Add odd/even row background color for tables such as list of open
+reviews.  This makes them more visible without clicking on them.
+
+* link:../Documentation/user-notify.html[Add `notify` section in
+  `project.config`]
++
+The notify section allows project owners to include emails to users
+directly from `project.config`. This removes the need to create fake
+user accounts to always BCC a group mailing list.
+
+* Include the contributor agreements in the `project.config` and
+  migrate contributor agreements to `All-Projects`
++
+Update the parsing of `project.config` to support the contributor
+agreements.
++
+Add a new schema to move the ContributorAgreement, AccountAgreement,
+and AccountGroupAgreement information into the `All-Projects`
+`project.config`.
+
+* Add `sameGroupVisibility` to `All-Projects` `project.config`
++
+The `sameGroupVisiblity` is needed to restrict the visibility of
+accounts when `accountVisibility` is `SAME_GROUP`. Namely, this is a
+way to make sure the `autoVerify` group in a `contributor-agreements`
+section is never suggested.
+
+* Log sign in failures on info level
++
+If for a user signing in into the Gerrit web UI fails, this can have
+many reasons, e.g. username is wrong, password is wrong, user is marked
+as inactive, user is locked in the user backend etc. In all cases the
+user just gets a generic error message 'Incorrect username or
+password.'. Gerrit administrators had trouble to find the exact reason
+for the sign in problem because the corresponding AccountException was
+not logged.
+
+* Do not log 'Object too large' as error with full stacktrace
++
+If a user pushes an object which is larger than the configured
+`receive.maxObjectSizeLimit` parameter, the push is rejected with an
+'Object too large' error. In addition an error log entry with the full
+stacktrace was written into the error log.
++
+This is not really a server error, but just a user doing something that
+is not allowed, and thus it should not be logged as error. For a Gerrit
+administrator it might still be interesting how often the limit is hit.
+This is why it makes sense to still log this on info level.
++
+For the user pushing a too large object we now do not print the
+'fatal: Unpack error, check server log' message anymore, but only the
+'Object too large' error message.
+
+* Add better explanations to rejection messages
++
+Provide information to the user why a certain push was rejected.
+
+* Automatic schema upgrade on Gerrit startup
++
+In case when Gerrit administrator(s) don't have a direct access to the
+file system where the review site is located it gets difficult to
+perform a schema upgrade (run the init program). For such cases it is
+convenient if Gerrit performs schema upgrade automatically on its
+startup.
++
+Since this is a potentially dangerous operation, by default it will not
+be performed. The configuration parameter
+link:../Documentation/config-gerrit.html#site.upgradeSchemaOnStartup[
+site.upgradeSchemaOnStartup] is used to switch on automatic schema
+upgrade.
+
+* Shorten column names that are longer than 30 characters
++
+Some databases can't deal with column names that are longer than 30
+characters. Examples are MaxDB and
+link:http://groups.google.com/group/repo-discuss/browse_thread/thread/ecb713d42c04ae8a/cc963525d8247a17?lnk=gst#cc963525d8247a17[Oracle].
++
+Gerrit had two column names in the `accounts` table that exceeded the
+30 characters: `displayPatchSetsInReverseOrder`,
+`displayPersonNameInReviewCategory`
++
+These 2 columns were renamed so that their names fit within the 30
+character range.
+
+* Increase the maximum length for tracking ID's to 32 characters
++
+So far tracking ID's had a maximum length of only 20 characters.
+
+* Set `GERRIT_SITE` in Gerrit hooks as environment variable
++
+Allows development of hooks parametrised on Gerrit location. This can
+be useful to allow hooks to load the Gerrit configuration when needed
+(from `$GERRIT_SITE`) or even store their additional config files under
+`$GERRIT_SITE/etc` and retrieve them at startup.
+
+* Add an exponentially rolling garbage collection script
++
+`git-exproll.sh` is a git garbage collection script aimed specifically
+at reducing exccessive garbage collection and particularly large
+packfile churn for Gerrit installations.
++
+Excessive garbage collection on "dormant" repos is wasteful of both CPU
+and disk IO.  Large packfile churn can lead to heavy RAM and FS usage
+on Gerrit servers when the Gerrit process continues to hold open the
+old delete packfiles.  This situation is most detrimental when jgit is
+configured with large caching parameters.  Aside from these downsides,
+running git gc often can be very beneficial to performance on servers.
+This script attempts to implement a git gc policy which avoids the
+downsides mentioned above so that git gc can be comfortably run very
+regularly.
++
+`git-exproll.sh` uses keep files to manage which files will get
+repacked.  It also uses timestamps on the repos to detect dormant repos
+to avoid repacking them at all.  The primary packfile objective is to
+keep around a series of packfiles with sizes spaced out exponentially
+from each other, and to roll smaller packfiles into larger ones once
+the smaller ones have grown.  This strategy attempts to balance disk
+space usage with avoiding rewriting large packfiles most of the time.
++
+The exponential packing objective above does not save a large amount of
+time or CPU, but it does prevent the packfile churn.  Depending on repo
+usage, however the dormant repo detection and avoidance can result in a
+very large time savings.
+
+* Unpack JARs for running servers in `$site_path/tmp`
++
+Instead of unpacking a running server into `~/.gerritcodereview/tmp`
+only use that location for commands like init where there is no active
+site. From gerrit.sh always use `$site_path/tmp` for the JARs to
+isolate servers that run on the same host under the same UNIX user
+account.
+
+[[custom-extension]]
+* Allow for the `CUSTOM_EXTENSION` `auth.type` to configure URLs for
+  editing the user name and obtaining an HTTP password
++
+Allow `CUSTOM_EXTENSION` auth type to supply by `auth.editFullNameUrl`
+a URL in the web UI that links users to the other account system,
+where they can edit their name, and then use another reload URL to
+cycle through the `/login/` step and refresh the data cached by Gerrit.
++
+Allow `CUSTOM_EXTENSION` auth type to supply by `auth.httpPasswordUrl`
+a URL in the web UI that allows users to obtain an HTTP password.
++
+Like the rest of the `CUSTOM_EXTENSION` stuff, this is hack that will
+eventually go away when there is proper support for authentication
+plugins.
+
+Performance
+~~~~~~~~~~~
+* Assume labels are correct in ListChanges
++
+To reduce end-user latency when displaying changes in a search result
+or user dashboard, assume the labels are accurate in the database at
+display time and don't recompute the access privileges of a reviewer.
+
+* Notify the cache that the git_tags was modified
++
+The tag cache was updated in-place, which prevented the H2 based
+storage from writing out the updated tag information. This meant
+servers almost never had the right data stored on disk and had to
+recompute it at startup.
++
+Anytime the value is now modified in place, put it back into the
+cache so it can be saved for use on the next startup.
+
+* Special case hiding `refs/meta/config` from Git clients
++
+VisibleRefFilter requires a lot of server CPU to accurately provide
+the correct listing to clients when they cannot read `refs/*`.
++
+Since the default configuration is now to link:#hide-config[
+hide `refs/meta/config`], use a special case in VisibleRefFilter that
+permits showing every reference except `refs/meta/config` if a user can
+read every other reference in the repository.
+
+* Avoid second remote call to lookup approvals when loading change
+  results
++
+By using the new link:../Documentation/rest-api.html#changes[`/changes/`]
+REST endpoint the web UI client now obtains the label information
+during the query and avoids a second round trip to lookup the current
+approvals for each displayed change. For most users this should improve
+the way the page renders. The verified and code review columns will be
+populated before the table is made visible, preventing the layout from
+"jumping" the way the old UI did when the 2nd RPC finally finished and
+supplied the label data.
+
+* Load patch set approvals in parallel
++
+ResultSet is a future-like interface, the database system is free to
+execute each result set asynchronously in the background if it
+supports that. gwtorm's default SQL backend always runs queries
+immediately and then returns a ListResultSet, so for most installs this
+has no real impact in ordering.
++
+For the system that runs gerrit-review, each query has a high cost in
+network latency, the system treats ResultSet as a future promise to
+supply the matching rows. Getting all of the necessary ResultSets up
+front allows the database to send all requests to the backend as early
+as possible, allowing the network latency to overlap.
+
+Upgrades
+--------
+* Update Gson to 2.1
+* Update GWT to 2.4.0
+* Update JGit to 2.0.0.201206130900-r.23-gb3dbf19
+
+* Use gwtexpui 1.2.6
++
+** Hide superfluous status text from clippy flash widget
+** Fix diappearance of text in CopyableLabel when clicking on it
+
+* Update Guava to 12.0.1
++
+This fixes a performance problem with LoadingCache where the cache's
+inner table did not dynamically resize to handle a larger number
+of cached items, causing O(N) lookup performance for most objects.
+
+Bug Fixes
+---------
+
+Security
+~~~~~~~~
+* Ensure that only administrators can change the global capabilities
++
+Only Gerrit server administrators (members of the groups that have
+the `administrateServer` capability) should be able to edit the
+global capabilities because being able to edit the global capabilities
+means being able to assign the `administrateServer` capability.
++
+Because of this on the `All-Projects` project it is disallowed to assign
++
+. the `owner` access rights on `refs/*`
++
+Project owners (members of groups to which the `owner` access right
+is assigned) are able to edit the access control list of the projects
+they own. Hence being owner of the `All-Projects` project would allow
+to edit the global capabilities and assign the `administrateServer`
+capabilitiy without being Gerrit administrator.
++
+In earlier Gerrit versions (2.1.x) it was already implemented like
+this but the corresponding checks got lost.
++
+. the 'push' access right on `refs/meta/config`
++
+Being able to push configuration changes to the `All-Projects` project
+allows to edit the global capabilities and hence a user with this
+access right could assign the `administrateServer` capability without
+being Gerrit administrator.
++
+From the Gerrit WebUI (ProjectAccessScreen) it is not possible anymore
+to assign on the `All-Projects` project the `owner` access right on
+`refs/*` and the `push` access right on `refs/meta/config`.
++
+In addition it is ensured that an `owner` access right that is assigned
+for `refs/*` on the `All-Projects` project has no effect and that only
+Gerrit administrators with the `push` access right can push
+configuration changes to the `All-Projects` project.
++
+It is still possible to assign both access rights (`owner` on `refs/*`
+and `push` on `refs/meta/config`) on the `All-Projects` project by directly
+editing its `project.config` file and pushing to `refs/meta/config`.
+To fix this it would be needed to reject assigning these access rights
+on the `All-Projects` project as invalid configuration, however doing this
+would mean to break existing configurations of the `All-Projects` project
+that assign these access rights. At the moment there is no migration
+framework in place that would allow to migrate `project.config` files.
+Hence this check is currently not done and these access rights in this
+case have simply no effect.
+
+Web
+~~~
+
+* Do not show "Session cookie not available" on sign in
++
+When LDAP is used for authentication, clicking on the 'Sign In' link
+opens a user/password dialog. In this dialog the "Session cookie not
+available." message was always shown as warning. This warning was
+pretty useless since the user was about to sign in because he had no
+current session.
++
+This problem was discussed on the
+link:https://groups.google.com/forum/#!topic/repo-discuss/j-t77m8-7I0/discussion[
+Gerrit mailing list].
+
+* Reject restoring a change if its destination branch does not exist
+  anymore
+
+* Reject submitting a change if its destination branch does not exist
+  anymore
++
+If a branch got deleted and there was an open change for this branch,
+it was still possible to submit this open change. As result the
+destination branch was implicitly recreated, even if the user
+submitting the change had no privileges to create branches.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1352[issue 1352]:
+  Don't display "Download" link for `/COMMIT_MSG`
++
+The commit message file is special, it doesn't actually exist and
+cannot be downloaded. Don't offer the download link in the side by
+side viewer.
+
+* Dependencies were lost in the ChangeScreen's "Needed By" table
++
+Older patchsets are now iterated for decendents, so that the dependency
+chain does not break on new upstream patchsets.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1442[issue 1442]:
+  Only show draft change dependency if current user is owner or reviewer
++
+In the change screen, the dependencies panel was showing draft changes
+in the "Depends On" and "Needed By" lists for all users, and when there
+was no user logged in.
+
+* Fix disappearance of action buttons when selecting the last patch set
+as `Old Version History`
+
+* Fix updating patch list when `Old Version History` is changed
++
+If a collapsed patch set panel was expanded and re-closed it's patch
+list wasn't updated anymore when the selection for `Old Version History`
+was changed.
+
+* Don't NPE if current patch set is not available
++
+Broken changes may have the current patch set field incorrectly
+specified, causing currentPatchSet to be unable to locate the
+correct data and return it. When this happens don't NPE, just
+claim the change is not reviewed.
+
+* Fix displaying of comments on deleted files
++
+Published and draft comments that are posted on deleted files were not
+loaded and displayed.
+
+* Only set reviewed attribute on open changes
++
+If a change is merged or abandoned, do not consider the reviewed
+property for the calling user, so that the change is not highlighted
+as unreviewed on the user's dashboard.
+
+* Change PatchTable pointer when loading patch
++
+This patch fixes an issue with the "file list" table displayed by
+clicking on the "Files" sub-menu when viewing a diff.
++
+Originally when navigating between patch screens the highlighted row
+(pointer) of the file list table would not change when not directly
+interacting with the table e.g. by clicking on the previous or next
+file link.
++
+This patch updates the file list table whenever a new patch screen is loaded
+so that the pointer corresponds to the current patch being displayed.
+
+* Don't hyperlink non-internal groups
++
+When an external group (such as LDAP) is used in a permission rule,
+don't attempt to link to the group in the internal account system UI.
+The group won't load successfully. Instead just display the name and
+put the UUID into a tooltip to show the full DN.
+
+* Fix: Popup jumps back to original position when resizing screen
++
+On 'Watched Projects' screen, the 'Browse' button displays a popup
+window. If the user moves it and then resizes the screen, it won't snap
+back to the original position.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1457[issue 1457]:
+  Prevent groups from being renamed to empty string
+
+* Fixed AccountGroupInfoScreen search callback
++
+If the search returned no results, the search button would not be
+enabled and the status panel was not shown. Fixed the panel and button
+to always be enabled.
+
+* Fix NullPointerException on `/p/`
++
+Requesting just `/p/` caused a NullPointerException as the redirection
+logic had no project name to form a URL from. Detect requests for `/p/`
+and redirect to 'Admin' > 'Projects' to show the projects the caller
+has access to.
+
+Mail
+~~~~
+
+* Fix: Rebase did not mail all reviewers
+
+* Fix email showing in AccountLink instead of names
++
+Prefer the full name for the display text of the link.
+
+* Fix signature delimiter for e-mail messages
++
+Make sure the signature delimiter is "-- " (two dashes and a space).
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1397[issue 1397]:
+  Don't wait for banner message from SMTP server after STARTTLS
+  negotiation
++
+According to RFC 2847 section 5.2, SMTP server won't send the banner
+message again after STARTTLS negotiation. The original code will hang
+until SMTP server kicks it off due to timeout and can't send email with
+STARTTLS enabled, aka. `sendemail.smtpEncryption = tls`.
+
+SSH
+~~~
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1437[issue 1437]:
+  Send event to stream when draft change is published
++
+When a change is uploaded as a draft, a `patchset-created` event is
+sent to the event stream, but since drafts are private to the owner,
+the event is not publicly visible.  When the draft is later published,
+no publicly visible event was sent. As result of this external tools
+that rely on the event stream to detect new changes didn't receive
+events for any changes that were first uploaded as draft.
++
+There is now a new event, `draft-published`, which is sent to the
+event stream when a draft change is published.  The content of this
+event is the same as `patchset-created`.
+
+* Fix: Wrong ps/rev in `change-merged` stream-event
++
+When using cherry-pick as merge strategy, the wrong ref was set in the
+`change-merged` stream-event.
++
+The issue stems from Gerrit would not acknowledge the resulting new
+pachset (the actual cherry-pick).
+
+* Fix the `export-review-notes` command's Guice bindings
++
+The `export-review-notes` command was broken becasue of the CachePool
+class being bound twice. The startup of the command failed because of
+that.
+
+* Fix sorting of SSH help text
++
+Commands were displaying in random order, sort commands before output.
+
+* `replicate` command: Do not log errors for wrong user input
++
+If the user provided an invalid combination of command options or an
+non existing project name this was logged in the `error.log` but
+printing the error out to the user is sufficient.
+
+Authentication
+~~~~~~~~~~~~~~
+
+* Fix NPE in LdapRealm caused by non-LDAP users
++
+Servers that are connected to LDAP but have non-LDAP user accounts
+created by `gerrit create-account` (e.g. batch role accounts for
+build systems) were crashing with a NullPointerException when the
+LdapRealm tried to discover which LDAP groups the non-LDAP user
+was a member of in the directory.
+
+* Fix domain field of HTTP digest authentication
++
+Per RFC 2617 the domain field is optional. If it is not present,
+the digest token is valid on any URL on the server. When set it
+must be a path prefix describing the URLs that the password would
+be valid against.
++
+When a canonical URL is known, supply that as the only domain that
+is valid. When the URL is missing (e.g. because the provider is
+still broken) rely on the context path of the application instead.
+
+Replication
+~~~~~~~~~~~
+
+* Fix inconsistent behaviour when replicating `refs/meta/config`
++
+In `replication.config`, if `authGroup` is set to be used together with
+`mirror = true`, refs blocked through the `authGroup` are deleted from
+the slave/mirror. The same correctly applies if the `authGroup` is used
+to block `refs/meta/config`.
++
+However, if `replicatePermission` was set to `false`, Gerrit was
+refusing to clean up `refs/meta/config` on the slave/mirror.
+
+* Fix bug with member assignment order in PushReplication.
++
+The groupCache was being used before it was set in the class. Fix the
+ordering of the assignment.
+
+Approval Categories
+~~~~~~~~~~~~~~~~~~~
+
+* Make `NoBlock` and `NoOp` approval category functions work
+
+* Fix category block status without negative score
++
+Categories without blocking or approval scores will result in the
+blocking/approved image appearing in the category column after changes
+are merged should the score by the reviewer match the minimum or
+maximum value respectively.
++
+A check to ignore "No Score" values of 0 was added.
+
+* Fix NPE in `PRED__load_commit_labels_1`
++
+If a change query uses reviewer information and loads the approvals
+map, but there are no approvals for a given patch set available, the
+collection came out null, which cannot be iterated. Make it always be
+an empty list.
+
+Other
+~~~~~
+
+* Set link:../Documentation/config-gerrit.html#transfer.timeout[transfer
+  timeout] for pushes through HTTP
++
+The transfer timeout was only set when pushing via SSH.
+
+* link:../Documentation/config-gerrit.html#receive.maxObjectSizeLimit[
+  Limit maximum Git object size] when pushing through HTTP
++
+The limit for the maximum object size was only set when pushing via SSH.
+
+* Fix units of `httpd.maxwait`
++
+The default unit here is minutes, but Jetty wants to get milliseconds
+from the maxWait field. Convert the minutes returned by getTimeUnit to
+be milliseconds, matching what Jetty expects.
++
+This should resolve a large number of 503 errors for Git over HTTP.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1493[issue 1493]:
+  Fix wrong "change ... closed" message on direct push
++
+Pushing a commit directly into the central repository with bypassing
+code review wrongly resulted in a "change ... closed" message if the
+commit was already pushed for review and if a Change-Id was included in
+the commit message. Despite of the error message the push succeeded and
+the corresponding change got closed. Now the message is not printed
+anymore.
+
+* Fix NPE that can hide guice CreationException on site init
++
+Note that the `--show-stack-trace` option is needed to print the stack
+trace when a program stops with a Die exception.
+
+* Do not automatically add author/committer as reviewer to drafts
+
+* Fix NullPointerException in MergeOp
++
+The body of the commit object may have been discarded earlier to
+save memory, so ensure it exists before asking for the author.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=1396[issue 1396]:
+  Initialize the submodule commit message buffer
+
+* Create index for submodule subscriptions on site upgrade
+
+* Fix URL to Jetty XML DTDs so they can be properly validated
+
+* Fix resource leak when `changeMerge.test` is `true`
+
+* Fix possible synchronization issue in TaskThunk
+
+* Make sure we use only one type of NoteMerger for review notes creation
+
+* Fix generation of owner group in GroupDetail
++
+Set the GroupDetail.ownerGroup to the AccountGroup.ownerGroupUUID
+instead of the groupUUID.
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java
index 8bb0709..5a600c0 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/DefaultCacheFactory.java
@@ -22,10 +22,12 @@
 import com.google.common.cache.Weigher;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.cache.CacheBinding;
+import com.google.gerrit.server.cache.ForwardingRemovalListener;
 import com.google.gerrit.server.cache.MemoryCacheFactory;
 import com.google.gerrit.server.cache.PersistentCacheFactory;
 import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
 import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
 
@@ -37,6 +39,13 @@
   public static class Module extends LifecycleModule {
     @Override
     protected void configure() {
+      install(new FactoryModule() {
+        @Override
+        protected void configure() {
+          factory(ForwardingRemovalListener.Factory.class);
+        }
+      });
+
       bind(DefaultCacheFactory.class);
       bind(MemoryCacheFactory.class).to(DefaultCacheFactory.class);
       bind(PersistentCacheFactory.class).to(H2CacheFactory.class);
@@ -45,10 +54,13 @@
   }
 
   private final Config cfg;
+  private final ForwardingRemovalListener.Factory forwardingRemovalListenerFactory;
 
   @Inject
-  public DefaultCacheFactory(@GerritServerConfig Config config) {
+  public DefaultCacheFactory(@GerritServerConfig Config config,
+      ForwardingRemovalListener.Factory forwardingRemovalListenerFactory) {
     this.cfg = config;
+    this.forwardingRemovalListenerFactory = forwardingRemovalListenerFactory;
   }
 
   @Override
@@ -73,6 +85,8 @@
         "cache", def.name(), "memoryLimit",
         def.maximumWeight()));
 
+    builder.removalListener(forwardingRemovalListenerFactory.create(def.name()));
+
     Weigher<K, V> weigher = def.weigher();
     if (weigher != null && unwrapValueHolder) {
       final Weigher<K, V> impl = weigher;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index 10b1924..e57d704 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -38,6 +38,7 @@
 
   public static final String MINE = "/";
   public static final String ADMIN_GROUPS = "/admin/groups/";
+  public static final String ADMIN_CREATE_GROUP = "/admin/create-group/";
   public static final String ADMIN_PROJECTS = "/admin/projects/";
   public static final String ADMIN_CREATE_PROJECT = "/admin/create-project/";
   public static final String ADMIN_PLUGINS = "/admin/plugins/";
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/audit/Audit.java b/gerrit-common/src/main/java/com/google/gerrit/common/audit/Audit.java
new file mode 100644
index 0000000..90c7f75
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/audit/Audit.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.audit;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Audit annotation for JSON/RPC interfaces.
+ *
+ * Flag with @Audit all the JSON/RPC methods to
+ * be traced in audit-trail and submitted to the
+ * AuditService.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Audit {
+  String action() default "";
+
+  /** List of positions of parameters to be obfuscated in audit-trail (i.e. passwords) */
+  int[] obfuscate() default {};
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/auth/userpass/UserPassAuthService.java b/gerrit-common/src/main/java/com/google/gerrit/common/auth/userpass/UserPassAuthService.java
index 1d25a3d..0936d23 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/auth/userpass/UserPassAuthService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/auth/userpass/UserPassAuthService.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.auth.userpass;
 
+import com.google.gerrit.common.audit.Audit;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.AllowCrossSiteRequest;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
@@ -22,6 +23,7 @@
 
 @RpcImpl(version = Version.V2_0)
 public interface UserPassAuthService extends RemoteJsonService {
+  @Audit(action = "sign in", obfuscate={1})
   @AllowCrossSiteRequest
   void authenticate(String username, String password,
       AsyncCallback<LoginResult> callback);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java b/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
new file mode 100644
index 0000000..a5ab851
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
@@ -0,0 +1,73 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.changes;
+
+import java.util.EnumSet;
+
+/** Output options available when using {@code /changes/} RPCs. */
+public enum ListChangesOption {
+  LABELS(0),
+
+  /** Return information on the current patch set of the change. */
+  CURRENT_REVISION(1),
+  ALL_REVISIONS(2),
+
+  /** If revisions are included, parse the commit object. */
+  CURRENT_COMMIT(3),
+  ALL_COMMITS(4),
+
+  /** If a patch set is included, include the files of the patch set. */
+  CURRENT_FILES(5),
+  ALL_FILES(6);
+
+  private final int value;
+
+  private ListChangesOption(int v) {
+    this.value = v;
+  }
+
+  public int getValue() {
+    return value;
+  }
+
+  public static ListChangesOption fromValue(int value) {
+    return ListChangesOption.values()[value];
+  }
+
+  public static EnumSet<ListChangesOption> fromBits(int v) {
+    EnumSet<ListChangesOption> r = EnumSet.noneOf(ListChangesOption.class);
+    for (ListChangesOption o : ListChangesOption.values()) {
+      if ((v & (1 << o.value)) != 0) {
+        r.add(o);
+        v &= ~(1 << o.value);
+      }
+      if (v == 0) {
+        return r;
+      }
+    }
+    if (v != 0) {
+      throw new IllegalArgumentException("unknown " + Integer.toHexString(v));
+    }
+    return r;
+  }
+
+  public static int toBits(EnumSet<ListChangesOption> set) {
+    int r = 0;
+    for (ListChangesOption o : set) {
+      r |= 1 << o.value;
+    }
+    return r;
+  }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
index 4de576a..aa212f9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
@@ -14,16 +14,18 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.reviewdb.client.ContactInformation;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
 import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.VoidResult;
 import com.google.gwtjsonrpc.common.RpcImpl.Version;
+import com.google.gwtjsonrpc.common.VoidResult;
 
 import java.util.List;
 import java.util.Set;
@@ -33,20 +35,25 @@
   @SignInRequired
   void mySshKeys(AsyncCallback<List<AccountSshKey>> callback);
 
+  @Audit
   @SignInRequired
   void addSshKey(String keyText, AsyncCallback<AccountSshKey> callback);
 
+  @Audit
   @SignInRequired
   void deleteSshKeys(Set<AccountSshKey.Id> ids,
       AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void changeUserName(String newName, AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void generatePassword(AccountExternalId.Key key,
       AsyncCallback<AccountExternalId> callback);
 
+  @Audit
   @SignInRequired
   void clearPassword(AccountExternalId.Key key,
       AsyncCallback<AccountExternalId> gerritCallback);
@@ -55,23 +62,28 @@
   void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);
 
   @SignInRequired
-  void myGroups(AsyncCallback<List<GroupDetail>> callback);
+  void myGroups(AsyncCallback<List<AccountGroup>> callback);
 
+  @Audit
   @SignInRequired
   void deleteExternalIds(Set<AccountExternalId.Key> keys,
       AsyncCallback<Set<AccountExternalId.Key>> callback);
 
+  @Audit
   @SignInRequired
   void updateContact(String fullName, String emailAddr,
       ContactInformation info, AsyncCallback<Account> callback);
 
+  @Audit
   @SignInRequired
   void enterAgreement(String agreementName,
       AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void registerEmail(String address, AsyncCallback<Account> callback);
 
+  @Audit
   @SignInRequired
   void validateEmail(String token, AsyncCallback<VoidResult> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
index 7377d7e..18cf657 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountService.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
@@ -36,10 +37,12 @@
   @SignInRequired
   void myDiffPreferences(AsyncCallback<AccountDiffPreference> callback);
 
+  @Audit
   @SignInRequired
   void changePreferences(AccountGeneralPreferences pref,
       AsyncCallback<VoidResult> gerritCallback);
 
+  @Audit
   @SignInRequired
   void changeDiffPreferences(AccountDiffPreference diffPref,
       AsyncCallback<VoidResult> callback);
@@ -47,14 +50,17 @@
   @SignInRequired
   void myProjectWatch(AsyncCallback<List<AccountProjectWatchInfo>> callback);
 
+  @Audit
   @SignInRequired
   void addProjectWatch(String projectName, String filter,
       AsyncCallback<AccountProjectWatchInfo> callback);
 
+  @Audit
   @SignInRequired
   void updateProjectWatch(AccountProjectWatch watch,
       AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void deleteProjectWatches(Set<AccountProjectWatch.Key> keys,
       AsyncCallback<VoidResult> callback);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetailService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetailService.java
index 8b43624..c50d2e3 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetailService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetailService.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.Change;
@@ -25,12 +26,16 @@
 
 @RpcImpl(version = Version.V2_0)
 public interface ChangeDetailService extends RemoteJsonService {
+  @Audit
   void changeDetail(Change.Id id, AsyncCallback<ChangeDetail> callback);
 
+  @Audit
   void includedInDetail(Change.Id id, AsyncCallback<IncludedInDetail> callback);
 
+  @Audit
   void patchSetDetail(PatchSet.Id key, AsyncCallback<PatchSetDetail> callback);
 
+  @Audit
   void patchSetDetail2(PatchSet.Id baseId, PatchSet.Id key,
       AccountDiffPreference diffPrefs, AsyncCallback<PatchSetDetail> callback);
 
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
index 0ddd239..0c466497 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeListService.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
@@ -28,6 +29,7 @@
    *
    * @param req the add and remove cluster.
    */
+  @Audit
   @SignInRequired
   void toggleStars(ToggleStarRequest req, AsyncCallback<VoidResult> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java
index 872bddc..4ef6b3e 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwtjsonrpc.common.AsyncCallback;
@@ -24,27 +25,34 @@
 
 @RpcImpl(version = Version.V2_0)
 public interface ChangeManageService extends RemoteJsonService {
+  @Audit
   @SignInRequired
   void submit(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
 
+  @Audit
   @SignInRequired
   void abandonChange(PatchSet.Id patchSetId, String message,
       AsyncCallback<ChangeDetail> callback);
 
+  @Audit
   @SignInRequired
   void revertChange(PatchSet.Id patchSetId, String message,
       AsyncCallback<ChangeDetail> callback);
 
+  @Audit
   @SignInRequired
   void restoreChange(PatchSet.Id patchSetId, String message,
       AsyncCallback<ChangeDetail> callback);
 
+  @Audit
   @SignInRequired
   void publish(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
 
+  @Audit
   @SignInRequired
   void deleteDraftChange(PatchSet.Id patchSetId, AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void rebaseChange(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
index ffa1e3e..5cb7fa2 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupInclude;
@@ -28,48 +29,60 @@
 
 @RpcImpl(version = Version.V2_0)
 public interface GroupAdminService extends RemoteJsonService {
+  @Audit
   @SignInRequired
   void visibleGroups(AsyncCallback<GroupList> callback);
 
+  @Audit
   @SignInRequired
   void createGroup(String newName, AsyncCallback<AccountGroup.Id> callback);
 
+  @Audit
   @SignInRequired
   void groupDetail(AccountGroup.Id groupId, AccountGroup.UUID uuid,
       AsyncCallback<GroupDetail> callback);
 
+  @Audit
   @SignInRequired
   void changeGroupDescription(AccountGroup.Id groupId, String description,
       AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void changeGroupOptions(AccountGroup.Id groupId, GroupOptions groupOptions,
       AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void changeGroupOwner(AccountGroup.Id groupId, String newOwnerName,
       AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void renameGroup(AccountGroup.Id groupId, String newName,
       AsyncCallback<GroupDetail> callback);
 
+  @Audit
   @SignInRequired
   void changeGroupType(AccountGroup.Id groupId, AccountGroup.Type newType,
       AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void addGroupMember(AccountGroup.Id groupId, String nameOrEmail,
       AsyncCallback<GroupDetail> callback);
 
+  @Audit
   @SignInRequired
   void addGroupInclude(AccountGroup.Id groupId, String groupName,
       AsyncCallback<GroupDetail> callback);
 
+  @Audit
   @SignInRequired
   void deleteGroupMembers(AccountGroup.Id groupId,
       Set<AccountGroupMember.Key> keys, AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void deleteGroupIncludes(AccountGroup.Id groupId,
       Set<AccountGroupInclude.Key> keys, AsyncCallback<VoidResult> callback);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java
index 6352461..b3095cd 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupList.java
@@ -14,25 +14,27 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.reviewdb.client.AccountGroup;
+
 import java.util.List;
 
 public class GroupList {
-  protected List<GroupDetail> groups;
+  protected List<AccountGroup> groups;
   protected boolean canCreateGroup;
 
   protected GroupList() {
   }
 
-  public GroupList(final List<GroupDetail> groups, final boolean canCreateGroup) {
+  public GroupList(final List<AccountGroup> groups, final boolean canCreateGroup) {
     this.groups = groups;
     this.canCreateGroup = canCreateGroup;
   }
 
-  public List<GroupDetail> getGroups() {
+  public List<AccountGroup> getGroups() {
     return groups;
   }
 
-  public void setGroups(List<GroupDetail> groups) {
+  public void setGroups(List<AccountGroup> groups) {
     this.groups = groups;
   }
 
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
index 0191544..91ecb92 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
@@ -34,13 +35,16 @@
 
 @RpcImpl(version = Version.V2_0)
 public interface PatchDetailService extends RemoteJsonService {
+  @Audit
   void patchScript(Patch.Key key, PatchSet.Id a, PatchSet.Id b,
       AccountDiffPreference diffPrefs, AsyncCallback<PatchScript> callback);
 
+  @Audit
   @SignInRequired
   void saveDraft(PatchLineComment comment,
       AsyncCallback<PatchLineComment> callback);
 
+  @Audit
   @SignInRequired
   void deleteDraft(PatchLineComment.Key key, AsyncCallback<VoidResult> callback);
 
@@ -57,18 +61,22 @@
    *        change, then <code>null</code> is passed as result to
    *        {@link AsyncCallback#onSuccess(Object)}
    */
+  @Audit
   @SignInRequired
   void deleteDraftPatchSet(PatchSet.Id psid, AsyncCallback<ChangeDetail> callback);
 
+  @Audit
   @SignInRequired
   void publishComments(PatchSet.Id psid, String message,
       Set<ApprovalCategoryValue.Id> approvals,
       AsyncCallback<VoidResult> callback);
 
+  @Audit
   @SignInRequired
   void addReviewers(Change.Id id, List<String> reviewers, boolean confirmed,
       AsyncCallback<ReviewerResult> callback);
 
+  @Audit
   @SignInRequired
   void removeReviewer(Change.Id id, Account.Id reviewerId,
       AsyncCallback<ReviewerResult> callback);
@@ -82,6 +90,7 @@
   /**
    * Update the reviewed status for the patch.
    */
+  @Audit
   @SignInRequired
   void setReviewedByCurrentUser(Key patchKey, boolean reviewed, AsyncCallback<VoidResult> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
index a650117..13c0a48 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
@@ -34,6 +35,7 @@
   void projectDetail(Project.NameKey projectName,
       AsyncCallback<ProjectDetail> callback);
 
+  @Audit
   @SignInRequired
   void createNewProject(String projectName, String parentName,
       boolean emptyCommit, boolean permissionsOnly,
@@ -42,10 +44,12 @@
   void projectAccess(Project.NameKey projectName,
       AsyncCallback<ProjectAccess> callback);
 
+  @Audit
   @SignInRequired
   void changeProjectSettings(Project update,
       AsyncCallback<ProjectDetail> callback);
 
+  @Audit
   @SignInRequired
   void changeProjectAccess(Project.NameKey projectName, String baseRevision,
       String message, List<AccessSection> sections,
@@ -59,10 +63,12 @@
   void listBranches(Project.NameKey projectName,
       AsyncCallback<ListBranchesResult> callback);
 
+  @Audit
   @SignInRequired
   void addBranch(Project.NameKey projectName, String branchName,
       String startingRevision, AsyncCallback<ListBranchesResult> callback);
 
+  @Audit
   @SignInRequired
   void deleteBranch(Project.NameKey projectName, Set<Branch.NameKey> ids,
       AsyncCallback<Set<Branch.NameKey>> callback);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 7a84b20..aefad27 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client;
 
+import static com.google.gerrit.common.PageLinks.ADMIN_CREATE_GROUP;
 import static com.google.gerrit.common.PageLinks.ADMIN_CREATE_PROJECT;
 import static com.google.gerrit.common.PageLinks.ADMIN_GROUPS;
 import static com.google.gerrit.common.PageLinks.ADMIN_PROJECTS;
@@ -47,6 +48,7 @@
 import com.google.gerrit.client.admin.AccountGroupInfoScreen;
 import com.google.gerrit.client.admin.AccountGroupMembersScreen;
 import com.google.gerrit.client.admin.AccountGroupScreen;
+import com.google.gerrit.client.admin.CreateGroupScreen;
 import com.google.gerrit.client.admin.CreateProjectScreen;
 import com.google.gerrit.client.admin.GroupListScreen;
 import com.google.gerrit.client.admin.PluginListScreen;
@@ -631,6 +633,10 @@
             || matchExact("/admin/create-project", token)) {
           Gerrit.display(token, new CreateProjectScreen());
 
+        } else if (matchExact(ADMIN_CREATE_GROUP, token)
+            || matchExact("/admin/create-group", token)) {
+          Gerrit.display(token, new CreateGroupScreen());
+
         } else {
           Gerrit.display(token, new NotFoundScreen());
         }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 267419f..be74428 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.client;
 
 import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
+import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
 
 import com.google.gerrit.client.account.AccountCapabilities;
 import com.google.gerrit.client.auth.openid.OpenIdSignInDialog;
@@ -584,10 +585,23 @@
     addDiffLink(diffBar, C.menuDiffPatchSets(), PatchScreen.TopView.PATCH_SETS);
     addDiffLink(diffBar, C.menuDiffFiles(), PatchScreen.TopView.FILES);
 
+    final LinkMenuBar projectsBar = new LinkMenuBar();
+    addLink(projectsBar, C.menuProjectsList(), PageLinks.ADMIN_PROJECTS);
+    if(signedIn) {
+      AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
+        @Override
+        public void onSuccess(AccountCapabilities result) {
+          if (result.canPerform(CREATE_PROJECT)) {
+            addLink(projectsBar, C.menuProjectsCreate(), PageLinks.ADMIN_CREATE_PROJECT);
+          }
+        }
+      }, CREATE_PROJECT);
+    }
+    menuLeft.add(projectsBar, C.menuProjects());
+
     if (signedIn) {
       final LinkMenuBar menuBar = new LinkMenuBar();
       addLink(menuBar, C.menuGroups(), PageLinks.ADMIN_GROUPS);
-      addLink(menuBar, C.menuProjects(), PageLinks.ADMIN_PROJECTS);
       AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
         @Override
         public void onSuccess(AccountCapabilities result) {
@@ -649,8 +663,11 @@
           if (cfg.getRegisterUrl() != null) {
             menuRight.add(anchor(C.menuRegister(), cfg.getRegisterUrl()));
           }
-          signInAnchor = anchor(C.menuSignIn(), loginRedirect(History.getToken()));
-          menuRight.add(signInAnchor);
+          menuRight.addItem(C.menuSignIn(), new Command() {
+            public void execute() {
+              doSignIn(History.getToken());
+            }
+          });
           break;
 
         case DEVELOPMENT_BECOME_ANY_ACCOUNT:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index 09e6b84..c47c789 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -68,10 +68,13 @@
   String menuDiffPatchSets();
   String menuDiffFiles();
 
+  String menuProjects();
+  String menuProjectsList();
+  String menuProjectsCreate();
+
   String menuAdmin();
   String menuPeople();
   String menuGroups();
-  String menuProjects();
   String menuPlugins();
 
   String menuDocumentation();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index 294ba49..67ebe2a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -51,10 +51,13 @@
 menuDiffPatchSets = Patch Sets
 menuDiffFiles = Files
 
+menuProjects = Projects
+menuProjectsList = List
+menuProjectsCreate = Create New Project
+
 menuAdmin = Admin
 menuPeople = People
 menuGroups = Groups
-menuProjects = Projects
 menuPlugins = Plugins
 
 menuDocumentation = Documentation
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index 8f38f51..9cbf5cd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -78,7 +78,7 @@
   String contributorAgreementLegal();
   String contributorAgreementShortDescription();
   String coverMessage();
-  String createProjectLink();
+  String createGroupLink();
   String createProjectPanel();
   String dataCell();
   String dataHeader();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
index b5c0900..d763ff1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritResources.java
@@ -39,4 +39,7 @@
 
   @Source("redNot.png")
   public ImageResource redNot();
+
+  @Source("downloadIcon.png")
+  public ImageResource downloadIcon();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java
index e3d8468..7e7b927 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchPanel.java
@@ -27,6 +27,8 @@
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
 
@@ -42,15 +44,22 @@
     searchBox = new HintTextBox();
     searchBox.setVisibleLength(70);
     searchBox.setHintText(Gerrit.C.searchHint());
+    final MySuggestionDisplay suggestionDisplay = new MySuggestionDisplay();
     searchBox.addKeyPressHandler(new KeyPressHandler() {
       @Override
       public void onKeyPress(final KeyPressEvent event) {
         if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
-          doSearch();
+          if (!suggestionDisplay.isSuggestionSelected) {
+            doSearch();
+          }
         }
       }
     });
 
+    final SuggestBox suggestBox =
+        new SuggestBox(new SearchSuggestOracle(), searchBox, suggestionDisplay);
+    searchBox.setStyleName("gwt-TextBox");
+
     final Button searchButton = new Button(Gerrit.C.searchButton());
     searchButton.addClickHandler(new ClickHandler() {
       @Override
@@ -59,7 +68,7 @@
       }
     });
 
-    body.add(searchBox);
+    body.add(suggestBox);
     body.add(searchButton);
   }
 
@@ -106,4 +115,15 @@
       Gerrit.display(PageLinks.toChangeQuery(query), QueryScreen.forQuery(query));
     }
   }
+
+  private static class MySuggestionDisplay extends SuggestBox.DefaultSuggestionDisplay {
+    private boolean isSuggestionSelected;
+
+    @Override
+    protected Suggestion getCurrentSelection() {
+      Suggestion currentSelection = super.getCurrentSelection();
+      isSuggestionSelected = currentSelection != null;
+      return currentSelection;
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
new file mode 100644
index 0000000..172a2af
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -0,0 +1,140 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client;
+
+import com.google.gwt.user.client.ui.SuggestOracle;
+import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
+
+import java.util.ArrayList;
+import java.util.TreeSet;
+
+public class SearchSuggestOracle extends HighlightSuggestOracle {
+  private static final TreeSet<String> suggestions = new TreeSet<String>();
+
+  static {
+    suggestions.add("age:");
+    suggestions.add("age:1week"); // Give an example age
+
+    suggestions.add("change:");
+
+    suggestions.add("owner:");
+    suggestions.add("owner:self");
+    suggestions.add("ownerin:");
+
+    suggestions.add("reviewer:");
+    suggestions.add("reviewer:self");
+    suggestions.add("reviewerin:");
+
+    suggestions.add("commit:");
+    suggestions.add("project:");
+    suggestions.add("branch:");
+    suggestions.add("topic:");
+    suggestions.add("ref:");
+    suggestions.add("tr:");
+    suggestions.add("bug:");
+    suggestions.add("label:");
+    suggestions.add("message:");
+    suggestions.add("file:");
+
+    suggestions.add("has:");
+    suggestions.add("has:draft");
+    suggestions.add("has:star");
+
+    suggestions.add("is:");
+    suggestions.add("is:starred");
+    suggestions.add("is:watched");
+    suggestions.add("is:reviewed");
+    suggestions.add("is:owner");
+    suggestions.add("is:reviewer");
+    suggestions.add("is:open");
+    suggestions.add("is:draft");
+    suggestions.add("is:closed");
+    suggestions.add("is:submitted");
+    suggestions.add("is:merged");
+    suggestions.add("is:abandoned");
+
+    suggestions.add("status:");
+    suggestions.add("status:open");
+    suggestions.add("status:reviewed");
+    suggestions.add("status:submitted");
+    suggestions.add("status:closed");
+    suggestions.add("status:merged");
+    suggestions.add("status:abandoned");
+
+    suggestions.add("AND");
+    suggestions.add("OR");
+    suggestions.add("NOT");
+  }
+
+  @Override
+  public void requestDefaultSuggestions(Request request, Callback done) {
+    final ArrayList<SearchSuggestion> r = new ArrayList<SearchSuggestOracle.SearchSuggestion>();
+    // No text - show some default suggestions.
+    r.add(new SearchSuggestion("status:open", "status:open"));
+    r.add(new SearchSuggestion("age:1week", "age:1week"));
+    if (Gerrit.isSignedIn()) {
+      r.add(new SearchSuggestion("owner:self", "owner:self"));
+    }
+    done.onSuggestionsReady(request, new Response(r));
+  }
+
+  @Override
+  protected void onRequestSuggestions(Request request, Callback done) {
+    final String query = request.getQuery();
+    int lastSpace = query.lastIndexOf(' ');
+    final String lastWord;
+    // NOTE: this method is not called if the query is empty.
+    if (lastSpace == query.length() - 1) {
+      // Starting a new word - don't show suggestions yet.
+      done.onSuggestionsReady(request, null);
+      return;
+    } else if (lastSpace == -1) {
+      lastWord = query;
+    } else {
+      lastWord = query.substring(lastSpace + 1);
+    }
+
+    final ArrayList<SearchSuggestion> r = new ArrayList<SearchSuggestOracle.SearchSuggestion>();
+    for (String suggestion : suggestions.tailSet(lastWord)) {
+      if ((lastWord.length() < suggestion.length()) && suggestion.startsWith(lastWord)) {
+        if (suggestion.contains("self") && !Gerrit.isSignedIn()) {
+          continue;
+        }
+        r.add(new SearchSuggestion(suggestion, query + suggestion.substring(lastWord.length())));
+      }
+    }
+    done.onSuggestionsReady(request, new Response(r));
+  }
+
+  private static class SearchSuggestion implements SuggestOracle.Suggestion {
+    private final String suggestion;
+    private final String fullQuery;
+    public SearchSuggestion(String suggestion, String fullQuery) {
+      this.suggestion = suggestion;
+      // Add a space to the query if it is a complete operation (e.g.
+      // "status:open") so the user can keep on typing.
+      this.fullQuery = fullQuery.endsWith(":") ? fullQuery : fullQuery + " ";
+    }
+    @Override
+    public String getDisplayString() {
+      return suggestion;
+    }
+
+    @Override
+    public String getReplacementString() {
+      return fullQuery;
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
index 719c395..6cb749d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyGroupsScreen.java
@@ -16,7 +16,7 @@
 
 import com.google.gerrit.client.admin.GroupTable;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.common.data.GroupDetail;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 
 import java.util.List;
 
@@ -33,8 +33,9 @@
   @Override
   protected void onLoad() {
     super.onLoad();
-    Util.ACCOUNT_SEC.myGroups(new ScreenLoadCallback<List<GroupDetail>>(this) {
-      public void preDisplay(final List<GroupDetail> result) {
+    Util.ACCOUNT_SEC.myGroups(new ScreenLoadCallback<List<AccountGroup>>(this) {
+      @Override
+      public void preDisplay(final List<AccountGroup> result) {
         groups.display(result);
       }
     });
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index a9ad519..a085ce5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -60,7 +60,6 @@
   String noMembersInfo();
   String headingExternalGroup();
   String headingCreateGroup();
-  String headingCreateProject();
   String headingParentProjectName();
   String columnProjectName();
   String headingAgreements();
@@ -98,6 +97,7 @@
   String groupListOpen();
 
   String groupListTitle();
+  String createGroupTitle();
   String groupTabGeneral();
   String groupTabMembers();
   String projectListTitle();
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 e20d544..406f3d3 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
@@ -41,7 +41,6 @@
 noMembersInfo = Group Members can only be viewed for Gerrit internal groups. For external groups and Gerrit system groups the members cannot be displayed.
 headingExternalGroup = Selected External Group
 headingCreateGroup = Create New Group
-headingCreateProject = Create New Project
 headingAgreements = Contributor Agreements
 
 projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
@@ -78,6 +77,7 @@
 groupListOpen = Open group
 
 groupListTitle = Groups
+createGroupTitle = Create Group
 groupTabGeneral = General
 groupTabMembers = Members
 projectListTitle = Projects
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java
new file mode 100644
index 0000000..4574c6d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateGroupScreen.java
@@ -0,0 +1,121 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.admin;
+
+import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
+
+import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.NotFoundScreen;
+import com.google.gerrit.client.account.AccountCapabilities;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.OnEditEnabler;
+import com.google.gerrit.client.ui.Screen;
+import com.google.gerrit.client.ui.SmallHeading;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+
+public class CreateGroupScreen extends Screen {
+
+  private NpTextBox addTxt;
+  private Button addNew;
+
+  public CreateGroupScreen() {
+    super();
+    setRequiresSignIn(true);
+  }
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
+      @Override
+      public void onSuccess(AccountCapabilities ac) {
+        if (ac.canPerform(CREATE_GROUP)) {
+          display();
+        } else {
+          Gerrit.display(PageLinks.ADMIN_CREATE_GROUP, new NotFoundScreen());
+        }
+      }
+    }, CREATE_GROUP);
+  }
+
+  @Override
+  protected void onInitUI() {
+    super.onInitUI();
+    setPageTitle(Util.C.createGroupTitle());
+    addCreateGroupPanel();
+  }
+
+  private void addCreateGroupPanel() {
+    VerticalPanel addPanel = new VerticalPanel();
+    addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
+    addPanel.add(new SmallHeading(Util.C.headingCreateGroup()));
+
+    addTxt = new NpTextBox();
+    addTxt.setVisibleLength(60);
+    addTxt.addKeyPressHandler(new KeyPressHandler() {
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
+          doCreateGroup();
+        }
+      }
+    });
+    addPanel.add(addTxt);
+
+    addNew = new Button(Util.C.buttonCreateGroup());
+    addNew.setEnabled(false);
+    addNew.addClickHandler(new ClickHandler() {
+      @Override
+      public void onClick(final ClickEvent event) {
+        doCreateGroup();
+      }
+    });
+    addPanel.add(addNew);
+    add(addPanel);
+
+    new OnEditEnabler(addNew, addTxt);
+  }
+
+  private void doCreateGroup() {
+    final String newName = addTxt.getText();
+    if (newName == null || newName.length() == 0) {
+      return;
+    }
+
+    addNew.setEnabled(false);
+    Util.GROUP_SVC.createGroup(newName, new GerritCallback<AccountGroup.Id>() {
+      public void onSuccess(final AccountGroup.Id result) {
+        History.newItem(Dispatcher.toGroup(result, AccountGroupScreen.MEMBERS));
+      }
+
+      @Override
+      public void onFailure(Throwable caught) {
+        super.onFailure(caught);
+        addNew.setEnabled(true);
+      }
+    });
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index c0a55d0..dc58e22 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -14,54 +14,26 @@
 
 package com.google.gerrit.client.admin;
 
-import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
-
-import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountCapabilities;
-import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.AccountScreen;
-import com.google.gerrit.client.ui.OnEditEnabler;
-import com.google.gerrit.client.ui.SmallHeading;
+import com.google.gerrit.client.ui.Hyperlink;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.GroupList;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.user.client.History;
-import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.VerticalPanel;
-import com.google.gwtexpui.globalkey.client.NpTextBox;
 
 public class GroupListScreen extends AccountScreen {
+  private VerticalPanel createGroupLinkPanel;
   private GroupTable groups;
 
-  private VerticalPanel addPanel;
-  private NpTextBox addTxt;
-  private Button addNew;
-
   @Override
   protected void onLoad() {
     super.onLoad();
-    addPanel.setVisible(false);
-    AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
-      @Override
-      public void onSuccess(AccountCapabilities ac) {
-        addPanel.setVisible(ac.canPerform(CREATE_GROUP));
-      }
-    }, CREATE_GROUP);
     Util.GROUP_SVC
         .visibleGroups(new ScreenLoadCallback<GroupList>(this) {
           @Override
           protected void preDisplay(GroupList result) {
+            createGroupLinkPanel.setVisible(result.isCanCreateGroup());
             groups.display(result.getGroups());
             groups.finishDisplay();
           }
@@ -73,56 +45,14 @@
     super.onInitUI();
     setPageTitle(Util.C.groupListTitle());
 
+    createGroupLinkPanel = new VerticalPanel();
+    createGroupLinkPanel.setStyleName(Gerrit.RESOURCES.css().createGroupLink());
+    createGroupLinkPanel.add(new Hyperlink(Util.C.headingCreateGroup(),
+        PageLinks.ADMIN_CREATE_GROUP));
+    add(createGroupLinkPanel);
+
     groups = new GroupTable(true /* hyperlink to admin */, PageLinks.ADMIN_GROUPS);
     add(groups);
-
-    addPanel = new VerticalPanel();
-    addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
-    addPanel.add(new SmallHeading(Util.C.headingCreateGroup()));
-
-    addTxt = new NpTextBox();
-    addTxt.setVisibleLength(60);
-    addTxt.addKeyPressHandler(new KeyPressHandler() {
-      @Override
-      public void onKeyPress(KeyPressEvent event) {
-        if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
-          doCreateGroup();
-        }
-      }
-    });
-    addPanel.add(addTxt);
-
-    addNew = new Button(Util.C.buttonCreateGroup());
-    addNew.setEnabled(false);
-    addNew.addClickHandler(new ClickHandler() {
-      @Override
-      public void onClick(final ClickEvent event) {
-        doCreateGroup();
-      }
-    });
-    addNew.addFocusHandler(new FocusHandler() {
-      @Override
-      public void onFocus(FocusEvent event) {
-        // unregister the keys for the 'groups' table so that pressing ENTER
-        // when the 'addNew' button has the focus triggers the button (if the
-        // keys for the 'groups' table would not be unregistered the 'addNew'
-        // button would not be triggered on ENTER but the group which is
-        // selected in the 'groups' table would be opened)
-        groups.setRegisterKeys(false);
-      }
-    });
-    addNew.addBlurHandler(new BlurHandler() {
-      @Override
-      public void onBlur(BlurEvent event) {
-        // re-register the keys for the 'groups' table when the 'addNew' button
-        // gets blurred
-        groups.setRegisterKeys(true);
-      }
-    });
-    addPanel.add(addNew);
-    add(addPanel);
-
-    new OnEditEnabler(addNew, addTxt);
   }
 
   @Override
@@ -130,24 +60,4 @@
     super.registerKeys();
     groups.setRegisterKeys(true);
   }
-
-  private void doCreateGroup() {
-    final String newName = addTxt.getText();
-    if (newName == null || newName.length() == 0) {
-      return;
-    }
-
-    addNew.setEnabled(false);
-    Util.GROUP_SVC.createGroup(newName, new GerritCallback<AccountGroup.Id>() {
-      public void onSuccess(final AccountGroup.Id result) {
-        History.newItem(Dispatcher.toGroup(result));
-      }
-
-      @Override
-      public void onFailure(Throwable caught) {
-        super.onFailure(caught);
-        addNew.setEnabled(true);
-      }
-    });
-  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
index 7f73226..6818841 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupTable.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.ui.Hyperlink;
 import com.google.gerrit.client.ui.NavigationTable;
-import com.google.gerrit.common.data.GroupDetail;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
@@ -32,7 +31,7 @@
 
 
 public class GroupTable extends NavigationTable<AccountGroup> {
-  private static final int NUM_COLS = 5;
+  private static final int NUM_COLS = 3;
 
   private final boolean enableLink;
 
@@ -52,9 +51,7 @@
 
     table.setText(0, 1, Util.C.columnGroupName());
     table.setText(0, 2, Util.C.columnGroupDescription());
-    table.setText(0, 3, Util.C.headingOwner());
-    table.setText(0, 4, Util.C.columnGroupType());
-    table.setText(0, 5, Util.C.columnGroupVisibleToAll());
+    table.setText(0, 3, Util.C.columnGroupVisibleToAll());
     table.addClickHandler(new ClickHandler() {
       @Override
       public void onClick(ClickEvent event) {
@@ -82,20 +79,19 @@
     History.newItem(Dispatcher.toGroup(getRowItem(row).getId()));
   }
 
-  public void display(final List<GroupDetail> result) {
+  public void display(final List<AccountGroup> result) {
     while (1 < table.getRowCount())
       table.removeRow(table.getRowCount() - 1);
 
-    for(GroupDetail detail : result) {
+    for(AccountGroup group : result) {
       final int row = table.getRowCount();
       table.insertRow(row);
       applyDataRowStyle(row);
-      populate(row, detail);
+      populate(row, group);
     }
   }
 
-  void populate(final int row, final GroupDetail detail) {
-    AccountGroup k = detail.group;
+  void populate(final int row, final AccountGroup k) {
     if (enableLink) {
       table.setWidget(row, 1, new Hyperlink(k.getName(),
           Dispatcher.toGroup(k.getId())));
@@ -103,10 +99,8 @@
       table.setText(row, 1, k.getName());
     }
     table.setText(row, 2, k.getDescription());
-    table.setText(row, 3, detail.ownerGroup.getName());
-    table.setText(row, 4, k.getType().toString());
     if (k.isVisibleToAll()) {
-      table.setWidget(row, 5, new Image(Gerrit.RESOURCES.greenCheck()));
+      table.setWidget(row, 3, new Image(Gerrit.RESOURCES.greenCheck()));
     }
 
     final FlexCellFormatter fmt = table.getFlexCellFormatter();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index 5cb178c..d911c93 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -14,12 +14,7 @@
 
 package com.google.gerrit.client.admin;
 
-import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
-
 import com.google.gerrit.client.Dispatcher;
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.account.AccountCapabilities;
-import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.projects.ProjectInfo;
 import com.google.gerrit.client.projects.ProjectMap;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
@@ -28,22 +23,13 @@
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.common.PageLinks;
 import com.google.gwt.user.client.History;
-import com.google.gwt.user.client.ui.VerticalPanel;
 
 public class ProjectListScreen extends Screen {
-  private VerticalPanel createProjectLinkPanel;
   private ProjectsTable projects;
 
   @Override
   protected void onLoad() {
     super.onLoad();
-    createProjectLinkPanel.setVisible(false);
-    AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
-      @Override
-      public void onSuccess(AccountCapabilities ac) {
-        createProjectLinkPanel.setVisible(ac.canPerform(CREATE_PROJECT));
-      }
-    }, CREATE_PROJECT);
     ProjectMap.all(new ScreenLoadCallback<ProjectMap>(this) {
       @Override
       protected void preDisplay(final ProjectMap result) {
@@ -58,13 +44,6 @@
     super.onInitUI();
     setPageTitle(Util.C.projectListTitle());
 
-    createProjectLinkPanel = new VerticalPanel();
-    createProjectLinkPanel.setStyleName(Gerrit.RESOURCES.css()
-        .createProjectLink());
-    createProjectLinkPanel.add(new Hyperlink(Util.C.headingCreateProject(),
-        PageLinks.ADMIN_CREATE_PROJECT));
-    add(createProjectLinkPanel);
-
     projects = new ProjectsTable() {
       @Override
       protected void onOpenRow(final int row) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java
index 3dd54a7..85dd794 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/auth/openid/OpenIdSsoPanel.java
@@ -16,10 +16,8 @@
 
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.SmallHeading;
 import com.google.gerrit.common.auth.SignInMode;
 import com.google.gerrit.common.auth.openid.DiscoveryResult;
-import com.google.gerrit.common.auth.openid.OpenIdUrls;
 import com.google.gwt.dom.client.FormElement;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.FormPanel;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
index debe145..560e6b3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeList.java
@@ -16,9 +16,12 @@
 
 import com.google.gerrit.client.rpc.NativeList;
 import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.common.changes.ListChangesOption;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtorm.client.KeyUtil;
 
+import java.util.EnumSet;
+
 /** List of changes available from {@code /changes/}. */
 public class ChangeList extends NativeList<ChangeInfo> {
   private static final String URI = "/changes/";
@@ -31,6 +34,7 @@
     for (String q : queries) {
       call.addParameterRaw("q", KeyUtil.encode(q));
     }
+    addOptions(call, ListChangesOption.LABELS);
     call.send(callback);
   }
 
@@ -41,6 +45,7 @@
     if (limit > 0) {
       call.addParameter("n", limit);
     }
+    addOptions(call, ListChangesOption.LABELS);
     if (!PagedSingleListScreen.MIN_SORTKEY.equals(sortkey)) {
       call.addParameter("P", sortkey);
     }
@@ -54,12 +59,19 @@
     if (limit > 0) {
       call.addParameter("n", limit);
     }
+    addOptions(call, ListChangesOption.LABELS);
     if (!PagedSingleListScreen.MAX_SORTKEY.equals(sortkey)) {
       call.addParameter("N", sortkey);
     }
     call.send(callback);
   }
 
+  private static void addOptions(
+      RestApi call, ListChangesOption option1, ListChangesOption... options) {
+    EnumSet<ListChangesOption> s = EnumSet.of(option1, options);
+    call.addParameterRaw("O", Integer.toHexString(ListChangesOption.toBits(s)));
+  }
+
   private static RestApi newQuery(String query) {
     RestApi call = new RestApi(URI);
     // The server default is ?q=status:open so don't repeat it.
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
index 234f937..5fde797 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -306,12 +306,12 @@
           }
         }
       }
-      if (detail.getNeededBy() != null) {
-        for (final ChangeInfo ci : detail.getNeededBy()) {
-          if ((ci.getStatus() == Change.Status.NEW) ||
-              (ci.getStatus() == Change.Status.SUBMITTED)) {
-            depsOpen = true;
-          }
+    }
+    if (detail.getNeededBy() != null) {
+      for (final ChangeInfo ci : detail.getNeededBy()) {
+        if ((ci.getStatus() == Change.Status.NEW) ||
+            (ci.getStatus() == Change.Status.SUBMITTED)) {
+          depsOpen = true;
         }
       }
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
index ca8aedf..77099ab 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -128,6 +128,12 @@
    * followed by the action buttons.
    */
   public void ensureLoaded(final PatchSetDetail detail) {
+    loadInfoTable(detail);
+    loadActionPanel(detail);
+    loadPatchTable(detail);
+  }
+
+  public void loadInfoTable(final PatchSetDetail detail) {
     infoTable = new Grid(R_CNT, 2);
     infoTable.setStyleName(Gerrit.RESOURCES.css().infoBlock());
     infoTable.addStyleName(Gerrit.RESOURCES.css().patchSetInfoBlock());
@@ -153,15 +159,13 @@
     displayDownload();
 
     body.add(infoTable);
+  }
 
+  public void loadActionPanel(final PatchSetDetail detail) {
     if (!patchSet.getId().equals(diffBaseId)) {
-      patchTable = new PatchTable();
-      patchTable.setSavePointerId("PatchTable " + patchSet.getId());
-      patchTable.display(diffBaseId, detail);
-
       actionsPanel = new FlowPanel();
       actionsPanel.setStyleName(Gerrit.RESOURCES.css().patchSetActions());
-      body.add(actionsPanel);
+      actionsPanel.setVisible(true);
       if (Gerrit.isSignedIn()) {
         if (changeDetail.canEdit()) {
           populateReviewAction();
@@ -173,18 +177,28 @@
           if (changeDetail.canPublish()) {
             populatePublishAction();
           }
-          if (changeDetail.canDeleteDraft() &&
-              changeDetail.getPatchSets().size() > 1) {
+          if (changeDetail.canDeleteDraft()
+              && changeDetail.getPatchSets().size() > 1) {
             populateDeleteDraftPatchSetAction();
           }
         }
       }
       populateDiffAllActions(detail);
-      body.add(patchTable);
+      body.add(actionsPanel);
+    }
+  }
 
-      for(ClickHandler clickHandler : registeredClickHandler) {
+  public void loadPatchTable(final PatchSetDetail detail) {
+    if (!patchSet.getId().equals(diffBaseId)) {
+      patchTable = new PatchTable();
+      patchTable.setSavePointerId("PatchTable " + patchSet.getId());
+      patchTable.display(diffBaseId, detail);
+      for (ClickHandler clickHandler : registeredClickHandler) {
         patchTable.addClickHandler(clickHandler);
       }
+      patchTable.setRegisterKeys(true);
+      setActive(true);
+      body.add(patchTable);
     }
   }
 
@@ -644,34 +658,45 @@
   }
 
   public void refresh() {
-    AccountDiffPreference diffPrefs;
-    if (patchTable == null) {
-      diffPrefs = new ListenableAccountDiffPreference().get();
+    if (patchSet.getId().equals(diffBaseId)) {
+      if (patchTable != null) {
+        patchTable.setVisible(false);
+      }
+      if (actionsPanel != null) {
+        actionsPanel.setVisible(false);
+      }
     } else {
-      diffPrefs = patchTable.getPreferences().get();
-    }
+      if (patchTable != null) {
+        if (patchTable.getBase() == null && diffBaseId == null
+            || patchTable.getBase() != null
+            && patchTable.getBase().equals(diffBaseId)) {
+          actionsPanel.setVisible(true);
+          patchTable.setVisible(true);
+          return;
+        }
+      }
 
-    Util.DETAIL_SVC.patchSetDetail2(diffBaseId, patchSet.getId(), diffPrefs,
-        new GerritCallback<PatchSetDetail>() {
-          @Override
-          public void onSuccess(PatchSetDetail result) {
-            if (patchSet.getId().equals(diffBaseId)) {
-              patchTable.setVisible(false);
-              actionsPanel.setVisible(false);
-            } else {
-              if (patchTable != null) {
-                patchTable.removeFromParent();
-              }
-              patchTable = new PatchTable();
-              patchTable.display(diffBaseId, result);
-              body.add(patchTable);
+      AccountDiffPreference diffPrefs;
+      if (patchTable == null) {
+        diffPrefs = new ListenableAccountDiffPreference().get();
+      } else {
+        diffPrefs = patchTable.getPreferences().get();
+        patchTable.setVisible(false);
+      }
 
-              for (ClickHandler clickHandler : registeredClickHandler) {
-                patchTable.addClickHandler(clickHandler);
+      Util.DETAIL_SVC.patchSetDetail2(diffBaseId, patchSet.getId(), diffPrefs,
+          new GerritCallback<PatchSetDetail>() {
+            @Override
+            public void onSuccess(PatchSetDetail result) {
+              if (actionsPanel != null) {
+                actionsPanel.setVisible(true);
+              } else {
+                loadActionPanel(result);
               }
+              loadPatchTable(result);
             }
-          }
-        });
+          });
+    }
   }
 
   @Override
@@ -687,8 +712,8 @@
       Util.DETAIL_SVC.patchSetDetail2(diffBaseId, patchSet.getId(), diffPrefs,
           new GerritCallback<PatchSetDetail>() {
             public void onSuccess(final PatchSetDetail result) {
-              ensureLoaded(result);
-              patchTable.setRegisterKeys(true);
+              loadInfoTable(result);
+              loadActionPanel(result);
             }
           });
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java
index f206d6e..005423f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetsBlock.java
@@ -89,12 +89,6 @@
     for (final PatchSet ps : patchSets) {
       final PatchSetComplexDisclosurePanel p =
           new PatchSetComplexDisclosurePanel(ps, ps == currps);
-      if (diffBaseId != null) {
-        p.setDiffBaseId(diffBaseId);
-        if (ps == currps) {
-          p.refresh();
-        }
-      }
       add(p);
       patchSetPanelsList.add(p);
     }
@@ -175,7 +169,6 @@
         deactivate();
         PatchSetComplexDisclosurePanel patchSetPanel =
             patchSetPanels.get(patchSetId);
-        patchSetPanel.setOpen(true);
         patchSetPanel.setActive(true);
         activePatchSetId = patchSetId;
       }
@@ -226,6 +219,9 @@
     public void onOpen(OpenEvent<DisclosurePanel> event) {
       // when a patch set panel is opened by the user
       // it should automatically become active
+      PatchSetComplexDisclosurePanel patchSetPanel =
+          patchSetPanels.get(patchSetId);
+      patchSetPanel.refresh();
       activate(patchSetId);
     }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
index b228cf2..b8acd56 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchTable.java
@@ -119,6 +119,10 @@
     }
   }
 
+  public PatchSet.Id getBase() {
+    return base;
+  }
+
   public void setSavePointerId(final String id) {
     savePointerId = id;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/downloadIcon.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/downloadIcon.png
new file mode 100644
index 0000000..22ff495
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/downloadIcon.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index 8bc365b..7512d8c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -28,6 +28,7 @@
 @external .gwt-InlineHyperlink;
 @external .gwt-RadioButton;
 
+@external .searchPanel;
 @external .smallHeading;
 @external .wdi;
 @external .wdd;
@@ -1148,7 +1149,7 @@
   padding: 5px 5px 5px 5px;
 }
 
-.createProjectLink {
+.createGroupLink {
   margin-bottom: 10px;
 }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
index 70dcf75..4801e65 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
@@ -48,6 +48,9 @@
   String fileList();
   String expandComment();
 
+  String toggleReviewed();
+  String markAsReviewedAndGoToNext();
+
   String commentEditorSet();
   String commentInsert();
   String commentSaveDraft();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
index 694ccb4..11823ac 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
@@ -30,6 +30,9 @@
 fileList = Browse files in patch set
 expandComment = Expand or collapse comment
 
+toggleReviewed = Toggle the reviewed flag
+markAsReviewedAndGoToNext = Mark patch as reviewed and go to next unreviewed patch
+
 commentEditorSet = Comment Editing
 commentInsert = Create a new inline comment
 commentSaveDraft = Save draft comment
@@ -45,7 +48,7 @@
 
 reviewedAnd = Reviewed &
 next = next
-download = (Download)
+download = Download
 
 fileTypeSymlink = Type: Symbolic Link
 fileTypeGitlink = Type: Git Commit in Subproject
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
index 0976a9d..4f8731c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
@@ -25,8 +25,8 @@
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.ChangeLink;
+import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.client.ui.ListenableAccountDiffPreference;
-import com.google.gerrit.client.ui.PatchLink;
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.common.data.PatchScript;
 import com.google.gerrit.common.data.PatchSetDetail;
@@ -112,6 +112,7 @@
 
   private CheckBox reviewedCheckBox;
   private FlowPanel reviewedPanel;
+  private InlineHyperlink reviewedLink;
   private HistoryTable historyTable;
   private FlowPanel topPanel;
   private FlowPanel contentPanel;
@@ -131,7 +132,9 @@
 
   /** Keys that cause an action on this screen */
   private KeyCommandSet keysNavigation;
+  private KeyCommandSet keysAction;
   private HandlerRegistration regNavigation;
+  private HandlerRegistration regAction;
   private boolean intralineFailure;
 
   /**
@@ -193,13 +196,6 @@
     Anchor reviewedAnchor = new Anchor("");
     SafeHtml.set(reviewedAnchor, text);
 
-    reviewedAnchor.addClickHandler(new ClickHandler() {
-      @Override
-      public void onClick(ClickEvent event) {
-        setReviewedByCurrentUser(true);
-      }
-    });
-
     final PatchValidator unreviewedValidator = new PatchValidator() {
       public boolean isValid(Patch patch) {
         return !patch.isReviewedByCurrentUser();
@@ -212,25 +208,20 @@
 
     if (nextUnreviewedPatchIndex > -1) {
       // Create invisible patch link to change page
-      final PatchLink reviewedLink =
+      reviewedLink =
           fileList.createLink(nextUnreviewedPatchIndex, getPatchScreenType(),
               null, null);
       reviewedLink.setText("");
-      reviewedAnchor.addClickHandler(new ClickHandler() {
-        @Override
-        public void onClick(ClickEvent event) {
-          reviewedLink.go();
-        }
-      });
     } else {
-      final ChangeLink upLink = new ChangeLink("", patchKey.getParentKey());
-      reviewedAnchor.addClickHandler(new ClickHandler() {
-        @Override
-        public void onClick(ClickEvent event) {
-          upLink.go();
-        }
-      });
+      reviewedLink = new ChangeLink("", patchKey.getParentKey());
     }
+    reviewedAnchor.addClickHandler(new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        setReviewedByCurrentUser(true);
+        reviewedLink.go();
+      }
+    });
 
     return reviewedAnchor;
   }
@@ -310,6 +301,14 @@
     keysNavigation.add(new UpToChangeCommand(patchKey.getParentKey(), 0, 'u'));
     keysNavigation.add(new FileListCmd(0, 'f', PatchUtil.C.fileList()));
 
+    if (Gerrit.isSignedIn()) {
+      keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
+      keysAction
+          .add(new ToggleReviewedCmd(0, 'm', PatchUtil.C.toggleReviewed()));
+      keysAction.add(new MarkAsReviewedAndGoToNextCmd(0, 'M', PatchUtil.C
+          .markAsReviewedAndGoToNext()));
+    }
+
     historyTable = new HistoryTable(this);
 
     commitMessageBlock = new CommitMessageBlock();
@@ -393,6 +392,10 @@
       regNavigation.removeHandler();
       regNavigation = null;
     }
+    if (regAction != null) {
+      regAction.removeHandler();
+      regAction = null;
+    }
     super.onUnload();
   }
 
@@ -405,6 +408,13 @@
       regNavigation = null;
     }
     regNavigation = GlobalKey.add(this, keysNavigation);
+    if (regAction != null) {
+      regAction.removeHandler();
+      regAction = null;
+    }
+    if (keysAction != null) {
+      regAction = GlobalKey.add(this, keysAction);
+    }
   }
 
   protected abstract AbstractPatchContentTable createContentTable();
@@ -603,4 +613,31 @@
       p.open();
     }
   }
+
+  public class ToggleReviewedCmd extends KeyCommand {
+    public ToggleReviewedCmd(int mask, int key, String help) {
+      super(mask, key, help);
+    }
+
+    @Override
+    public void onKeyPress(final KeyPressEvent event) {
+      final boolean isReviewed = !reviewedCheckBox.getValue();
+      reviewedCheckBox.setValue(isReviewed);
+      setReviewedByCurrentUser(isReviewed);
+    }
+  }
+
+  public class MarkAsReviewedAndGoToNextCmd extends KeyCommand {
+    public MarkAsReviewedAndGoToNextCmd(int mask, int key, String help) {
+      super(mask, key, help);
+    }
+
+    @Override
+    public void onKeyPress(final KeyPressEvent event) {
+      if (reviewedLink != null) {
+        setReviewedByCurrentUser(true);
+        reviewedLink.go();
+      }
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
index 871eb2b..a689259 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.java
@@ -76,6 +76,9 @@
   CheckBox whitespaceErrors;
 
   @UiField
+  CheckBox showLineEndings;
+
+  @UiField
   CheckBox showTabs;
 
   @UiField
@@ -210,6 +213,7 @@
     colWidth.setIntValue(dp.getLineLength());
     intralineDifference.setValue(dp.isIntralineDifference());
     whitespaceErrors.setValue(dp.isShowWhitespaceErrors());
+    showLineEndings.setValue(dp.isShowLineEndings());
     showTabs.setValue(dp.isShowTabs());
     skipDeleted.setValue(dp.isSkipDeleted());
     skipUncommented.setValue(dp.isSkipUncommented());
@@ -242,6 +246,7 @@
     dp.setSyntaxHighlighting(syntaxHighlighting.getValue());
     dp.setIntralineDifference(intralineDifference.getValue());
     dp.setShowWhitespaceErrors(whitespaceErrors.getValue());
+    dp.setShowLineEndings(showLineEndings.getValue());
     dp.setShowTabs(showTabs.getValue());
     dp.setSkipDeleted(skipDeleted.getValue());
     dp.setSkipUncommented(skipUncommented.getValue());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml
index 2c7afff..586b767 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScriptSettingsPanel.ui.xml
@@ -108,8 +108,8 @@
       </g:CheckBox>
       <br/>
       <g:CheckBox
-          ui:field='showTabs'
-          text='Show Tabs'
+          ui:field='showLineEndings'
+          text='Show Line Endings'
           tabIndex='8'>
         <ui:attribute name='text'/>
       </g:CheckBox>
@@ -117,15 +117,15 @@
 
     <td rowspan='2'>
       <g:CheckBox
-          ui:field='expandAllComments'
-          text='Expand All Comments'
+          ui:field='showTabs'
+          text='Show Tabs'
           tabIndex='9'>
         <ui:attribute name='text'/>
       </g:CheckBox>
       <br/>
       <g:CheckBox
-          ui:field='retainHeader'
-          text='Retain Header On File Switch'
+          ui:field='expandAllComments'
+          text='Expand All Comments'
           tabIndex='10'>
         <ui:attribute name='text'/>
       </g:CheckBox>
@@ -133,15 +133,15 @@
 
     <td rowspan='2'>
       <g:CheckBox
-          ui:field='skipUncommented'
-          text='Skip Uncommented Files'
+          ui:field='retainHeader'
+          text='Retain Header On File Switch'
           tabIndex='11'>
         <ui:attribute name='text'/>
       </g:CheckBox>
       <br/>
       <g:CheckBox
-          ui:field='skipDeleted'
-          text='Skip Deleted Files'
+          ui:field='skipUncommented'
+          text='Skip Uncommented Files'
           tabIndex='12'>
         <ui:attribute name='text'/>
       </g:CheckBox>
@@ -149,9 +149,16 @@
 
     <td valign='bottom' rowspan='2'>
       <g:CheckBox
+          ui:field='skipDeleted'
+          text='Skip Deleted Files'
+          tabIndex='13'>
+        <ui:attribute name='text'/>
+      </g:CheckBox>
+      <br/>
+      <g:CheckBox
           ui:field='manualReview'
           text='Manual Review'
-          tabIndex='13'>
+          tabIndex='14'>
         <ui:attribute name='text'/>
       </g:CheckBox>
     </td>
@@ -162,14 +169,14 @@
           ui:field='update'
           text='Update'
           styleName='{style.updateButton}'
-          tabIndex='14'>
+          tabIndex='15'>
         <ui:attribute name='text'/>
       </g:Button>
       <g:Button
           ui:field='save'
           text='Save'
           styleName='{style.updateButton}'
-          tabIndex='15'>
+          tabIndex='16'>
         <ui:attribute name='text'/>
       </g:Button>
     </td>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java
index 5dd4e1f..5d4207f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.java
@@ -21,16 +21,18 @@
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Style.Display;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Image;
 import com.google.gwtorm.client.KeyUtil;
 
 import java.util.LinkedList;
@@ -46,6 +48,8 @@
     String selected();
 
     String hidden();
+
+    String downloadLink();
   }
 
   public enum Side {
@@ -68,7 +72,7 @@
   BoxStyle style;
 
   @UiField
-  SpanElement sideMarker;
+  DivElement sideMarker;
 
   public PatchSetSelectBox(Side side, final PatchScreen.Type type) {
     this.side = side;
@@ -86,14 +90,24 @@
     this.idActive = (side == Side.A) ? idSideA : idSideB;
     this.links = new LinkedList<Anchor>();
 
+    linkPanel.clear();
+
     if (screenType == PatchScreen.Type.UNIFIED) {
       sideMarker.setInnerText((side == Side.A) ? "(-)" : "(+)");
+    } else {
+      sideMarker.getStyle().setDisplay(Display.NONE);
     }
 
+    Anchor baseLink = null;
     if (detail.getInfo().getParents().size() > 1) {
-      addLink(PatchUtil.C.patchBaseAutoMerge(), null);
+      baseLink = createLink(PatchUtil.C.patchBaseAutoMerge(), null);
     } else {
-      addLink(PatchUtil.C.patchBase(), null);
+      baseLink = createLink(PatchUtil.C.patchBase(), null);
+    }
+
+    links.add(baseLink);
+    if (screenType == PatchScreen.Type.UNIFIED || side == Side.A) {
+      linkPanel.add(baseLink);
     }
 
     if (side == Side.B) {
@@ -102,7 +116,9 @@
 
     for (Patch patch : script.getHistory()) {
       PatchSet.Id psId = patch.getKey().getParentKey();
-      addLink(Integer.toString(psId.get()), psId);
+      Anchor anchor = createLink(Integer.toString(psId.get()), psId);
+      links.add(anchor);
+      linkPanel.add(anchor);
     }
 
     if (idActive == null && side == Side.A) {
@@ -111,14 +127,13 @@
       links.get(idActive.get()).setStyleName(style.selected());
     }
 
-    Anchor downloadLink = getDownloadLink();
+    Anchor downloadLink = createDownloadLink();
     if (downloadLink != null) {
-      linkPanel.add(new Label(" - "));
       linkPanel.add(downloadLink);
     }
   }
 
-  private void addLink(String label, final PatchSet.Id id) {
+  private Anchor createLink(String label, final PatchSet.Id id) {
     final Anchor anchor = new Anchor(label);
     anchor.addClickHandler(new ClickHandler() {
       @Override
@@ -143,11 +158,10 @@
 
     });
 
-    links.add(anchor);
-    linkPanel.add(anchor);
+    return anchor;
   }
 
-  private Anchor getDownloadLink() {
+  private Anchor createDownloadLink() {
     boolean isCommitMessage = Patch.COMMIT_MSG.equals(script.getNewName());
 
     if (isCommitMessage || (side == Side.A && 0 >= script.getA().size())
@@ -161,8 +175,14 @@
     String sideURL = (side == Side.A) ? "1" : "0";
     final String base = GWT.getHostPageBaseURL() + "cat/";
 
-    final Anchor anchor = new Anchor(PatchUtil.C.download());
+    Image image = new Image(Gerrit.RESOURCES.downloadIcon());
+
+    final Anchor anchor = new Anchor();
     anchor.setHref(base + KeyUtil.encode(key.toString()) + "^" + sideURL);
+    anchor.setTitle(PatchUtil.C.download());
+    anchor.setStyleName(style.downloadLink());
+    DOM.insertBefore(anchor.getElement(), image.getElement(),
+        DOM.getFirstChild(anchor.getElement()));
 
     return anchor;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.ui.xml
index 2c4bd5d..2fd183c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchSetSelectBox.ui.xml
@@ -28,18 +28,21 @@
 
     .wrapper {
       width: 100%;
+      text-align: center;
+      font-size: 0; /* inline-block spacing fix */
     }
 
-    .patchSetLabel {
-      font-weight: bold;
+    .linkPanel {
+      display: inline-block;
     }
 
     .linkPanel > div {
       display: inline-block;
-      padding: 3px;
+      float: left;
     }
 
     .linkPanel {
+      overflow: hidden; /* div clear fix */
       font-size: 12px;
     }
 
@@ -47,6 +50,27 @@
       padding: 3px;
       display: inline-block;
       text-decoration: none;
+      float: left;
+    }
+
+    .patchSetLabel {
+      font-weight: bold;
+      float: left;
+      padding: 3px;
+    }
+
+    .sideMarker {
+      padding: 3px;
+    }
+
+    .downloadLink {
+      float: left;
+      padding: 1px !important;
+      margin-left: 3px;
+    }
+
+    .downloadLink > a {
+      text-size: 0;
     }
 
     .selected {
@@ -56,6 +80,7 @@
 
     .sideMarker {
       font-family: monospace;
+      float: left;
     }
 
     .hidden {
@@ -64,7 +89,10 @@
   </ui:style>
 
   <g:HTMLPanel styleName='wrapper'>
-    <g:HTMLPanel styleName='{style.linkPanel}' ui:field='linkPanel'><span class='{style.patchSetLabel}'><ui:text from="{cons.patchSet}" /></span> <span class='{style.sideMarker}' ui:field='sideMarker'></span>: </g:HTMLPanel>
+    <g:HTMLPanel styleName='{style.linkPanel}' ui:field='linkPanel'>
+      <div class='{style.patchSetLabel}'><ui:text from="{cons.patchSet}" /></div>
+      <div class='{style.sideMarker}' ui:field='sideMarker'></div>
+    </g:HTMLPanel>
   </g:HTMLPanel>
 </ui:UiBinder>
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderSideBySide.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderSideBySide.ui.xml
index 424e6e5..d6fd717 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderSideBySide.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderSideBySide.ui.xml
@@ -26,6 +26,7 @@
       width: 100%;
       background-color: trimColor;
       overflow: hidden;
+      font-size: 0; /* inline-block spacing fix */
     }
 
     .wrapper .box {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderUnified.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderUnified.ui.xml
index e26e96a..24acfa3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderUnified.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTableHeaderUnified.ui.xml
@@ -24,6 +24,7 @@
     .wrapper {
       width: 100%;
       background-color: trimColor;
+      font-size: 0; /* inline-block spacing fix */
     }
 
     .wrapper .box {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
index e1fb883..4e9488c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/RestApi.java
@@ -80,6 +80,10 @@
     return addParameterRaw(name, String.valueOf(value));
   }
 
+  public RestApi addParameter(String name, Enum<?> value) {
+    return addParameterRaw(name, value.name());
+  }
+
   public RestApi addParameterRaw(String name, String value) {
     if (hasQueryParams) {
       url.append("&");
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
index 13a6f43..e9b3500 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HttpLogoutServlet.java
@@ -14,7 +14,10 @@
 
 package com.google.gerrit.httpd;
 
+import com.google.gerrit.audit.AuditEvent;
+import com.google.gerrit.audit.AuditService;
 import com.google.common.base.Strings;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -36,19 +39,21 @@
   private final Provider<WebSession> webSession;
   private final Provider<String> urlProvider;
   private final String logoutUrl;
+  private final AuditService audit;
 
   @Inject
   HttpLogoutServlet(final AuthConfig authConfig,
       final Provider<WebSession> webSession,
       @CanonicalWebUrl @Nullable final Provider<String> urlProvider,
-      final AccountManager accountManager) {
+      final AccountManager accountManager,
+      final AuditService audit) {
     this.webSession = webSession;
     this.urlProvider = urlProvider;
     this.logoutUrl = authConfig.getLogoutURL();
+    this.audit = audit;
   }
 
-  @Override
-  protected void doGet(final HttpServletRequest req,
+  private void doLogout(final HttpServletRequest req,
       final HttpServletResponse rsp) throws IOException {
     webSession.get().logout();
     if (logoutUrl != null) {
@@ -67,4 +72,22 @@
       rsp.sendRedirect(url);
     }
   }
+
+  @Override
+  protected void doGet(final HttpServletRequest req,
+      final HttpServletResponse rsp) throws IOException {
+
+    final String sid = webSession.get().getToken();
+    final CurrentUser currentUser = webSession.get().getCurrentUser();
+    final String what = "sign out";
+    final long when = System.currentTimeMillis();
+
+    try {
+      doLogout(req, rsp);
+    } finally {
+      audit.dispatch(new AuditEvent(sid, currentUser,
+          what, when, null, null));
+    }
+  }
+
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java
index be8ae32..a4217cf 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RestApiServlet.java
@@ -102,10 +102,13 @@
       CapabilityControl ctl = user.getCapabilities();
       if (!ctl.canPerform(rc.value()) && !ctl.canAdministrateServer()) {
         String msg = String.format(
-            "fatal: %s does not have \"%s\" capability.",
-            Objects.firstNonNull(user.getUserName(),
-                ((IdentifiedUser)user).getNameEmail()),
-            rc.value());
+          "fatal: %s does not have \"%s\" capability.",
+          Objects.firstNonNull(
+            user.getUserName(),
+            user instanceof IdentifiedUser
+              ? ((IdentifiedUser) user).getNameEmail()
+              : user.toString()),
+          rc.value());
         throw new RequireCapabilityException(msg);
       }
     }
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 9e69946..a7fde9b 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
@@ -16,6 +16,7 @@
 
 import static com.google.inject.Scopes.SINGLETON;
 
+import com.google.common.base.Strings;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.httpd.raw.CatServlet;
 import com.google.gerrit.httpd.raw.HostPageServlet;
@@ -164,6 +165,11 @@
       protected void doGet(HttpServletRequest req, HttpServletResponse rsp)
           throws IOException {
         String name = req.getPathInfo();
+        if (Strings.isNullOrEmpty(name)) {
+          toGerrit(PageLinks.ADMIN_PROJECTS, req, rsp);
+          return;
+        }
+
         while (name.endsWith("/")) {
           name = name.substring(0, name.length() - 1);
         }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
index bb47b8b..2bcaa30 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java
@@ -28,6 +28,7 @@
   protected void configureServlets() {
     bind(HttpPluginServlet.class);
     serve("/plugins/*").with(HttpPluginServlet.class);
+    serveRegex("^/(?:a/)?plugins/(.*)?$").with(HttpPluginServlet.class);
 
     bind(StartPluginListener.class)
       .annotatedWith(UniqueAnnotations.create())
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ThemeFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ThemeFactory.java
index a2f4c99..9723674 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ThemeFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ThemeFactory.java
@@ -43,7 +43,7 @@
     theme.trimColor = color(name, "trimColor", "#D4E9A9");
     theme.selectionColor = color(name, "selectionColor", "#FFFFCC");
     theme.topMenuColor = color(name, "topMenuColor", theme.trimColor);
-    theme.changeTableOutdatedColor = color(name, "changeTableOutdatedColor", "#FF0000");
+    theme.changeTableOutdatedColor = color(name, "changeTableOutdatedColor", "#F08080");
     theme.tableOddRowColor = color(name, "tableOddRowColor", "transparent");
     theme.tableEvenRowColor = color(name, "tableEvenRowColor", "transparent");
     return theme;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
index 3513f89..8f7394d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
@@ -14,43 +14,71 @@
 
 package com.google.gerrit.httpd.rpc;
 
+import com.google.common.collect.Lists;
+import com.google.gerrit.audit.AuditEvent;
+import com.google.gerrit.audit.AuditService;
+import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.common.errors.NotSignedInException;
 import com.google.gerrit.httpd.WebSession;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gson.GsonBuilder;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
 import com.google.gwtjsonrpc.server.ActiveCall;
 import com.google.gwtjsonrpc.server.JsonServlet;
+import com.google.gwtjsonrpc.server.MethodHandle;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-
 /**
  * Base JSON servlet to ensure the current user is not forged.
  */
 @SuppressWarnings("serial")
 final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall> {
+  private static final Logger log = LoggerFactory.getLogger(GerritJsonServlet.class);
+  private static final ThreadLocal<GerritCall> currentCall =
+      new ThreadLocal<GerritCall>();
+  private static final ThreadLocal<MethodHandle> currentMethod =
+      new ThreadLocal<MethodHandle>();
   private final Provider<WebSession> session;
   private final RemoteJsonService service;
+  private final AuditService audit;
+
 
   @Inject
-  GerritJsonServlet(final Provider<WebSession> w, final RemoteJsonService s) {
+  GerritJsonServlet(final Provider<WebSession> w, final RemoteJsonService s,
+      final AuditService a) {
     session = w;
     service = s;
+    audit = a;
   }
 
   @Override
   protected GerritCall createActiveCall(final HttpServletRequest req,
       final HttpServletResponse rsp) {
-    return new GerritCall(session.get(), req, rsp);
+    final GerritCall call = new GerritCall(session.get(), req, rsp);
+    currentCall.set(call);
+    return call;
   }
 
   @Override
   protected GsonBuilder createGsonBuilder() {
-    final GsonBuilder g = super.createGsonBuilder();
+    return gerritDefaultGsonBuilder();
+  }
+
+  private static GsonBuilder gerritDefaultGsonBuilder() {
+    final GsonBuilder g = defaultGsonBuilder();
 
     g.registerTypeAdapter(org.eclipse.jgit.diff.Edit.class,
         new org.eclipse.jgit.diff.EditDeserializer());
@@ -83,13 +111,113 @@
     return service;
   }
 
+  @Override
+  protected void service(final HttpServletRequest req,
+      final HttpServletResponse resp) throws IOException {
+    try {
+      super.service(req, resp);
+    } finally {
+      audit();
+      currentCall.set(null);
+    }
+  }
+
+  private void audit() {
+    try {
+      GerritCall call = currentCall.get();
+      Audit note = (Audit) call.getMethod().getAnnotation(Audit.class);
+      if (note != null) {
+        final String sid = call.getWebSession().getToken();
+        final CurrentUser username = call.getWebSession().getCurrentUser();
+        final List<Object> args =
+            extractParams(note, call);
+        final String what = extractWhat(note, call.getMethod().getName());
+        final Object result = call.getResult();
+
+        audit.dispatch(new AuditEvent(sid, username, what, call.getWhen(), args,
+            result));
+      }
+    } catch (Throwable all) {
+      log.error("Unable to log the call", all);
+    }
+  }
+
+  private List<Object> extractParams(final Audit note, final GerritCall call) {
+    List<Object> args = Lists.newArrayList(Arrays.asList(call.getParams()));
+    for (int idx : note.obfuscate()) {
+      args.set(idx, "*****");
+    }
+    return args;
+  }
+
+  private String extractWhat(final Audit note, final String methodName) {
+    String what = note.action();
+    if (what.length() == 0) {
+      boolean ccase = Character.isLowerCase(methodName.charAt(0));
+
+      StringBuilder sb = new StringBuilder();
+      for (int i = 0; i < methodName.length(); i++) {
+        char c = methodName.charAt(i);
+        if (ccase && !Character.isLowerCase(c)) {
+          sb.append(' ');
+        }
+        sb.append(Character.toLowerCase(c));
+      }
+      what = sb.toString();
+    }
+
+    return what;
+  }
+
   static class GerritCall extends ActiveCall {
     private final WebSession session;
+    private final long when;
+    private static final Field resultField;
+
+    // Needed to allow access to non-public result field in GWT/JSON-RPC
+    static {
+      Field declaredField = null;
+      try {
+        declaredField = ActiveCall.class.getDeclaredField("result");
+        declaredField.setAccessible(true);
+      } catch (Exception e) {
+        log.error("Unable to expose RPS/JSON result field");
+      }
+
+      resultField = declaredField;
+    }
+
+    // Surrogate of the missing getResult() in GWT/JSON-RPC
+    public Object getResult() {
+      if (resultField == null) {
+        return null;
+      }
+
+      try {
+        return resultField.get(this);
+      } catch (IllegalArgumentException e) {
+        log.error("Cannot access result field");
+      } catch (IllegalAccessException e) {
+        log.error("No permissions to access result field");
+      }
+
+      return null;
+    }
 
     GerritCall(final WebSession session, final HttpServletRequest i,
         final HttpServletResponse o) {
       super(i, o);
       this.session = session;
+      this.when = System.currentTimeMillis();
+    }
+
+    @Override
+    public MethodHandle getMethod() {
+      if (currentMethod.get() == null) {
+        return super.getMethod();
+      } else {
+        return currentMethod.get();
+      }
     }
 
     @Override
@@ -120,5 +248,18 @@
         return session.isSignedIn() && session.isTokenValid(keyIn);
       }
     }
+
+    public WebSession getWebSession() {
+      return session;
+    }
+
+    public long getWhen() {
+      return when;
+    }
+
+    public long getElapsed() {
+      return System.currentTimeMillis() - when;
+    }
   }
+
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
index 8cdccac..b62a10b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -17,7 +17,6 @@
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.data.AccountSecurity;
 import com.google.gerrit.common.data.ContributorAgreement;
-import com.google.gerrit.common.data.GroupDetail;
 import com.google.gerrit.common.errors.ContactInformationStoreException;
 import com.google.gerrit.common.errors.InvalidSshKeyException;
 import com.google.gerrit.common.errors.NameAlreadyUsedException;
@@ -213,9 +212,9 @@
   }
 
   @Override
-  public void myGroups(final AsyncCallback<List<GroupDetail>> callback) {
-    run(callback, new Action<List<GroupDetail>>() {
-      public List<GroupDetail> run(final ReviewDb db) throws OrmException,
+  public void myGroups(final AsyncCallback<List<AccountGroup>> callback) {
+    run(callback, new Action<List<AccountGroup>>() {
+      public List<AccountGroup> run(final ReviewDb db) throws OrmException,
           NoSuchGroupException, Failure {
         return myGroupsFactory.create().call();
       }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
index fd274fd..33ce371 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.httpd.rpc.account;
 
-import com.google.gerrit.common.data.GroupDetail;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.VisibleGroups;
 import com.google.gwtorm.server.OrmException;
@@ -24,7 +24,7 @@
 
 import java.util.List;
 
-class MyGroupsFactory extends Handler<List<GroupDetail>> {
+class MyGroupsFactory extends Handler<List<AccountGroup>> {
   interface Factory {
     MyGroupsFactory create();
   }
@@ -39,7 +39,7 @@
   }
 
   @Override
-  public List<GroupDetail> call() throws OrmException, NoSuchGroupException {
+  public List<AccountGroup> call() throws OrmException, NoSuchGroupException {
     final VisibleGroups visibleGroups = visibleGroupsFactory.create();
     return visibleGroups.get(user).getGroups();
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index 250f5e3..7b3b8e7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -125,10 +125,13 @@
 
       } else if (RefConfigSection.isValid(name)) {
         RefControl rc = pc.controlForRef(name);
-        if (rc.isOwner() || metaConfigControl.isVisible()) {
+        if (rc.isOwner()) {
           local.add(section);
           ownerOf.add(name);
 
+        } else if (metaConfigControl.isVisible()) {
+          local.add(section);
+
         } else if (rc.isVisible()) {
           // Filter the section to only add rules describing groups that
           // are visible to the current-user. This includes any group the
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
index 15f167c..983f1fc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
@@ -89,25 +89,25 @@
     changeProjectSettingsFactory.create(update).to(callback);
   }
 
+  private static ObjectId getBase(final String baseRevision) {
+    if (baseRevision != null && !baseRevision.isEmpty()) {
+      return ObjectId.fromString(baseRevision);
+    }
+    return null;
+  }
+
   @Override
   public void changeProjectAccess(Project.NameKey projectName,
       String baseRevision, String msg, List<AccessSection> sections,
       AsyncCallback<ProjectAccess> cb) {
-    ObjectId base;
-    if (baseRevision != null && !baseRevision.isEmpty()) {
-      base = ObjectId.fromString(baseRevision);
-    } else {
-      base = null;
-    }
-    changeProjectAccessFactory.create(projectName, base, sections, msg).to(cb);
+    changeProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb);
   }
 
   @Override
   public void reviewProjectAccess(Project.NameKey projectName,
       String baseRevision, String msg, List<AccessSection> sections,
       AsyncCallback<Change.Id> cb) {
-    ObjectId base = ObjectId.fromString(baseRevision);
-    reviewProjectAccessFactory.create(projectName, base, sections, msg).to(cb);
+    reviewProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb);
   }
 
   @Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index c422f6d..69a283a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetAncestor;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RevId;
@@ -31,17 +32,16 @@
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.patch.AddReviewer;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
-import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -81,36 +81,56 @@
 
   @Override
   protected Change.Id updateProjectConfig(ProjectConfig config, MetaDataUpdate md)
-      throws IOException, NoSuchProjectException, ConfigInvalidException, OrmException {
-    int nextChangeId = db.nextChangeId();
-    PatchSet.Id patchSetId = new PatchSet.Id(new Change.Id(nextChangeId), 1);
-    final PatchSet ps = new PatchSet(patchSetId);
+      throws IOException, OrmException {
+    Change.Id changeId = new Change.Id(db.nextChangeId());
+    PatchSet ps = new PatchSet(new PatchSet.Id(changeId, 1));
     RevCommit commit = config.commitToNewRef(md, ps.getRefName());
     if (commit.getId().equals(base)) {
       return null;
     }
-    Change.Key changeKey = new Change.Key("I" + commit.name());
-    final Change change =
-        new Change(changeKey, new Change.Id(nextChangeId), user.getAccountId(),
-            new Branch.NameKey(config.getProject().getNameKey(),
-                GitRepositoryManager.REF_CONFIG));
+
+    Change change = new Change(
+        new Change.Key("I" + commit.name()),
+        changeId,
+        user.getAccountId(),
+        new Branch.NameKey(
+            config.getProject().getNameKey(),
+            GitRepositoryManager.REF_CONFIG));
     change.nextPatchSetId();
 
     ps.setCreatedOn(change.getCreatedOn());
-    ps.setUploader(user.getAccountId());
+    ps.setUploader(change.getOwner());
     ps.setRevision(new RevId(commit.name()));
 
-    db.patchSets().insert(Collections.singleton(ps));
-
-    final PatchSetInfo info = patchSetInfoFactory.get(commit, ps.getId());
+    PatchSetInfo info = patchSetInfoFactory.get(commit, ps.getId());
     change.setCurrentPatchSet(info);
     ChangeUtil.updated(change);
 
-    db.changes().insert(Collections.singleton(change));
+    db.changes().beginTransaction(changeId);
+    try {
+      insertAncestors(ps.getId(), commit);
+      db.patchSets().insert(Collections.singleton(ps));
+      db.changes().insert(Collections.singleton(change));
+      addProjectOwnersAsReviewers(changeId);
+      db.commit();
+    } finally {
+      db.rollback();
+    }
+    return changeId;
+  }
 
-    addProjectOwnersAsReviewers(change.getId());
+  private void insertAncestors(PatchSet.Id id, RevCommit src)
+      throws OrmException {
+    final int cnt = src.getParentCount();
+    List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
+    for (int p = 0; p < cnt; p++) {
+      PatchSetAncestor a;
 
-    return change.getId();
+      a = new PatchSetAncestor(new PatchSetAncestor.Id(id, p + 1));
+      a.setAncestorRevision(new RevId(src.getParent(p).name()));
+      toInsert.add(a);
+    }
+    db.patchSetAncestors().insert(toInsert);
   }
 
   private void addProjectOwnersAsReviewers(final Change.Id changeId) {
diff --git a/gerrit-package-plugins/.gitignore b/gerrit-package-plugins/.gitignore
new file mode 100644
index 0000000..c96b05c
--- /dev/null
+++ b/gerrit-package-plugins/.gitignore
@@ -0,0 +1,6 @@
+/target
+/.classpath
+/.project
+/.settings/org.maven.ide.eclipse.prefs
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-package-plugins.iml
diff --git a/gerrit-package-plugins/pom.xml b/gerrit-package-plugins/pom.xml
new file mode 100644
index 0000000..c072719
--- /dev/null
+++ b/gerrit-package-plugins/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>com.google.gerrit</groupId>
+  <artifactId>gerrit-package-plugins</artifactId>
+  <packaging>war</packaging>
+  <version>2.5-SNAPSHOT</version>
+
+  <name>Gerrit Code Review - Package Plugins</name>
+  <url>http://code.google.com/p/gerrit/</url>
+
+  <properties>
+    <project.build.sourceEncoding>
+      UTF-8
+    </project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.gerrit</groupId>
+      <artifactId>gerrit-war</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+    </dependency>
+    <dependency>
+      <groupId>com.googlesource.gerrit.plugins.replication</groupId>
+      <artifactId>replication</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <version>2.1</version>
+        <executions>
+          <execution>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <includeTypes>jar</includeTypes>
+              <stripVersion>true</stripVersion>
+              <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/plugins</outputDirectory>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <version>2.1.1</version>
+        <configuration>
+          <warName>gerrit-full-${project.version}</warName>
+          <archive>
+            <addMavenDescriptor>false</addMavenDescriptor>
+            <manifestEntries>
+              <Main-Class>Main</Main-Class>
+              <Implementation-Title>Gerrit Code Review</Implementation-Title>
+              <Implementation-Version>${project.version}</Implementation-Version>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
index a55cea5..8b3d87e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java
@@ -43,6 +43,7 @@
     step().to(InitSshd.class);
     step().to(InitHttpd.class);
     step().to(InitCache.class);
+    step().to(InitPlugins.class);
   }
 
   protected LinkedBindingBuilder<InitStep> step() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
new file mode 100644
index 0000000..155fe4c
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
@@ -0,0 +1,140 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import com.google.gerrit.launcher.GerritLauncher;
+import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.plugins.PluginLoader;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+@Singleton
+public class InitPlugins implements InitStep {
+  private final static String PLUGIN_DIR = "WEB-INF/plugins/";
+  private final static String JAR = ".jar";
+
+  private final ConsoleUI ui;
+  private final SitePaths site;
+
+  @Inject
+  InitPlugins(final ConsoleUI ui, final SitePaths site) {
+    this.ui = ui;
+    this.site = site;
+  }
+
+  @Override
+  public void run() throws Exception {
+    ui.header("Plugins");
+
+    final File myWar;
+    try {
+      myWar = GerritLauncher.getDistributionArchive();
+    } catch (FileNotFoundException e) {
+      System.err.println("warn: Cannot find gerrit.war");
+      return;
+    }
+
+    boolean foundPlugin = false;
+    try {
+      final ZipFile zf = new ZipFile(myWar);
+      try {
+        final Enumeration<? extends ZipEntry> e = zf.entries();
+        while (e.hasMoreElements()) {
+          final ZipEntry ze = e.nextElement();
+          if (ze.isDirectory()) {
+            continue;
+          }
+
+          if (ze.getName().startsWith(PLUGIN_DIR) && ze.getName().endsWith(JAR)) {
+            if (!foundPlugin) {
+              if (!ui.yesno(false, "Prompt to install core plugins")) {
+                return;
+              }
+              foundPlugin = true;
+            }
+
+            final String pluginJarName = new File(ze.getName()).getName();
+            final String pluginName = pluginJarName.substring(0,  pluginJarName.length() - JAR.length());
+
+            final InputStream in = zf.getInputStream(ze);
+            try {
+              final File tmpPlugin = PluginLoader.storeInTemp(pluginName, in, site);
+              final String pluginVersion = getVersion(tmpPlugin);
+
+              if (!ui.yesno(false, "Install plugin %s version %s", pluginName,
+                  pluginVersion)) {
+                tmpPlugin.delete();
+                continue;
+              }
+
+              final File plugin = new File(site.plugins_dir, pluginJarName);
+              if (plugin.exists()) {
+                final String installedPluginVersion = getVersion(plugin);
+                if (!ui.yesno(false,
+                    "version %s is already installed, overwrite it",
+                    installedPluginVersion)) {
+                  tmpPlugin.delete();
+                  continue;
+                }
+                if (!plugin.delete()) {
+                  throw new IOException("Failed to delete plugin " + pluginName
+                      + ": " + plugin.getAbsolutePath());
+                }
+              }
+              if (!tmpPlugin.renameTo(plugin)) {
+                throw new IOException("Failed to install plugin " + pluginName
+                    + ": " + tmpPlugin.getAbsolutePath() + " -> "
+                    + plugin.getAbsolutePath());
+              }
+            } finally {
+              in.close();
+            }
+          }
+        }
+      } finally {
+        zf.close();
+      }
+    } catch (IOException e) {
+      throw new IOException("Failure during plugin installation", e);
+    }
+
+    if (!foundPlugin) {
+      ui.message("No plugins found.");
+    }
+  }
+
+  private static String getVersion(final File plugin) throws IOException {
+    final JarFile jarFile = new JarFile(plugin);
+    try {
+      final Manifest manifest = jarFile.getManifest();
+      final Attributes main = manifest.getMainAttributes();
+      return main.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
+    } finally {
+      jarFile.close();
+    }
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
index 1e93651..b4e0fad 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ConsoleUI.java
@@ -61,6 +61,9 @@
   /** Display a header message before a series of prompts. */
   public abstract void header(String fmt, Object... args);
 
+  /** Display a message. */
+  public abstract void message(String fmt, Object... args);
+
   /** Request the user to answer a yes/no question. */
   public abstract boolean yesno(Boolean def, String fmt, Object... args);
 
@@ -215,6 +218,11 @@
       fmt = fmt.replaceAll("\n", "\n*** ");
       console.printf("\n*** " + fmt + "\n*** \n\n", args);
     }
+
+    @Override
+    public void message(String fmt, Object... args) {
+      console.printf(fmt, args);
+    }
   }
 
   private static class Batch extends ConsoleUI {
@@ -250,5 +258,9 @@
     @Override
     public void header(String fmt, Object... args) {
     }
+
+    @Override
+    public void message(String fmt, Object... args) {
+    }
   }
 }
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettifyConstants.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettifyConstants.java
index e14063a..df60305 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettifyConstants.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettifyConstants.java
@@ -23,4 +23,5 @@
   String wseTabAfterSpace();
   String wseTrailingSpace();
   String wseBareCR();
+  String leCR();
 }
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettifyConstants.properties b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettifyConstants.properties
index d440c65..97ab0cf 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettifyConstants.properties
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettifyConstants.properties
@@ -1,3 +1,4 @@
 wseTabAfterSpace=Whitespace error: Tab after space
 wseTrailingSpace=Whitespace error: Trailing space at end of line
-wseBareCR=Whitespace error: CR without LF
+wseBareCR=CR without LF
+leCR=Carriage Return
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java
index c9a9ab8..151149b 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java
@@ -335,6 +335,10 @@
       html = showTrailingWhitespace(html);
     }
 
+    if (diffPrefs.isShowLineEndings()){
+      html = showLineEndings(html);
+    }
+
     if (diffPrefs.isShowTabs()) {
       String t = 1 < diffPrefs.getTabSize() ? "\t" : "";
       html = html.replaceAll("\t", "<span class=\"vt\">\u00BB</span>" + t);
@@ -449,12 +453,11 @@
 
       } else if (end) {
         if (cr == src.length() - 1) {
-          buf.append(src.substring(0, cr));
+          buf.append(src.substring(0, cr + 1));
           return;
         }
       } else if (cr == src.length() - 2 && src.charAt(cr + 1) == '\n') {
-        buf.append(src.substring(0, cr));
-        buf.append('\n');
+        buf.append(src);
         return;
       }
 
@@ -499,6 +502,14 @@
     return src;
   }
 
+  private SafeHtml showLineEndings(SafeHtml src) {
+    final String r = "<span class=\"lecr\""
+        + " title=\"" + PrettifyConstants.C.leCR() + "\"" //
+        + ">\\\\r</span>";
+    src = src.replaceAll("\r", r);
+    return src;
+  }
+
   private String expandTabs(String html) {
     StringBuilder tmp = new StringBuilder();
     int i = 0;
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/SparseFileContent.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/SparseFileContent.java
index a5373b8..1a5468c 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/SparseFileContent.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/SparseFileContent.java
@@ -249,7 +249,7 @@
     range.lines = lines;
 
     SparseFileContent r = new SparseFileContent();
-    r.setSize(size());
+    r.setSize(lines.size());
     r.setMissingNewlineAtEnd(isMissingNewlineAtEnd());
     r.setPath(getPath());
     r.ranges.add(range);
diff --git a/gerrit-prettify/src/main/resources/com/google/gerrit/prettify/client/gerrit.css b/gerrit-prettify/src/main/resources/com/google/gerrit/prettify/client/gerrit.css
index 3478e68..23e7e46 100644
--- a/gerrit-prettify/src/main/resources/com/google/gerrit/prettify/client/gerrit.css
+++ b/gerrit-prettify/src/main/resources/com/google/gerrit/prettify/client/gerrit.css
@@ -14,6 +14,7 @@
  */
 
 @external .wse;
+@external .lecr;
 @external .vt;
 @external .wdd;
 @external .wdi;
@@ -35,6 +36,19 @@
   cursor: pointer;
 }
 
+.lecr {
+  border-bottom: #aaaaaa 1px dashed;
+  border-left: #aaaaaa 1px dashed;
+  padding-bottom: 0px;
+  margin: 0px 2px;
+  padding-left: 2px;
+  padding-right: 2px;
+  border-top: #aaaaaa 1px dashed;
+  border-right: #aaaaaa 1px dashed;
+  padding-top: 0px;
+  cursor: pointer;
+}
+
 .vt,
 .vt .str,
 .vt .kwd,
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java
index 3b04725..afafd46 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java
@@ -62,6 +62,7 @@
     p.setLineLength(100);
     p.setSyntaxHighlighting(true);
     p.setShowWhitespaceErrors(true);
+    p.setShowLineEndings(true);
     p.setIntralineDifference(true);
     p.setShowTabs(true);
     p.setContext(DEFAULT_CONTEXT);
@@ -112,6 +113,9 @@
   @Column(id = 14)
   protected boolean manualReview;
 
+  @Column(id = 15)
+  protected boolean showLineEndings;
+
   protected AccountDiffPreference() {
   }
 
@@ -126,6 +130,7 @@
     this.lineLength = p.lineLength;
     this.syntaxHighlighting = p.syntaxHighlighting;
     this.showWhitespaceErrors = p.showWhitespaceErrors;
+    this.showLineEndings = p.showLineEndings;
     this.intralineDifference = p.intralineDifference;
     this.showTabs = p.showTabs;
     this.skipDeleted = p.skipDeleted;
@@ -180,6 +185,14 @@
     this.showWhitespaceErrors = showWhitespaceErrors;
   }
 
+  public boolean isShowLineEndings() {
+    return showLineEndings;
+  }
+
+  public void setShowLineEndings(boolean showLineEndings) {
+    this.showLineEndings = showLineEndings;
+  }
+
   public boolean isIntralineDifference() {
     return intralineDifference;
   }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index 61e6a97..bfbacc0 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -491,6 +491,10 @@
     --nbrPatchSets;
   }
 
+  public void updateNumberOfPatchSets(int max) {
+    nbrPatchSets = Math.max(nbrPatchSets, max);
+  }
+
   public PatchSet.Id currPatchSetId() {
     return new PatchSet.Id(changeId, nbrPatchSets);
   }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
index 0d0ea88..00e282b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
@@ -82,7 +82,10 @@
     RENAMED('R'),
 
     /** Path was copied from {@link Patch#getSourceFileName()}. */
-    COPIED('C');
+    COPIED('C'),
+
+    /** Sufficient amount of content changed to claim the file was written. */
+    REWRITE('W');
 
     private final char code;
 
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/TrackingId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/TrackingId.java
index e73dd73..c664285 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/TrackingId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/TrackingId.java
@@ -20,7 +20,7 @@
 
 /** External tracking id associated with a {@link Change} */
 public final class TrackingId {
-  public static final int TRACKING_ID_MAX_CHAR = 20;
+  public static final int TRACKING_ID_MAX_CHAR = 32;
   public static final int TRACKING_SYSTEM_MAX_CHAR = 10;
 
   /** External tracking id */
@@ -80,20 +80,20 @@
     protected Change.Id changeId;
 
     @Column(id = 2)
-    protected Id trackingId;
+    protected Id trackingKey;
 
     @Column(id = 3)
     protected System trackingSystem;
 
     protected Key() {
       changeId = new Change.Id();
-      trackingId = new Id();
+      trackingKey = new Id();
       trackingSystem = new System();
     }
 
     protected Key(final Change.Id ch, final Id id, final System s) {
       changeId = ch;
-      trackingId = id;
+      trackingKey = id;
       trackingSystem = s;
     }
 
@@ -103,7 +103,7 @@
     }
 
     public TrackingId.Id getTrackingId() {
-      return trackingId;
+      return trackingKey;
     }
 
     public TrackingId.System getTrackingSystem() {
@@ -112,7 +112,7 @@
 
     @Override
     public com.google.gwtorm.client.Key<?>[] members() {
-      return new com.google.gwtorm.client.Key<?>[] {trackingId, trackingSystem};
+      return new com.google.gwtorm.client.Key<?>[] {trackingKey, trackingSystem};
     }
   }
 
@@ -140,7 +140,7 @@
   }
 
   public String getTrackingId() {
-    return key.trackingId.get();
+    return key.trackingKey.get();
   }
 
   public String getSystem() {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/TrackingIdAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/TrackingIdAccess.java
index 92b9b10..d8b2cee 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/TrackingIdAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/TrackingIdAccess.java
@@ -29,7 +29,7 @@
   @Query("WHERE key.changeId = ?")
   ResultSet<TrackingId> byChange(Change.Id change) throws OrmException;
 
-  @Query("WHERE key.trackingId = ?")
+  @Query("WHERE key.trackingKey = ?")
   ResultSet<TrackingId> byTrackingId(TrackingId.Id trackingId)
       throws OrmException;
 }
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
index 1244695..4d1b78c 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
@@ -146,8 +146,8 @@
 -- *********************************************************************
 -- TrackingIdAccess
 --
-CREATE INDEX tracking_ids_byTrkId
-ON tracking_ids (tracking_id);
+CREATE INDEX tracking_ids_byTrkKey
+ON tracking_ids (tracking_key);
 
 
 -- *********************************************************************
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
index 26c6d19..6637666 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
@@ -229,8 +229,8 @@
 -- *********************************************************************
 -- TrackingIdAccess
 --
-CREATE INDEX tracking_ids_byTrkId
-ON tracking_ids (tracking_id);
+CREATE INDEX tracking_ids_byTrkKey
+ON tracking_ids (tracking_key);
 
 
 -- *********************************************************************
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java
new file mode 100644
index 0000000..364df80
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java
@@ -0,0 +1,172 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.audit;
+
+import com.google.common.base.Preconditions;
+import com.google.gerrit.server.CurrentUser;
+
+import java.util.Collections;
+import java.util.List;
+
+public class AuditEvent {
+
+  public static final String UNKNOWN_SESSION_ID = "000000000000000000000000000";
+  private static final Object UNKNOWN_RESULT = "N/A";
+
+  public final String sessionId;
+  public final CurrentUser who;
+  public final long when;
+  public final String what;
+  public final List<?> params;
+  public final Object result;
+  public final long timeAtStart;
+  public final long elapsed;
+  public final UUID uuid;
+
+  public static class UUID {
+
+    protected final String uuid;
+
+    protected UUID() {
+      uuid = String.format("audit:%s", java.util.UUID.randomUUID().toString());
+    }
+
+    public UUID(final String n) {
+      uuid = n;
+    }
+
+    public String get() {
+      return uuid;
+    }
+
+    @Override
+    public int hashCode() {
+      return uuid.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null) {
+        return false;
+      }
+      if (!(obj instanceof UUID)) {
+        return false;
+      }
+
+      return uuid.equals(((UUID) obj).uuid);
+    }
+  }
+
+  /**
+   * Creates a new audit event.
+   *
+   * @param sessionId session id the event belongs to
+   * @param who principal that has generated the event
+   * @param what object of the event
+   * @param params parameters of the event
+   */
+  public AuditEvent(String sessionId, CurrentUser who, String what, List<?> params) {
+    this(sessionId, who, what, System.currentTimeMillis(), params,
+        UNKNOWN_RESULT);
+  }
+
+  /**
+   * Creates a new audit event with results
+   *
+   * @param sessionId session id the event belongs to
+   * @param who principal that has generated the event
+   * @param what object of the event
+   * @param when time-stamp of when the event started
+   * @param params parameters of the event
+   * @param result result of the event
+   */
+  public AuditEvent(String sessionId, CurrentUser who, String what, long when,
+      List<?> params, Object result) {
+    Preconditions.checkNotNull(what, "what is a mandatory not null param !");
+
+    this.sessionId = getValueWithDefault(sessionId, UNKNOWN_SESSION_ID);
+    this.who = who;
+    this.what = what;
+    this.when = when;
+    this.timeAtStart = this.when;
+    this.params = getValueWithDefault(params, Collections.emptyList());
+    this.uuid = new UUID();
+    this.result = result;
+    this.elapsed = System.currentTimeMillis() - timeAtStart;
+  }
+
+  private <T> T getValueWithDefault(T value, T defaultValueIfNull) {
+    if (value == null) {
+      return defaultValueIfNull;
+    } else {
+      return value;
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    return uuid.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+
+    AuditEvent other = (AuditEvent) obj;
+    return this.uuid.equals(other.uuid);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append(uuid.toString());
+    sb.append("|");
+    sb.append(sessionId);
+    sb.append('|');
+    sb.append(who);
+    sb.append('|');
+    sb.append(when);
+    sb.append('|');
+    sb.append(what);
+    sb.append('|');
+    sb.append(elapsed);
+    sb.append('|');
+    if (params != null) {
+      sb.append('[');
+      for (int i = 0; i < params.size(); i++) {
+        if (i > 0) sb.append(',');
+
+        Object param = params.get(i);
+        if (param == null) {
+          sb.append("null");
+        } else {
+          sb.append(param);
+        }
+      }
+      sb.append(']');
+    }
+    sb.append('|');
+    if (result != null) {
+      sb.append(result);
+    }
+
+    return sb.toString();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditListener.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditListener.java
new file mode 100644
index 0000000..0aab248
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditListener.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.audit;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+@ExtensionPoint
+public interface AuditListener {
+
+  void onAuditableAction(AuditEvent action);
+
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
new file mode 100644
index 0000000..dc870ac
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditModule.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.audit;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.inject.AbstractModule;
+
+public class AuditModule extends AbstractModule {
+
+  @Override
+  protected void configure() {
+    DynamicSet.setOf(binder(), AuditListener.class);
+    bind(AuditService.class);
+  }
+
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
new file mode 100644
index 0000000..a992aa1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditService.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.audit;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class AuditService {
+  private final DynamicSet<AuditListener> auditListeners;
+
+  @Inject
+  public AuditService(DynamicSet<AuditListener> auditListeners) {
+    this.auditListeners = auditListeners;
+  }
+
+  public void dispatch(AuditEvent action) {
+    for (AuditListener auditListener : auditListeners) {
+      auditListener.onAuditableAction(action);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index a295c49..d7a487d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -29,7 +29,6 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
-import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -74,16 +73,29 @@
    *
    * @param change Change to update
    * @throws OrmException
-   * @throws IOException
    * @return List<PatchSetApproval> The previous approvals
    */
   public List<PatchSetApproval> copyVetosToLatestPatchSet(Change change)
-      throws OrmException, IOException {
+      throws OrmException {
+    return copyVetosToLatestPatchSet(db, change);
+  }
+
+  /**
+   * Moves the PatchSetApprovals to the last PatchSet on the change while
+   * keeping the vetos.
+   *
+   * @param db database connection to use for updates.
+   * @param change Change to update
+   * @throws OrmException
+   * @return List<PatchSetApproval> The previous approvals
+   */
+  public List<PatchSetApproval> copyVetosToLatestPatchSet(ReviewDb db,
+      Change change) throws OrmException {
     PatchSet.Id source;
     if (change.getNumberOfPatchSets() > 1) {
       source = new PatchSet.Id(change.getId(), change.getNumberOfPatchSets() - 1);
     } else {
-      throw new IOException("Previous patch set could not be found");
+      throw new OrmException("Previous patch set could not be found");
     }
 
     PatchSet.Id dest = change.currPatchSetId();
@@ -103,18 +115,9 @@
     return patchSetApprovals;
   }
 
-
-  /** Attach reviewers to a change. */
-  public void addReviewers(Change change, PatchSet ps, PatchSetInfo info,
-      Set<Account.Id> wantReviewers) throws OrmException {
-    Set<Id> existing = Sets.<Account.Id> newHashSet();
-    addReviewers(change, ps, info, wantReviewers, existing);
-  }
-
-  /** Attach reviewers to a change. */
-  public void addReviewers(Change change, PatchSet ps, PatchSetInfo info,
-      Set<Account.Id> wantReviewers, Set<Account.Id> existingReviewers)
-      throws OrmException {
+  public void addReviewers(ReviewDb db, Change change, PatchSet ps,
+      PatchSetInfo info, Set<Id> wantReviewers,
+      Set<Account.Id> existingReviewers) throws OrmException {
     List<ApprovalType> allTypes = approvalTypes.getApprovalTypes();
     if (allTypes.isEmpty()) {
       return;
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 3868710..94b2169 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
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server;
 
+import com.google.common.collect.Sets;
 import com.google.gerrit.common.ChangeHookRunner;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.reviewdb.client.Account;
@@ -38,7 +39,6 @@
 import com.google.gerrit.server.mail.ReplyToChangeSender;
 import com.google.gerrit.server.mail.RevertedSender;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -68,6 +68,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -196,13 +197,15 @@
    * Rebases a commit
    *
    * @param git Repository to find commits in
+   * @param inserter inserter to handle new trees and blobs.
    * @param original The commit to rebase
    * @param base Base to rebase against
    * @return CommitBuilder the newly rebased commit
    * @throws IOException Merged failed
    */
-  public static CommitBuilder rebaseCommit(Repository git, RevCommit original,
-      RevCommit base, PersonIdent committerIdent) throws IOException {
+  public static CommitBuilder rebaseCommit(Repository git,
+      final ObjectInserter inserter, RevCommit original, RevCommit base,
+      PersonIdent committerIdent) throws IOException {
 
     if (original.getParentCount() == 0) {
       throw new IOException(
@@ -222,6 +225,20 @@
     }
 
     final ThreeWayMerger merger = MergeStrategy.RESOLVE.newMerger(git, true);
+    merger.setObjectInserter(new ObjectInserter.Filter() {
+      @Override
+      protected ObjectInserter delegate() {
+        return inserter;
+      }
+
+      @Override
+      public void flush() {
+      }
+
+      @Override
+      public void release() {
+      }
+    });
     merger.setBase(parentCommit);
     merger.merge(original, base);
 
@@ -251,7 +268,7 @@
       final ApprovalsUtil approvalsUtil) throws NoSuchChangeException,
       EmailException, OrmException, MissingObjectException,
       IncorrectObjectTypeException, IOException,
-      PatchSetInfoNotAvailableException, InvalidChangeOperationException {
+      InvalidChangeOperationException {
 
     final Change.Id changeId = patchSetId.getParentKey();
     final ChangeControl changeControl =
@@ -319,104 +336,118 @@
           branchTipCommit = revWalk.parseCommit(destRef.getObjectId());
         }
 
-        final RevCommit originalCommit =
-            revWalk.parseCommit(ObjectId.fromString(originalPatchSet
-                .getRevision().get()));
-
-        CommitBuilder rebasedCommitBuilder =
-            rebaseCommit(git, originalCommit, branchTipCommit, myIdent);
-
+        final RevCommit rebasedCommit;
         final ObjectInserter oi = git.newObjectInserter();
-        final ObjectId rebasedCommitId;
         try {
-          rebasedCommitId = oi.insert(rebasedCommitBuilder);
+          ObjectId oldId = ObjectId.fromString(originalPatchSet.getRevision().get());
+          ObjectId newId = oi.insert(rebaseCommit(
+              git, oi, revWalk.parseCommit(oldId), branchTipCommit, myIdent));
           oi.flush();
+          rebasedCommit = revWalk.parseCommit(newId);
         } finally {
           oi.release();
         }
 
-        Change updatedChange =
-            db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
-              @Override
-              public Change update(Change change) {
-                if (change.getStatus().isOpen()) {
-                  change.nextPatchSetId();
-                  return change;
-                } else {
-                  return null;
-                }
-              }
-            });
+        change.nextPatchSetId();
+        final PatchSet newPatchSet = new PatchSet(change.currPatchSetId());
+        newPatchSet.setCreatedOn(new Timestamp(System.currentTimeMillis()));
+        newPatchSet.setUploader(user.getAccountId());
+        newPatchSet.setRevision(new RevId(rebasedCommit.name()));
 
-        if (updatedChange == null) {
-          throw new InvalidChangeOperationException("Change is closed: "
-              + change.toString());
-        } else {
-          change = updatedChange;
-        }
-
-        final PatchSet rebasedPatchSet = new PatchSet(change.currPatchSetId());
-        rebasedPatchSet.setCreatedOn(change.getCreatedOn());
-        rebasedPatchSet.setUploader(user.getAccountId());
-        rebasedPatchSet.setRevision(new RevId(rebasedCommitId.getName()));
-
-        insertAncestors(db, rebasedPatchSet.getId(),
-            revWalk.parseCommit(rebasedCommitId));
-
-        db.patchSets().insert(Collections.singleton(rebasedPatchSet));
         final PatchSetInfo info =
-            patchSetInfoFactory.get(db, rebasedPatchSet.getId());
+            patchSetInfoFactory.get(rebasedCommit, newPatchSet.getId());
 
-        change =
-            db.changes().atomicUpdate(change.getId(),
-                new AtomicUpdate<Change>() {
-                  @Override
-                  public Change update(Change change) {
-                    change.setCurrentPatchSet(info);
-                    ChangeUtil.updated(change);
-                    return change;
-                  }
-                });
-
-        final RefUpdate ru = git.updateRef(rebasedPatchSet.getRefName());
-        ru.setNewObjectId(rebasedCommitId);
+        RefUpdate ru = git.updateRef(newPatchSet.getRefName());
+        ru.setExpectedOldObjectId(ObjectId.zeroId());
+        ru.setNewObjectId(rebasedCommit);
         ru.disableRefLog();
         if (ru.update(revWalk) != RefUpdate.Result.NEW) {
-          throw new IOException("Failed to create ref "
-              + rebasedPatchSet.getRefName() + " in " + git.getDirectory()
-              + ": " + ru.getResult());
+          throw new IOException(String.format(
+              "Failed to create ref %s in %s: %s", newPatchSet.getRefName(),
+              change.getDest().getParentKey().get(), ru.getResult()));
         }
-
         replication.fire(change.getProject(), ru.getName());
 
-        List<PatchSetApproval> patchSetApprovals = approvalsUtil.copyVetosToLatestPatchSet(change);
+        final Set<Account.Id> oldReviewers = Sets.newHashSet();
+        final Set<Account.Id> oldCC = Sets.newHashSet();
+        db.changes().beginTransaction(change.getId());
+        try {
+          Change updatedChange;
 
-        final Set<Account.Id> oldReviewers = new HashSet<Account.Id>();
-        final Set<Account.Id> oldCC = new HashSet<Account.Id>();
-
-        for (PatchSetApproval a : patchSetApprovals) {
-          if (a.getValue() != 0) {
-            oldReviewers.add(a.getAccountId());
+          updatedChange = db.changes().atomicUpdate(changeId,
+              new AtomicUpdate<Change>() {
+                @Override
+                public Change update(Change change) {
+                  if (change.getStatus().isOpen()) {
+                    change.updateNumberOfPatchSets(newPatchSet.getPatchSetId());
+                    return change;
+                  } else {
+                    return null;
+                  }
+                }
+              });
+          if (updatedChange != null) {
+            change = updatedChange;
           } else {
-            oldCC.add(a.getAccountId());
+            throw new InvalidChangeOperationException(
+                String.format("Change %s is closed", change.getId()));
           }
-        }
 
-        final ChangeMessage cmsg =
-            new ChangeMessage(new ChangeMessage.Key(changeId,
-                ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
-        cmsg.setMessage("Patch Set " + patchSetId.get() + ": Rebased");
-        db.changeMessages().insert(Collections.singleton(cmsg));
+          insertAncestors(db, newPatchSet.getId(), rebasedCommit);
+          db.patchSets().insert(Collections.singleton(newPatchSet));
+          updatedChange = db.changes().atomicUpdate(changeId,
+              new AtomicUpdate<Change>() {
+                @Override
+                public Change update(Change change) {
+                  if (change.getStatus().isClosed()) {
+                    return null;
+                  }
+                  if (!change.currentPatchSetId().equals(patchSetId)) {
+                    return null;
+                  }
+                  if (change.getStatus() != Change.Status.DRAFT) {
+                    change.setStatus(Change.Status.NEW);
+                  }
+                  change.setLastSha1MergeTested(null);
+                  change.setCurrentPatchSet(info);
+                  ChangeUtil.updated(change);
+                  return change;
+                }
+              });
+          if (updatedChange != null) {
+            change = updatedChange;
+          } else {
+            throw new InvalidChangeOperationException(
+                String.format("Change %s was modified", change.getId()));
+          }
+
+          for (PatchSetApproval a : approvalsUtil.copyVetosToLatestPatchSet(change)) {
+            if (a.getValue() != 0) {
+              oldReviewers.add(a.getAccountId());
+            } else {
+              oldCC.add(a.getAccountId());
+            }
+          }
+
+          final ChangeMessage cmsg =
+              new ChangeMessage(new ChangeMessage.Key(changeId,
+                  ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
+          cmsg.setMessage("Patch Set " + patchSetId.get() + ": Rebased");
+          db.changeMessages().insert(Collections.singleton(cmsg));
+          db.commit();
+        } finally {
+          db.rollback();
+        }
 
         final ReplacePatchSetSender cm =
             rebasedPatchSetSenderFactory.create(change);
         cm.setFrom(user.getAccountId());
-        cm.setPatchSet(rebasedPatchSet);
+        cm.setPatchSet(newPatchSet);
         cm.addReviewers(oldReviewers);
         cm.addExtraCC(oldCC);
         cm.send();
 
-        hooks.doPatchsetCreatedHook(change, rebasedPatchSet, db);
+        hooks.doPatchsetCreatedHook(change, newPatchSet, db);
       } finally {
         revWalk.release();
       }
@@ -432,9 +463,7 @@
       final PatchSetInfoFactory patchSetInfoFactory,
       final GitReferenceUpdated replication, PersonIdent myIdent)
       throws NoSuchChangeException, EmailException, OrmException,
-      MissingObjectException, IncorrectObjectTypeException, IOException,
-      PatchSetInfoNotAvailableException {
-
+      MissingObjectException, IncorrectObjectTypeException, IOException {
     final Change.Id changeId = patchSetId.getParentKey();
     final PatchSet patch = db.patchSets().get(patchSetId);
     if (patch == null) {
@@ -446,7 +475,7 @@
       git = gitManager.openRepository(db.changes().get(changeId).getProject());
     } catch (RepositoryNotFoundException e) {
       throw new NoSuchChangeException(changeId, e);
-    };
+    }
 
     final RevWalk revWalk = new RevWalk(git);
     try {
@@ -459,54 +488,62 @@
       RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
       revWalk.parseHeaders(parentToCommitToRevert);
 
-      CommitBuilder revertCommit = new CommitBuilder();
-      revertCommit.addParentId(commitToRevert);
-      revertCommit.setTreeId(parentToCommitToRevert.getTree());
-      revertCommit.setAuthor(authorIdent);
-      revertCommit.setCommitter(myIdent);
+      CommitBuilder revertCommitBuilder = new CommitBuilder();
+      revertCommitBuilder.addParentId(commitToRevert);
+      revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
+      revertCommitBuilder.setAuthor(authorIdent);
+      revertCommitBuilder.setCommitter(myIdent);
 
       final ObjectId computedChangeId =
           ChangeIdUtil.computeChangeId(parentToCommitToRevert.getTree(),
               commitToRevert, authorIdent, myIdent, message);
-      revertCommit.setMessage(ChangeIdUtil.insertId(message, computedChangeId, true));
+      revertCommitBuilder.setMessage(ChangeIdUtil.insertId(message, computedChangeId, true));
 
-      final ObjectInserter oi = git.newObjectInserter();;
-      ObjectId id;
+      RevCommit revertCommit;
+      final ObjectInserter oi = git.newObjectInserter();
       try {
-        id = oi.insert(revertCommit);
+        ObjectId id = oi.insert(revertCommitBuilder);
         oi.flush();
+        revertCommit = revWalk.parseCommit(id);
       } finally {
         oi.release();
       }
 
-      Change.Key changeKey = new Change.Key("I" + computedChangeId.name());
-      final Change change =
-          new Change(changeKey, new Change.Id(db.nextChangeId()),
-              user.getAccountId(), db.changes().get(changeId).getDest());
+      final Change change = new Change(
+          new Change.Key("I" + computedChangeId.name()),
+          new Change.Id(db.nextChangeId()),
+          user.getAccountId(),
+          db.changes().get(changeId).getDest());
       change.nextPatchSetId();
 
       final PatchSet ps = new PatchSet(change.currPatchSetId());
       ps.setCreatedOn(change.getCreatedOn());
-      ps.setUploader(user.getAccountId());
-      ps.setRevision(new RevId(id.getName()));
+      ps.setUploader(change.getOwner());
+      ps.setRevision(new RevId(revertCommit.name()));
 
-      db.patchSets().insert(Collections.singleton(ps));
-
-      final PatchSetInfo info =
-          patchSetInfoFactory.get(revWalk.parseCommit(id), ps.getId());
-      change.setCurrentPatchSet(info);
+      change.setCurrentPatchSet(patchSetInfoFactory.get(revertCommit, ps.getId()));
       ChangeUtil.updated(change);
-      db.changes().insert(Collections.singleton(change));
 
       final RefUpdate ru = git.updateRef(ps.getRefName());
-      ru.setNewObjectId(id);
+      ru.setExpectedOldObjectId(ObjectId.zeroId());
+      ru.setNewObjectId(revertCommit);
       ru.disableRefLog();
       if (ru.update(revWalk) != RefUpdate.Result.NEW) {
-        throw new IOException("Failed to create ref " + ps.getRefName()
-            + " in " + git.getDirectory() + ": " + ru.getResult());
+        throw new IOException(String.format(
+            "Failed to create ref %s in %s: %s", ps.getRefName(),
+            change.getDest().getParentKey().get(), ru.getResult()));
       }
-      replication.fire(db.changes().get(changeId).getProject(),
-          ru.getName());
+      replication.fire(change.getProject(), ru.getName());
+
+      db.changes().beginTransaction(change.getId());
+      try {
+        insertAncestors(db, ps.getId(), revertCommit);
+        db.patchSets().insert(Collections.singleton(ps));
+        db.changes().insert(Collections.singleton(change));
+        db.commit();
+      } finally {
+        db.rollback();
+      }
 
       final ChangeMessage cmsg =
           new ChangeMessage(new ChangeMessage.Key(changeId,
@@ -514,7 +551,7 @@
       final StringBuilder msgBuf =
           new StringBuilder("Patch Set " + patchSetId.get() + ": Reverted");
       msgBuf.append("\n\n");
-      msgBuf.append("This patchset was reverted in change: " + changeKey.get());
+      msgBuf.append("This patchset was reverted in change: " + change.getKey().get());
 
       cmsg.setMessage(msgBuf.toString());
       db.changeMessages().insert(Collections.singleton(cmsg));
@@ -596,7 +633,7 @@
   public static <T extends ReplyToChangeSender> void updatedChange(
       final ReviewDb db, final IdentifiedUser user, final Change change,
       final ChangeMessage cmsg, ReplyToChangeSender.Factory<T> senderFactory)
-      throws NoSuchChangeException, EmailException, OrmException {
+      throws OrmException {
     db.changeMessages().insert(Collections.singleton(cmsg));
 
     new ApprovalsUtil(db, null).syncChangeStatus(change);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java
index 3112ed4..4037266 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VisibleGroups.java
@@ -14,18 +14,15 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.gerrit.common.data.GroupDetail;
 import com.google.gerrit.common.data.GroupList;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.project.ProjectControl;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
@@ -41,7 +38,6 @@
   private final Provider<IdentifiedUser> identifiedUser;
   private final GroupCache groupCache;
   private final GroupControl.Factory groupControlFactory;
-  private final GroupDetailFactory.Factory groupDetailFactory;
 
   private boolean onlyVisibleToAll;
   private AccountGroup.Type groupType;
@@ -49,12 +45,10 @@
   @Inject
   VisibleGroups(final Provider<IdentifiedUser> currentUser,
       final GroupCache groupCache,
-      final GroupControl.Factory groupControlFactory,
-      final GroupDetailFactory.Factory groupDetailFactory) {
+      final GroupControl.Factory groupControlFactory) {
     this.identifiedUser = currentUser;
     this.groupCache = groupCache;
     this.groupControlFactory = groupControlFactory;
-    this.groupDetailFactory = groupDetailFactory;
   }
 
   public void setOnlyVisibleToAll(final boolean onlyVisibleToAll) {
@@ -65,13 +59,12 @@
     this.groupType = groupType;
   }
 
-  public GroupList get() throws OrmException, NoSuchGroupException {
-    final Iterable<AccountGroup> groups = groupCache.all();
-    return createGroupList(filterGroups(groups));
+  public GroupList get() {
+    return createGroupList(filterGroups(groupCache.all()));
   }
 
   public GroupList get(final Collection<ProjectControl> projects)
-      throws OrmException, NoSuchGroupException {
+      throws NoSuchGroupException {
     final Set<AccountGroup> groups =
         new TreeSet<AccountGroup>(new GroupComparator());
     for (final ProjectControl projectControl : projects) {
@@ -93,8 +86,7 @@
    * groups.
    * @See GroupMembership#getKnownGroups()
    */
-  public GroupList get(final IdentifiedUser user) throws OrmException,
-      NoSuchGroupException {
+  public GroupList get(final IdentifiedUser user) throws NoSuchGroupException {
     if (identifiedUser.get().getAccountId().equals(user.getAccountId())
         || identifiedUser.get().getCapabilities().canAdministrateServer()) {
       final Set<AccountGroup.UUID> effective =
@@ -134,13 +126,8 @@
     return filteredGroups;
   }
 
-  private GroupList createGroupList(final List<AccountGroup> groups)
-      throws OrmException, NoSuchGroupException {
-    final List<GroupDetail> groupDetailList = new ArrayList<GroupDetail>();
-    for (final AccountGroup group : groups) {
-      groupDetailList.add(groupDetailFactory.create(group.getId()).call());
-    }
-    return new GroupList(groupDetailList, identifiedUser.get()
+  private GroupList createGroupList(final List<AccountGroup> groups) {
+    return new GroupList(groups, identifiedUser.get()
         .getCapabilities().canCreateGroup());
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheRemovalListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheRemovalListener.java
new file mode 100644
index 0000000..078f2dc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheRemovalListener.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.cache;
+
+import com.google.common.cache.RemovalNotification;
+
+public interface CacheRemovalListener<K,V> {
+  public void onRemoval(String pluginName,
+    String cacheName,
+    RemovalNotification<K, V> notification);
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
new file mode 100644
index 0000000..c98ddf9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.cache;
+
+import com.google.common.base.Strings;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+/**
+ * This listener dispatches removal events to all other RemovalListeners
+ * attached via the DynamicSet API.
+ *
+ * @param <K>
+ * @param <V>
+ */
+@SuppressWarnings("rawtypes")
+public class ForwardingRemovalListener<K, V> implements RemovalListener<K, V> {
+  public interface Factory {
+    ForwardingRemovalListener create(String cacheName);
+  }
+
+  private final DynamicSet<CacheRemovalListener> listeners;
+  private final String cacheName;
+  private String pluginName = "gerrit";
+
+  @Inject
+  ForwardingRemovalListener(DynamicSet<CacheRemovalListener> listeners,
+      @Assisted String cacheName) {
+    this.listeners = listeners;
+    this.cacheName = cacheName;
+  }
+
+  @Inject(optional = true)
+  void setPluginName(String name) {
+    if (!Strings.isNullOrEmpty(name)) {
+      this.pluginName = name;
+    }
+  }
+
+  public void onRemoval(RemovalNotification<K, V> notification) {
+    for (CacheRemovalListener<K, V> l : listeners) {
+      l.onRemoval(pluginName, cacheName, notification);
+    }
+  }
+}
\ No newline at end of file
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 9e3aeca..2a66706 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
@@ -17,6 +17,7 @@
 import static com.google.inject.Scopes.SINGLETON;
 
 import com.google.common.cache.Cache;
+import com.google.gerrit.audit.AuditModule;
 import com.google.gerrit.common.data.ApprovalTypes;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
@@ -48,6 +49,7 @@
 import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.account.UniversalGroupBackend;
 import com.google.gerrit.server.auth.ldap.LdapModule;
+import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.ChangeMergeQueue;
@@ -167,8 +169,11 @@
     bind(ProjectControl.GenericFactory.class);
     factory(FunctionState.Factory.class);
 
+    install(new AuditModule());
+
     bind(GitReferenceUpdated.class);
     DynamicMap.mapOf(binder(), new TypeLiteral<Cache<?, ?>>() {});
+    DynamicSet.setOf(binder(), CacheRemovalListener.class);
     DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
     DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
index da38573..c0e00aa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.server.git.GitRepositoryManager.REF_REJECT_COMMITS;
 
-import com.google.common.base.Strings;
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeUpdateExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeUpdateExecutor.java
new file mode 100644
index 0000000..3452bb0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeUpdateExecutor.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Marker on the global {@link ListeningExecutorService} used by
+ * {@link ReceiveCommits} to create or replace changes.
+ */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface ChangeUpdateExecutor {
+}
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 fa47a2c..5b78c7b 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
@@ -673,6 +673,12 @@
           identifiedUserFactory.create(submitter.getAccountId());
       Set<String> emails = new HashSet<String>();
       for (RevCommit c : codeReviewCommits) {
+        try {
+          rw.parseBody(c);
+        } catch (IOException e) {
+          log.warn("Cannot parse commit " + c.name() + " in " + destBranch, e);
+          continue;
+        }
         emails.add(c.getAuthorIdent().getEmailAddress());
       }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
index dbb849c..23d8dad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MultiProgressMonitor.java
@@ -17,6 +17,7 @@
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ProgressMonitor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,7 +59,7 @@
   private static final char NO_SPINNER = ' ';
 
   /** Handle for a sub-task. */
-  public class Task {
+  public class Task implements ProgressMonitor {
     private final String name;
     private final int total;
     private volatile int count;
@@ -76,6 +77,7 @@
      *
      * @param completed number of work units completed.
      */
+    @Override
     public void update(final int completed) {
       count += completed;
       if (total != UNKNOWN) {
@@ -97,6 +99,23 @@
         wakeUp();
       }
     }
+
+    @Override
+    public void start(int totalTasks) {
+    }
+
+    @Override
+    public void beginTask(String title, int totalWork) {
+    }
+
+    @Override
+    public void endTask() {
+    }
+
+    @Override
+    public boolean isCancelled() {
+      return false;
+    }
   }
 
   private final OutputStream out;
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 e9a35b8..4d80193 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
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.git;
 
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
@@ -21,8 +22,18 @@
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
 
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.Capable;
@@ -61,18 +72,20 @@
 import com.google.gerrit.server.util.RequestScopePropagator;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.notes.NoteMap;
 import org.eclipse.jgit.revwalk.FooterKey;
@@ -85,6 +98,7 @@
 import org.eclipse.jgit.transport.AdvertiseRefsHook;
 import org.eclipse.jgit.transport.AdvertiseRefsHookChain;
 import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceiveCommand.Result;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -99,6 +113,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -201,11 +216,23 @@
     }
   }
 
+  private static final Function<Exception, OrmException> ORM_EXCEPTION =
+      new Function<Exception, OrmException>() {
+        @Override
+        public OrmException apply(Exception input) {
+          if (input instanceof OrmException) {
+            return (OrmException) input;
+          }
+          return new OrmException("Error updating database", input);
+        }
+      };
+
   private final Set<Account.Id> reviewerId = new HashSet<Account.Id>();
   private final Set<Account.Id> ccId = new HashSet<Account.Id>();
 
   private final IdentifiedUser currentUser;
   private final ReviewDb db;
+  private final SchemaFactory<ReviewDb> schemaFactory;
   private final AccountResolver accountResolver;
   private final CreateChangeSender.Factory createChangeSenderFactory;
   private final MergedSender.Factory mergedSenderFactory;
@@ -221,6 +248,7 @@
   private final TrackingFooters trackingFooters;
   private final TagCache tagCache;
   private final WorkQueue workQueue;
+  private final ListeningExecutorService changeUpdateExector;
   private final RequestScopePropagator requestScopePropagator;
 
   private final ProjectControl projectControl;
@@ -232,7 +260,7 @@
   private Branch.NameKey destBranch;
   private RefControl destBranchCtl;
 
-  private final List<Change> allNewChanges = new ArrayList<Change>();
+  private List<CreateRequest> newChanges = Collections.emptyList();
   private final Map<Change.Id, ReplaceRequest> replaceByChange =
       new HashMap<Change.Id, ReplaceRequest>();
   private final Map<RevCommit, ReplaceRequest> replaceByCommit =
@@ -252,9 +280,11 @@
   private Task closeProgress;
   private Task commandProgress;
   private MessageSender messageSender;
+  private BatchRefUpdate batch;
 
   @Inject
   ReceiveCommits(final ReviewDb db,
+      final SchemaFactory<ReviewDb> schemaFactory,
       final AccountResolver accountResolver,
       final CreateChangeSender.Factory createChangeSenderFactory,
       final MergedSender.Factory mergedSenderFactory,
@@ -270,6 +300,7 @@
       @GerritPersonIdent final PersonIdent gerritIdent,
       final TrackingFooters trackingFooters,
       final WorkQueue workQueue,
+      @ChangeUpdateExecutor ListeningExecutorService changeUpdateExector,
       final RequestScopePropagator requestScopePropagator,
 
       @Assisted final ProjectControl projectControl,
@@ -277,6 +308,7 @@
       final SubmoduleOp.Factory subOpFactory) throws IOException {
     this.currentUser = (IdentifiedUser) projectControl.getCurrentUser();
     this.db = db;
+    this.schemaFactory = schemaFactory;
     this.accountResolver = accountResolver;
     this.createChangeSenderFactory = createChangeSenderFactory;
     this.mergedSenderFactory = mergedSenderFactory;
@@ -292,6 +324,7 @@
     this.trackingFooters = trackingFooters;
     this.tagCache = tagCache;
     this.workQueue = workQueue;
+    this.changeUpdateExector = changeUpdateExector;
     this.requestScopePropagator = requestScopePropagator;
 
     this.projectControl = projectControl;
@@ -459,13 +492,34 @@
     closeProgress = progress.beginSubTask("closed", UNKNOWN);
     commandProgress = progress.beginSubTask("refs", UNKNOWN);
 
+    batch = repo.getRefDatabase().newBatchUpdate();
+    batch.setRefLogIdent(rp.getRefLogIdent());
+    batch.setRefLogMessage("push", true);
+
     parseCommands(commands);
     if (newChange != null && newChange.getResult() == NOT_ATTEMPTED) {
-      createNewChanges();
+      newChanges = selectNewChanges();
     }
-    newProgress.end();
+    preparePatchSetsForReplace();
 
-    doReplaces();
+    if (!batch.getCommands().isEmpty()) {
+      try {
+        batch.execute(rp.getRevWalk(), commandProgress);
+      } catch (IOException err) {
+        int cnt = 0;
+        for (ReceiveCommand cmd : batch.getCommands()) {
+          if (cmd.getResult() == NOT_ATTEMPTED) {
+            cmd.setResult(REJECTED_OTHER_REASON, "internal server error");
+            cnt++;
+          }
+        }
+        log.error(String.format(
+            "Failed to store %d refs in %s", cnt, project.getName()), err);
+      }
+    }
+
+    insertChangesAndPatchSets();
+    newProgress.end();
     replaceProgress.end();
 
     if (!errors.isEmpty()) {
@@ -514,9 +568,11 @@
           // Change refs are scheduled when they are created.
           //
           replication.fire(project.getNameKey(), c.getRefName());
-          Branch.NameKey destBranch = new Branch.NameKey(project.getNameKey(), c.getRefName());
-          hooks.doRefUpdatedHook(destBranch, c.getOldId(), c.getNewId(), currentUser.getAccount());
-          commandProgress.update(1);
+          hooks.doRefUpdatedHook(
+              new Branch.NameKey(project.getNameKey(), c.getRefName()),
+              c.getOldId(),
+              c.getNewId(),
+              currentUser.getAccount());
         }
       }
     }
@@ -524,22 +580,110 @@
     commandProgress.end();
     progress.end();
 
-    if (!allNewChanges.isEmpty() && canonicalWebUrl != null) {
+    Iterable<CreateRequest> created =
+        Iterables.filter(newChanges, new Predicate<CreateRequest>() {
+          @Override
+          public boolean apply(CreateRequest input) {
+            return input.created;
+          }
+        });
+    if (!Iterables.isEmpty(created) && canonicalWebUrl != null) {
       final String url = canonicalWebUrl;
       addMessage("");
       addMessage("New Changes:");
-      for (final Change c : allNewChanges) {
-        if (c.getStatus() == Change.Status.DRAFT) {
-          addMessage("  " + url + c.getChangeId() + " [DRAFT]");
+      for (CreateRequest c : created) {
+        StringBuilder m = new StringBuilder()
+            .append("  ")
+            .append(url)
+            .append(c.change.getChangeId());
+        if (c.change.getStatus() == Change.Status.DRAFT) {
+          m.append(" [DRAFT]");
         }
-        else {
-          addMessage("  " + url + c.getChangeId());
-        }
+        addMessage(m.toString());
       }
       addMessage("");
     }
   }
 
+  private void insertChangesAndPatchSets() {
+    int replaceCount = 0;
+    int okToInsert = 0;
+
+    for (ReplaceRequest replace : replaceByChange.values()) {
+      if (replace.inputCommand == newChange) {
+        replaceCount++;
+
+        if (replace.cmd.getResult() == OK) {
+          okToInsert++;
+        }
+      } else if (replace.cmd.getResult() == OK) {
+        try {
+          if (replace.insertPatchSet().checkedGet() != null) {
+            replace.inputCommand.setResult(OK);
+          }
+        } catch (IOException err) {
+          reject(replace.inputCommand, "internal server error");
+          log.error(String.format(
+              "Cannot add patch set to %d of %s",
+              replace.newPatchSet.getId(), project.getName()), err);
+        } catch (OrmException err) {
+          reject(replace.inputCommand, "internal server error");
+          log.error(String.format(
+              "Cannot add patch set to %d of %s",
+              replace.newPatchSet.getId(), project.getName()), err);
+        }
+      } else {
+        reject(replace.inputCommand, "internal server error");
+      }
+    }
+
+    if (newChange == null || newChange.getResult() != NOT_ATTEMPTED) {
+      // refs/for/ or refs/drafts/ not used, or it already failed earlier.
+      // No need to continue.
+      return;
+    }
+
+    for (CreateRequest create : newChanges) {
+      if (create.cmd.getResult() == OK) {
+        okToInsert++;
+      }
+    }
+
+    if (okToInsert != replaceCount + newChanges.size()) {
+      // One or more new references failed to create. Assume the
+      // system isn't working correctly anymore and abort.
+      reject(newChange, "internal server error");
+      log.error(String.format(
+          "Only %d of %d new change refs created in %s; aborting",
+          okToInsert, newChanges.size(), project.getName()));
+      return;
+    }
+
+    try {
+      List<CheckedFuture<?, OrmException>> futures = Lists.newArrayList();
+      for (ReplaceRequest replace : replaceByChange.values()) {
+        if (replace.inputCommand == newChange) {
+          futures.add(replace.insertPatchSet());
+        }
+      }
+
+      for (CreateRequest create : newChanges) {
+        futures.add(create.insertChange());
+      }
+
+      for (CheckedFuture<?, OrmException> f : futures) {
+        f.checkedGet();
+      }
+      newChange.setResult(OK);
+    } catch (OrmException err) {
+      log.error("Can't insert changes for " + project.getName(), err);
+      reject(newChange, "internal server error");
+    } catch (IOException err) {
+      log.error("Can't read commits for " + project.getName(), err);
+      reject(newChange, "internal server error");
+    }
+  }
+
   private String buildError(Error error, List<String> branches) {
     StringBuilder sb = new StringBuilder();
     if (branches.size() == 1) {
@@ -690,9 +834,7 @@
     RefControl ctl = projectControl.controlForRef(cmd.getRefName());
     if (ctl.canCreate(rp.getRevWalk(), obj)) {
       validateNewCommits(ctl, cmd);
-      if (cmd.getResult() == NOT_ATTEMPTED) {
-        cmd.execute(rp);
-      }
+      batch.addCommand(cmd);
     } else {
       errors.put(Error.CREATE, ctl.getRefName());
       reject(cmd, "can not create new references");
@@ -707,16 +849,14 @@
       }
 
       validateNewCommits(ctl, cmd);
-      if (cmd.getResult() == NOT_ATTEMPTED) {
-        cmd.execute(rp);
-      }
+      batch.addCommand(cmd);
     } else {
       if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
         errors.put(Error.CONFIG_UPDATE, GitRepositoryManager.REF_CONFIG);
       } else {
         errors.put(Error.UPDATE, ctl.getRefName());
       }
-      reject(cmd, "can not update the reference as a fast forward");
+      reject(cmd);
     }
   }
 
@@ -742,9 +882,7 @@
   private void parseDelete(final ReceiveCommand cmd) {
     RefControl ctl = projectControl.controlForRef(cmd.getRefName());
     if (ctl.canDelete()) {
-      if (cmd.getResult() == NOT_ATTEMPTED) {
-        cmd.execute(rp);
-      }
+      batch.addCommand(cmd);
     } else {
       if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
         reject(cmd, "cannot delete project configuration");
@@ -777,9 +915,7 @@
     }
 
     if (ctl.canForceUpdate()) {
-      if (cmd.getResult() == NOT_ATTEMPTED) {
-        cmd.execute(rp);
-      }
+      batch.setAllowNonFastForwards(true).addCommand(cmd);
     } else {
       cmd.setResult(REJECTED_NONFASTFORWARD, " need '"
           + PermissionRule.FORCE_PUSH + "' privilege.");
@@ -975,29 +1111,40 @@
     return true;
   }
 
-  private void createNewChanges() {
-    final List<RevCommit> toCreate = new ArrayList<RevCommit>();
+  private List<CreateRequest> selectNewChanges() {
+    final List<CreateRequest> newChanges = Lists.newArrayList();
     final RevWalk walk = rp.getRevWalk();
     walk.reset();
     walk.sort(RevSort.TOPO);
     walk.sort(RevSort.REVERSE, true);
     try {
+      Set<ObjectId> existing = Sets.newHashSet();
       walk.markStart(walk.parseCommit(newChange.getNewId()));
-      for (ObjectId id : existingObjects()) {
-        try {
-          walk.markUninteresting(walk.parseCommit(id));
-        } catch (IOException e) {
+      for (Ref ref : repo.getAllRefs().values()) {
+        if (ref.getObjectId() == null) {
           continue;
+        } else if (ref.getName().startsWith("refs/changes/")) {
+          existing.add(ref.getObjectId());
+        } else if (ref.getName().startsWith(R_HEADS)
+            || ref.getName().equals(destBranchCtl.getRefName())) {
+          try {
+            walk.markUninteresting(walk.parseCommit(ref.getObjectId()));
+          } catch (IOException e) {
+            log.warn(String.format("Invalid ref %s in %s",
+                ref.getName(), project.getName()), e);
+            continue;
+          }
         }
       }
 
+      List<ChangeLookup> pending = Lists.newArrayList();
       final Set<Change.Key> newChangeIds = new HashSet<Change.Key>();
       for (;;) {
         final RevCommit c = walk.next();
         if (c == null) {
           break;
         }
-        if (replaceByCommit.containsKey(c)) {
+        if (existing.contains(c) || replaceByCommit.containsKey(c)) {
           // This commit was already scheduled to replace an existing PatchSet.
           //
           continue;
@@ -1005,59 +1152,63 @@
         if (!validCommit(destBranchCtl, newChange, c)) {
           // Not a change the user can propose? Abort as early as possible.
           //
-          return;
+          return Collections.emptyList();
         }
 
+        Change.Key changeKey = new Change.Key("I" + c.name());
         final List<String> idList = c.getFooterLines(CHANGE_ID);
-        if (!idList.isEmpty()) {
-          final String idStr = idList.get(idList.size() - 1).trim();
-          if (idStr.matches("^I00*$")) {
-            // Reject this invalid line from EGit.
-            reject(newChange, "invalid Change-Id");
-            return;
-          }
+        if (idList.isEmpty()) {
+          newChanges.add(new CreateRequest(c, changeKey));
+          continue;
+        }
 
-          final Change.Key key = new Change.Key(idStr);
+        final String idStr = idList.get(idList.size() - 1).trim();
+        if (idStr.matches("^I00*$")) {
+          // Reject this invalid line from EGit.
+          reject(newChange, "invalid Change-Id");
+          return Collections.emptyList();
+        }
 
-          if (newChangeIds.contains(key)) {
-            reject(newChange, "squash commits first");
-            return;
-          }
+        changeKey = new Change.Key(idStr);
+        pending.add(new ChangeLookup(c, changeKey));
+      }
 
-          final List<Change> changes =
-              db.changes().byBranchKey(destBranch, key).toList();
-          if (changes.size() > 1) {
-            // WTF, multiple changes in this project have the same key?
-            // Since the commit is new, the user should recreate it with
-            // a different Change-Id. In practice, we should never see
-            // this error message as Change-Id should be unique.
-            //
-            reject(newChange, key.get() + " has duplicates");
-            return;
+      for (ChangeLookup p : pending) {
+        if (newChangeIds.contains(p.changeKey)) {
+          reject(newChange, "squash commits first");
+          return Collections.emptyList();
+        }
 
-          }
+        List<Change> changes = p.changes.toList();
+        if (changes.size() > 1) {
+          // WTF, multiple changes in this project have the same key?
+          // Since the commit is new, the user should recreate it with
+          // a different Change-Id. In practice, we should never see
+          // this error message as Change-Id should be unique.
+          //
+          reject(newChange, p.changeKey.get() + " has duplicates");
+          return Collections.emptyList();
+        }
 
-          if (changes.size() == 1) {
-            // Schedule as a replacement to this one matching change.
-            //
-            if (requestReplace(newChange, false, changes.get(0), c)) {
-              continue;
-            } else {
-              return;
-            }
-          }
-
-          if (changes.size() == 0) {
-            if (!isValidChangeId(idStr)) {
-              reject(newChange, "invalid Change-Id");
-              return;
-            }
-
-            newChangeIds.add(key);
+        if (changes.size() == 1) {
+          // Schedule as a replacement to this one matching change.
+          //
+          if (requestReplace(newChange, false, changes.get(0), p.commit)) {
+            continue;
+          } else {
+            return Collections.emptyList();
           }
         }
 
-        toCreate.add(c);
+        if (changes.size() == 0) {
+          if (!isValidChangeId(p.changeKey.get())) {
+            reject(newChange, "invalid Change-Id");
+            return Collections.emptyList();
+          }
+
+          newChangeIds.add(p.changeKey);
+        }
+        newChanges.add(new CreateRequest(p.commit, p.changeKey));
       }
     } catch (IOException e) {
       // Should never happen, the core receive process would have
@@ -1065,136 +1216,160 @@
       //
       newChange.setResult(REJECTED_MISSING_OBJECT);
       log.error("Invalid pack upload; one or more objects weren't sent", e);
-      return;
+      return Collections.emptyList();
     } catch (OrmException e) {
       log.error("Cannot query database to locate prior changes", e);
       reject(newChange, "database error");
-      return;
+      return Collections.emptyList();
     }
 
-    if (toCreate.isEmpty() && replaceByChange.isEmpty()) {
+    if (newChanges.isEmpty() && replaceByChange.isEmpty()) {
       reject(newChange, "no new changes");
-      return;
+      return Collections.emptyList();
     }
-
-    for (final RevCommit c : toCreate) {
-      try {
-        createChange(walk, c);
-      } catch (IOException e) {
-        log.error("Error computing patch of commit " + c.name(), e);
-        reject(newChange, "diff error");
-        return;
-      } catch (OrmException e) {
-        log.error("Error creating change for commit " + c.name(), e);
-        reject(newChange, "database error");
-        return;
-      }
-      newProgress.update(1);
+    for (CreateRequest create : newChanges) {
+      batch.addCommand(create.cmd);
     }
-    newChange.setResult(OK);
+    return newChanges;
   }
 
   private static boolean isValidChangeId(String idStr) {
     return idStr.matches("^I[0-9a-fA-F]{40}$") && !idStr.matches("^I00*$");
   }
 
-  private void createChange(final RevWalk walk, final RevCommit c)
-      throws OrmException, IOException {
-    walk.parseBody(c);
-    warnMalformedMessage(c);
+  private class ChangeLookup {
+    final RevCommit commit;
+    final Change.Key changeKey;
+    final ResultSet<Change> changes;
 
-    final Account.Id me = currentUser.getAccountId();
-    Change.Key changeKey = new Change.Key("I" + c.name());
-    final Set<Account.Id> reviewers = new HashSet<Account.Id>(reviewerId);
-    final Set<Account.Id> cc = new HashSet<Account.Id>(ccId);
-    final List<FooterLine> footerLines = c.getFooterLines();
-    for (final FooterLine footerLine : footerLines) {
-      try {
-        if (footerLine.matches(CHANGE_ID)) {
-          final String v = footerLine.getValue().trim();
-          if (isValidChangeId(v)) {
-            changeKey = new Change.Key(v);
-          }
-        } else if (isReviewer(footerLine)) {
-          reviewers.add(toAccountId(footerLine.getValue().trim()));
-        } else if (footerLine.matches(FooterKey.CC)) {
-          cc.add(toAccountId(footerLine.getValue().trim()));
-        }
-      } catch (NoSuchAccountException e) {
-        continue;
-      }
+    ChangeLookup(RevCommit c, Change.Key key) throws OrmException {
+      commit = c;
+      changeKey = key;
+      changes = db.changes().byBranchKey(destBranch, key);
     }
-    reviewers.remove(me);
-    cc.remove(me);
-    cc.removeAll(reviewers);
+  }
 
+  private class CreateRequest {
+    final RevCommit commit;
     final Change change;
     final PatchSet ps;
-    final PatchSetInfo info;
+    final ReceiveCommand cmd;
+    private final PatchSetInfo info;
+    boolean created;
 
-    change = new Change(changeKey, new Change.Id(db.nextChangeId()), me, destBranch);
-    change.setTopic(destTopicName);
-    change.nextPatchSetId();
+    CreateRequest(RevCommit c, Change.Key changeKey) throws OrmException {
+      commit = c;
 
-    db.changes().beginTransaction(change.getId());
-    try {
+      change = new Change(changeKey,
+          new Change.Id(db.nextChangeId()),
+          currentUser.getAccountId(),
+          destBranch);
+      change.setTopic(destTopicName);
+      change.nextPatchSetId();
+
       ps = new PatchSet(change.currPatchSetId());
       ps.setCreatedOn(change.getCreatedOn());
-      ps.setUploader(me);
+      ps.setUploader(change.getOwner());
       ps.setRevision(toRevId(c));
+
       if (MagicBranch.isDraft(newChange.getRefName())) {
         change.setStatus(Change.Status.DRAFT);
         ps.setDraft(true);
       }
-      insertAncestors(ps.getId(), c);
-      db.patchSets().insert(Collections.singleton(ps));
 
       info = patchSetInfoFactory.get(c, ps.getId());
       change.setCurrentPatchSet(info);
       ChangeUtil.updated(change);
-      db.changes().insert(Collections.singleton(change));
-      ChangeUtil.updateTrackingIds(db, change, trackingFooters, footerLines);
-      approvalsUtil.addReviewers(change, ps, info, reviewers);
-      db.commit();
-    } finally {
-      db.rollback();
+      cmd = new ReceiveCommand(ObjectId.zeroId(), c, ps.getRefName());
     }
 
-    final RefUpdate ru = repo.updateRef(ps.getRefName());
-    ru.setNewObjectId(c);
-    ru.disableRefLog();
-    if (ru.update(walk) != RefUpdate.Result.NEW) {
-      throw new IOException("Failed to create ref " + ps.getRefName() + " in "
-          + repo.getDirectory() + ": " + ru.getResult());
+    CheckedFuture<Void, OrmException> insertChange() throws IOException {
+      rp.getRevWalk().parseBody(commit);
+      warnMalformedMessage(commit);
+
+      final Thread caller = Thread.currentThread();
+      ListenableFuture<Void> future = changeUpdateExector.submit(
+          requestScopePropagator.wrap(new Callable<Void>() {
+        @Override
+        public Void call() throws OrmException {
+          if (caller == Thread.currentThread()) {
+            insertChange(db);
+          } else {
+            ReviewDb db = schemaFactory.open();
+            try {
+              insertChange(db);
+            } finally {
+              db.close();
+            }
+          }
+          synchronized (newProgress) {
+            newProgress.update(1);
+          }
+          return null;
+        }
+      }));
+      return Futures.makeChecked(future, ORM_EXCEPTION);
     }
-    replication.fire(project.getNameKey(), ru.getName());
 
-    allNewChanges.add(change);
-
-    workQueue.getDefaultQueue()
-        .submit(requestScopePropagator.wrap(new Runnable() {
-      @Override
-      public void run() {
+    private void insertChange(ReviewDb db) throws OrmException {
+      final Account.Id me = currentUser.getAccountId();
+      final Set<Account.Id> reviewers = new HashSet<Account.Id>(reviewerId);
+      final Set<Account.Id> cc = new HashSet<Account.Id>(ccId);
+      final List<FooterLine> footerLines = commit.getFooterLines();
+      for (final FooterLine footerLine : footerLines) {
         try {
-          final CreateChangeSender cm;
-          cm = createChangeSenderFactory.create(change);
-          cm.setFrom(me);
-          cm.setPatchSet(ps, info);
-          cm.addReviewers(reviewers);
-          cm.addExtraCC(cc);
-          cm.send();
-        } catch (Exception e) {
-          log.error("Cannot send email for new change " + change.getId(), e);
+          if (isReviewer(footerLine)) {
+            reviewers.add(toAccountId(footerLine.getValue().trim()));
+          } else if (footerLine.matches(FooterKey.CC)) {
+            cc.add(toAccountId(footerLine.getValue().trim()));
+          }
+        } catch (NoSuchAccountException e) {
+          continue;
         }
       }
+      reviewers.remove(me);
+      cc.remove(me);
+      cc.removeAll(reviewers);
 
-      @Override
-      public String toString() {
-        return "send-email newchange";
+      db.changes().beginTransaction(change.getId());
+      try {
+        insertAncestors(db, ps.getId(), commit);
+        db.patchSets().insert(Collections.singleton(ps));
+        db.changes().insert(Collections.singleton(change));
+        ChangeUtil.updateTrackingIds(db, change, trackingFooters, footerLines);
+        approvalsUtil.addReviewers(db, change, ps, info,
+            reviewers, Collections.<Account.Id> emptySet());
+        db.commit();
+      } finally {
+        db.rollback();
       }
-    }));
 
-    hooks.doPatchsetCreatedHook(change, ps, db);
+      created = true;
+      replication.fire(project.getNameKey(), ps.getRefName());
+      hooks.doPatchsetCreatedHook(change, ps, db);
+      workQueue.getDefaultQueue()
+          .submit(requestScopePropagator.wrap(new Runnable() {
+        @Override
+        public void run() {
+          try {
+            CreateChangeSender cm =
+                createChangeSenderFactory.create(change);
+            cm.setFrom(me);
+            cm.setPatchSet(ps, info);
+            cm.addReviewers(reviewers);
+            cm.addExtraCC(cc);
+            cm.send();
+          } catch (Exception e) {
+            log.error("Cannot send email for new change " + change.getId(), e);
+          }
+        }
+
+        @Override
+        public String toString() {
+          return "send-email newchange";
+        }
+      }));
+    }
   }
 
   private static boolean isReviewer(final FooterLine candidateFooterLine) {
@@ -1204,303 +1379,411 @@
         || candidateFooterLine.matches(TESTED_BY);
   }
 
-  private void doReplaces() {
-    for (final ReplaceRequest request : replaceByChange.values()) {
-      try {
-        doReplace(request, false);
-        replaceProgress.update(1);
-      } catch (IOException err) {
-        log.error("Error computing replacement patch for change "
-            + request.ontoChange + ", commit " + request.newCommit.name(), err);
-        reject(request.cmd, "diff error");
-      } catch (OrmException err) {
-        log.error("Error storing replacement patch for change "
-            + request.ontoChange + ", commit " + request.newCommit.name(), err);
-        reject(request.cmd, "database error");
+  private void preparePatchSetsForReplace() {
+    try {
+      readChangesForReplace();
+      readPatchSetsForReplace();
+
+      for (ReplaceRequest req : replaceByChange.values()) {
+        if (req.inputCommand.getResult() == NOT_ATTEMPTED) {
+          req.validate(false);
+        }
       }
-      if (request.cmd.getResult() == NOT_ATTEMPTED) {
-        log.error("Replacement patch for change " + request.ontoChange
-            + ", commit " + request.newCommit.name() + " wasn't attempted."
-            + "  This is a bug in the receive process implementation.");
-        reject(request.cmd, "internal error");
+    } catch (OrmException err) {
+      log.error("Cannot read database before replacement", err);
+      for (ReplaceRequest req : replaceByChange.values()) {
+        if (req.inputCommand.getResult() == NOT_ATTEMPTED) {
+          req.inputCommand.setResult(REJECTED_OTHER_REASON, "internal server error");
+        }
+      }
+    } catch (IOException err) {
+      log.error("Cannot read repository before replacement", err);
+      for (ReplaceRequest req : replaceByChange.values()) {
+        if (req.inputCommand.getResult() == NOT_ATTEMPTED) {
+          req.inputCommand.setResult(REJECTED_OTHER_REASON, "internal server error");
+        }
+      }
+    }
+
+    for (ReplaceRequest req : replaceByChange.values()) {
+      if (req.inputCommand.getResult() == NOT_ATTEMPTED && req.cmd != null) {
+        batch.addCommand(req.cmd);
+      }
+    }
+
+    if (newChange != null && newChange.getResult() != NOT_ATTEMPTED) {
+      // Cancel creations tied to refs/for/ or refs/drafts/ command.
+      for (ReplaceRequest req : replaceByChange.values()) {
+        if (req.inputCommand == newChange && req.cmd != null) {
+          req.cmd.setResult(Result.REJECTED_OTHER_REASON, "aborted");
+        }
+      }
+      for (CreateRequest req : newChanges) {
+        req.cmd.setResult(Result.REJECTED_OTHER_REASON, "aborted");
       }
     }
   }
 
-  private PatchSet.Id doReplace(final ReplaceRequest request, boolean ignoreNoChanges)
-      throws IOException, OrmException {
-    final RevCommit c = request.newCommit;
-    rp.getRevWalk().parseBody(c);
-    warnMalformedMessage(c);
-
-    final Account.Id me = currentUser.getAccountId();
-    final Set<Account.Id> reviewers = new HashSet<Account.Id>(reviewerId);
-    final Set<Account.Id> cc = new HashSet<Account.Id>(ccId);
-    final List<FooterLine> footerLines = c.getFooterLines();
-    for (final FooterLine footerLine : footerLines) {
-      try {
-        if (isReviewer(footerLine)) {
-          reviewers.add(toAccountId(footerLine.getValue().trim()));
-        } else if (footerLine.matches(FooterKey.CC)) {
-          cc.add(toAccountId(footerLine.getValue().trim()));
-        }
-      } catch (NoSuchAccountException e) {
-        continue;
+  private void readChangesForReplace() throws OrmException {
+    List<CheckedFuture<Change, OrmException>> futures =
+        Lists.newArrayListWithCapacity(replaceByChange.size());
+    for (ReplaceRequest request : replaceByChange.values()) {
+      futures.add(db.changes().getAsync(request.ontoChange));
+    }
+    for (CheckedFuture<Change, OrmException> f : futures) {
+      Change c = f.checkedGet();
+      if (c != null) {
+        replaceByChange.get(c.getId()).change = c;
       }
     }
-    reviewers.remove(me);
-    cc.remove(me);
-    cc.removeAll(reviewers);
+  }
 
-    final ReplaceResult result = new ReplaceResult();
-    final Set<Account.Id> oldReviewers = new HashSet<Account.Id>();
-    final Set<Account.Id> oldCC = new HashSet<Account.Id>();
-
-    Change change = db.changes().get(request.ontoChange);
-    if (change == null) {
-      reject(request.cmd, "change " + request.ontoChange + " not found");
-      return null;
+  private void readPatchSetsForReplace() throws OrmException {
+    Map<Change.Id, ResultSet<PatchSet>> results = Maps.newHashMap();
+    for (ReplaceRequest request : replaceByChange.values()) {
+      Change.Id id = request.ontoChange;
+      results.put(id, db.patchSets().byChange(id));
     }
-    if (change.getStatus().isClosed()) {
-      reject(request.cmd, "change " + request.ontoChange + " closed");
-      return null;
+    for (ReplaceRequest req : replaceByChange.values()) {
+      req.patchSets = results.get(req.ontoChange).toList();
+    }
+  }
+
+  private class ReplaceRequest {
+    final Change.Id ontoChange;
+    final RevCommit newCommit;
+    final ReceiveCommand inputCommand;
+    final boolean checkMergedInto;
+    Change change;
+    ChangeControl changeCtl;
+    List<PatchSet> patchSets;
+    PatchSet newPatchSet;
+    ReceiveCommand cmd;
+    PatchSetInfo info;
+    ChangeMessage msg;
+    String mergedIntoRef;
+    private PatchSet.Id priorPatchSet;
+
+    ReplaceRequest(final Change.Id toChange, final RevCommit newCommit,
+        final ReceiveCommand cmd, final boolean checkMergedInto) {
+      this.ontoChange = toChange;
+      this.newCommit = newCommit;
+      this.inputCommand = cmd;
+      this.checkMergedInto = checkMergedInto;
     }
 
-    final ChangeControl changeCtl = projectControl.controlFor(change);
-    if (!changeCtl.canAddPatchSet()) {
-      reject(request.cmd, "cannot replace " + request.ontoChange);
-      return null;
-    }
-    if (!validCommit(changeCtl.getRefControl(), request.cmd, c)) {
-      return null;
-    }
-
-    final PatchSet.Id priorPatchSet = change.currentPatchSetId();
-    for (final PatchSet ps : db.patchSets().byChange(request.ontoChange)) {
-      if (ps.getRevision() == null) {
-        log.warn("Patch set " + ps.getId() + " has no revision");
-        reject(request.cmd, "change state corrupt");
-        return null;
+    boolean validate(boolean ignoreNoChanges) throws IOException {
+      if (inputCommand.getResult() != NOT_ATTEMPTED) {
+        return false;
       }
 
-      final String revIdStr = ps.getRevision().get();
-      final ObjectId commitId;
-      try {
-        commitId = ObjectId.fromString(revIdStr);
-      } catch (IllegalArgumentException e) {
-        log.warn("Invalid revision in " + ps.getId() + ": " + revIdStr);
-        reject(request.cmd, "change state corrupt");
-        return null;
+      if (change == null || patchSets == null) {
+        reject(inputCommand, "change " + ontoChange + " not found");
+        return false;
       }
 
-      try {
-        final RevCommit prior = rp.getRevWalk().parseCommit(commitId);
+      if (change.getStatus().isClosed()) {
+        reject(inputCommand, "change " + ontoChange + " closed");
+        return false;
+      }
 
-        // Don't allow a change to directly depend upon itself. This is a
-        // very common error due to users making a new commit rather than
-        // amending when trying to address review comments.
-        //
-        if (rp.getRevWalk().isMergedInto(prior, c)) {
-          reject(request.cmd, "squash commits first");
-          return null;
+      changeCtl = projectControl.controlFor(change);
+      if (!changeCtl.canAddPatchSet()) {
+        reject(inputCommand, "cannot replace " + ontoChange);
+        return false;
+      }
+
+      rp.getRevWalk().parseBody(newCommit);
+      if (!validCommit(changeCtl.getRefControl(), inputCommand, newCommit)) {
+        return false;
+      }
+
+      priorPatchSet = change.currentPatchSetId();
+      for (final PatchSet ps : patchSets) {
+        if (ps.getRevision() == null) {
+          log.warn("Patch set " + ps.getId() + " has no revision");
+          reject(inputCommand, "change state corrupt");
+          return false;
         }
 
-        // Don't allow the same commit to appear twice on the same change
-        //
-        if (c == prior) {
-          reject(request.cmd, "commit already exists");
-          return null;
+        final String revIdStr = ps.getRevision().get();
+        final ObjectId commitId;
+        try {
+          commitId = ObjectId.fromString(revIdStr);
+        } catch (IllegalArgumentException e) {
+          log.warn("Invalid revision in " + ps.getId() + ": " + revIdStr);
+          reject(inputCommand, "change state corrupt");
+          return false;
         }
 
-        // Don't allow the same tree if the commit message is unmodified
-        // or no parents were updated (rebase), else warn that only part
-        // of the commit was modified.
-        //
-        if (priorPatchSet.equals(ps.getId()) && c.getTree() == prior.getTree()) {
-          rp.getRevWalk().parseBody(prior);
-          final boolean messageEq =
-              eq(c.getFullMessage(), prior.getFullMessage());
-          final boolean parentsEq = parentsEqual(c, prior);
-          final boolean authorEq = authorEqual(c, prior);
+        try {
+          final RevCommit prior = rp.getRevWalk().parseCommit(commitId);
 
-          if (messageEq && parentsEq && authorEq && !ignoreNoChanges) {
-            reject(request.cmd, "no changes made");
-            return null;
-          } else {
-            ObjectReader reader = rp.getRevWalk().getObjectReader();
-            StringBuilder msg = new StringBuilder();
-            msg.append("(W) ");
-            msg.append(reader.abbreviate(c).name());
-            msg.append(":");
-            msg.append(" no files changed");
-            if (!authorEq) {
-              msg.append(", author changed");
-            }
-            if (!messageEq) {
-              msg.append(", message updated");
-            }
-            if (!parentsEq) {
-              msg.append(", was rebased");
-            }
-            addMessage(msg.toString());
+          // Don't allow a change to directly depend upon itself. This is a
+          // very common error due to users making a new commit rather than
+          // amending when trying to address review comments.
+          //
+          if (rp.getRevWalk().isMergedInto(prior, newCommit)) {
+            reject(inputCommand, "squash commits first");
+            return false;
           }
-        }
-      } catch (IOException e) {
-        log.error("Change " + change.getId() + " missing " + revIdStr, e);
-        reject(request.cmd, "change state corrupt");
-        return null;
-      }
-    }
 
-    final PatchSet ps;
-    final ChangeMessage msg;
-    db.changes().beginTransaction(change.getId());
-    try {
-      change =
-        db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
-          @Override
-          public Change update(Change change) {
-            if (change.getStatus().isOpen()) {
-              change.nextPatchSetId();
-              change.setLastSha1MergeTested(null);
-              return change;
+          // Don't allow the same commit to appear twice on the same change
+          //
+          if (newCommit == prior) {
+            reject(inputCommand, "commit already exists");
+            return false;
+          }
+
+          // Don't allow the same tree if the commit message is unmodified
+          // or no parents were updated (rebase), else warn that only part
+          // of the commit was modified.
+          //
+          if (priorPatchSet.equals(ps.getId()) && newCommit.getTree() == prior.getTree()) {
+            rp.getRevWalk().parseBody(prior);
+            final boolean messageEq =
+                eq(newCommit.getFullMessage(), prior.getFullMessage());
+            final boolean parentsEq = parentsEqual(newCommit, prior);
+            final boolean authorEq = authorEqual(newCommit, prior);
+
+            if (messageEq && parentsEq && authorEq && !ignoreNoChanges) {
+              reject(inputCommand, "no changes made");
+              return false;
             } else {
-              return null;
+              ObjectReader reader = rp.getRevWalk().getObjectReader();
+              StringBuilder msg = new StringBuilder();
+              msg.append("(W) ");
+              msg.append(reader.abbreviate(newCommit).name());
+              msg.append(":");
+              msg.append(" no files changed");
+              if (!authorEq) {
+                msg.append(", author changed");
+              }
+              if (!messageEq) {
+                msg.append(", message updated");
+              }
+              if (!parentsEq) {
+                msg.append(", was rebased");
+              }
+              addMessage(msg.toString());
             }
           }
-        });
-      if (change == null) {
-        reject(request.cmd, "change is closed");
-        return null;
-      }
-
-      ps = new PatchSet(change.currPatchSetId());
-      ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
-      ps.setUploader(currentUser.getAccountId());
-      ps.setRevision(toRevId(c));
-      if (MagicBranch.isDraft(request.cmd.getRefName())) {
-        ps.setDraft(true);
-      }
-      insertAncestors(ps.getId(), c);
-      db.patchSets().insert(Collections.singleton(ps));
-
-      if (request.checkMergedInto) {
-        final Ref mergedInto = findMergedInto(change.getDest().get(), c);
-        result.mergedIntoRef = mergedInto != null ? mergedInto.getName() : null;
-      }
-      final PatchSetInfo info = patchSetInfoFactory.get(c, ps.getId());
-      change.setCurrentPatchSet(info);
-      result.change = change;
-      result.patchSet = ps;
-      result.info = info;
-
-      List<PatchSetApproval> patchSetApprovals = approvalsUtil.copyVetosToLatestPatchSet(change);
-
-      final Set<Account.Id> haveApprovals = new HashSet<Account.Id>();
-      oldReviewers.clear();
-      oldCC.clear();
-
-      for (PatchSetApproval a : patchSetApprovals) {
-        haveApprovals.add(a.getAccountId());
-        if (a.getValue() != 0) {
-          oldReviewers.add(a.getAccountId());
-        } else {
-          oldCC.add(a.getAccountId());
+        } catch (IOException e) {
+          log.error("Change " + change.getId() + " missing " + revIdStr, e);
+          reject(inputCommand, "change state corrupt");
+          return false;
         }
       }
 
-      approvalsUtil.addReviewers(change, ps, info, reviewers, haveApprovals);
+      change.nextPatchSetId();
+      newPatchSet = new PatchSet(change.currPatchSetId());
+      newPatchSet.setCreatedOn(new Timestamp(System.currentTimeMillis()));
+      newPatchSet.setUploader(currentUser.getAccountId());
+      newPatchSet.setRevision(toRevId(newCommit));
+      if (newChange != null && MagicBranch.isDraft(newChange.getRefName())) {
+        newPatchSet.setDraft(true);
+      }
+      info = patchSetInfoFactory.get(newCommit, newPatchSet.getId());
+      cmd = new ReceiveCommand(
+          ObjectId.zeroId(),
+          newCommit,
+          newPatchSet.getRefName());
+      return true;
+    }
 
-      msg =
-          new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
-              .messageUUID(db)), me, ps.getCreatedOn(), ps.getId());
-      msg.setMessage("Uploaded patch set " + ps.getPatchSetId() + ".");
-      db.changeMessages().insert(Collections.singleton(msg));
-      ChangeUtil.updateTrackingIds(db, change, trackingFooters, footerLines);
-      result.msg = msg;
+    CheckedFuture<PatchSet.Id, OrmException> insertPatchSet()
+        throws IOException {
+      rp.getRevWalk().parseBody(newCommit);
+      warnMalformedMessage(newCommit);
 
-      if (result.mergedIntoRef == null) {
-        // Change should be new, so it can go through review again.
-        //
+      final Thread caller = Thread.currentThread();
+      ListenableFuture<PatchSet.Id> future = changeUpdateExector.submit(
+          requestScopePropagator.wrap(new Callable<PatchSet.Id>() {
+        @Override
+        public PatchSet.Id call() throws OrmException {
+          try {
+            if (caller == Thread.currentThread()) {
+              return insertPatchSet(db);
+            } else {
+              ReviewDb db = schemaFactory.open();
+              try {
+                return insertPatchSet(db);
+              } finally {
+                db.close();
+              }
+            }
+          } finally {
+            synchronized (newProgress) {
+              replaceProgress.update(1);
+            }
+          }
+        }
+      }));
+      return Futures.makeChecked(future, ORM_EXCEPTION);
+    }
+
+    PatchSet.Id insertPatchSet(ReviewDb db) throws OrmException {
+      final Account.Id me = currentUser.getAccountId();
+      final Set<Account.Id> reviewers = new HashSet<Account.Id>(reviewerId);
+      final Set<Account.Id> cc = new HashSet<Account.Id>(ccId);
+      final List<FooterLine> footerLines = newCommit.getFooterLines();
+      for (final FooterLine footerLine : footerLines) {
+        try {
+          if (isReviewer(footerLine)) {
+            reviewers.add(toAccountId(footerLine.getValue().trim()));
+          } else if (footerLine.matches(FooterKey.CC)) {
+            cc.add(toAccountId(footerLine.getValue().trim()));
+          }
+        } catch (NoSuchAccountException e) {
+          continue;
+        }
+      }
+      reviewers.remove(me);
+      cc.remove(me);
+      cc.removeAll(reviewers);
+
+      final Set<Account.Id> oldReviewers = new HashSet<Account.Id>();
+      final Set<Account.Id> oldCC = new HashSet<Account.Id>();
+
+      db.changes().beginTransaction(change.getId());
+      try {
         change =
-            db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
-              @Override
-              public Change update(Change change) {
-                if (change.getStatus().isOpen()) {
+          db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+            @Override
+            public Change update(Change change) {
+              if (change.getStatus().isClosed()) {
+                return null;
+              }
+
+              change.updateNumberOfPatchSets(newPatchSet.getPatchSetId());
+              return change;
+            }
+          });
+        if (change == null) {
+          reject(inputCommand, "change is closed");
+          return null;
+        }
+
+        insertAncestors(db, newPatchSet.getId(), newCommit);
+        db.patchSets().insert(Collections.singleton(newPatchSet));
+
+        if (checkMergedInto) {
+          final Ref mergedInto = findMergedInto(change.getDest().get(), newCommit);
+          mergedIntoRef = mergedInto != null ? mergedInto.getName() : null;
+        }
+
+        List<PatchSetApproval> patchSetApprovals =
+            approvalsUtil.copyVetosToLatestPatchSet(db, change);
+
+        final Set<Account.Id> haveApprovals = new HashSet<Account.Id>();
+        oldReviewers.clear();
+        oldCC.clear();
+
+        for (PatchSetApproval a : patchSetApprovals) {
+          haveApprovals.add(a.getAccountId());
+          if (a.getValue() != 0) {
+            oldReviewers.add(a.getAccountId());
+          } else {
+            oldCC.add(a.getAccountId());
+          }
+        }
+
+        approvalsUtil.addReviewers(db, change, newPatchSet, info,
+            reviewers, haveApprovals);
+
+        msg =
+            new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
+                .messageUUID(db)), me, newPatchSet.getCreatedOn(), newPatchSet.getId());
+        msg.setMessage("Uploaded patch set " + newPatchSet.getPatchSetId() + ".");
+        db.changeMessages().insert(Collections.singleton(msg));
+        if (change.currentPatchSetId().equals(priorPatchSet)) {
+          ChangeUtil.updateTrackingIds(db, change, trackingFooters, footerLines);
+        }
+
+        if (mergedIntoRef == null) {
+          // Change should be new, so it can go through review again.
+          //
+          change =
+              db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+                @Override
+                public Change update(Change change) {
+                  if (change.getStatus().isClosed()) {
+                    return null;
+                  }
+
+                  if (!change.currentPatchSetId().equals(priorPatchSet)) {
+                    return change;
+                  }
+
                   if (destTopicName != null) {
                     change.setTopic(destTopicName);
                   }
-                  if (change.getStatus() == Change.Status.DRAFT && ps.isDraft()) {
+                  if (change.getStatus() == Change.Status.DRAFT && newPatchSet.isDraft()) {
                     // Leave in draft status.
                   } else {
                     change.setStatus(Change.Status.NEW);
                   }
-                  change.setCurrentPatchSet(result.info);
+                  change.setLastSha1MergeTested(null);
+                  change.setCurrentPatchSet(info);
                   ChangeUtil.updated(change);
                   return change;
-                } else {
-                  return null;
                 }
-              }
-            });
-        if (change == null) {
-          db.patchSets().delete(Collections.singleton(ps));
-          db.changeMessages().delete(Collections.singleton(msg));
-          reject(request.cmd, "change is closed");
-          return null;
+              });
+          if (change == null) {
+            db.patchSets().delete(Collections.singleton(newPatchSet));
+            db.changeMessages().delete(Collections.singleton(msg));
+            reject(inputCommand, "change is closed");
+            return null;
+          }
         }
+
+        db.commit();
+      } finally {
+        db.rollback();
       }
 
-      db.commit();
-    } finally {
-      db.rollback();
-    }
+      if (mergedIntoRef != null) {
+        // Change was already submitted to a branch, close it.
+        //
+        markChangeMergedByPush(db, this);
+      }
 
-    if (result.mergedIntoRef != null) {
-      // Change was already submitted to a branch, close it.
-      //
-      markChangeMergedByPush(db, result);
-    }
-
-    final RefUpdate ru = repo.updateRef(ps.getRefName());
-    ru.setNewObjectId(c);
-    ru.disableRefLog();
-    if (ru.update(rp.getRevWalk()) != RefUpdate.Result.NEW) {
-      throw new IOException("Failed to create ref " + ps.getRefName() + " in "
-          + repo.getDirectory() + ": " + ru.getResult());
-    }
-    replication.fire(project.getNameKey(), ru.getName());
-    hooks.doPatchsetCreatedHook(result.change, ps, db);
-    request.cmd.setResult(OK);
-
-    workQueue.getDefaultQueue()
-        .submit(requestScopePropagator.wrap(new Runnable() {
-      @Override
-      public void run() {
-        try {
-          final ReplacePatchSetSender cm;
-          cm = replacePatchSetFactory.create(result.change);
-          cm.setFrom(me);
-          cm.setPatchSet(ps, result.info);
-          cm.setChangeMessage(result.msg);
-          cm.addReviewers(reviewers);
-          cm.addExtraCC(cc);
-          cm.addReviewers(oldReviewers);
-          cm.addExtraCC(oldCC);
-          cm.send();
-        } catch (Exception e) {
-          log.error("Cannot send email for new patch set " + ps.getId(), e);
+      replication.fire(project.getNameKey(), newPatchSet.getRefName());
+      hooks.doPatchsetCreatedHook(change, newPatchSet, db);
+      if (mergedIntoRef != null) {
+        hooks.doChangeMergedHook(
+            change, currentUser.getAccount(), newPatchSet, db);
+      }
+      workQueue.getDefaultQueue()
+          .submit(requestScopePropagator.wrap(new Runnable() {
+        @Override
+        public void run() {
+          try {
+            ReplacePatchSetSender cm =
+                replacePatchSetFactory.create(change);
+            cm.setFrom(me);
+            cm.setPatchSet(newPatchSet, info);
+            cm.setChangeMessage(msg);
+            cm.addReviewers(reviewers);
+            cm.addExtraCC(cc);
+            cm.addReviewers(oldReviewers);
+            cm.addExtraCC(oldCC);
+            cm.send();
+          } catch (Exception e) {
+            log.error("Cannot send email for new patch set " + newPatchSet.getId(), e);
+          }
+          if (mergedIntoRef != null) {
+            sendMergedEmail(ReplaceRequest.this);
+          }
         }
-      }
 
-      @Override
-      public String toString() {
-        return "send-email newpatchset";
-      }
-    }));
-
-    sendMergedEmail(result);
-    return result != null ? result.info.getKey() : null;
+        @Override
+        public String toString() {
+          return "send-email newpatchset";
+        }
+      }));
+      return newPatchSet.getId();
+    }
   }
 
   static boolean parentsEqual(RevCommit a, RevCommit b) {
@@ -1566,30 +1849,19 @@
     return rw.isMergedInto(commit, rw.parseCommit(ref.getObjectId()));
   }
 
-  private static class ReplaceRequest {
-    final Change.Id ontoChange;
-    final RevCommit newCommit;
-    final ReceiveCommand cmd;
-    final boolean checkMergedInto;
-
-    ReplaceRequest(final Change.Id toChange, final RevCommit newCommit,
-        final ReceiveCommand cmd, final boolean checkMergedInto) {
-      this.ontoChange = toChange;
-      this.newCommit = newCommit;
-      this.cmd = cmd;
-      this.checkMergedInto = checkMergedInto;
-    }
-  }
-
-  private static class ReplaceResult {
-    Change change;
-    PatchSet patchSet;
-    PatchSetInfo info;
-    ChangeMessage msg;
-    String mergedIntoRef;
-  }
-
   private void validateNewCommits(RefControl ctl, ReceiveCommand cmd) {
+    if (ctl.canForgeAuthor()
+        && ctl.canForgeCommitter()
+        && ctl.canForgeGerritServerIdentity()
+        && ctl.canUploadMerges()
+        && !project.isUseSignedOffBy()
+        && Iterables.isEmpty(rejectCommits)
+        && !GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())
+        && !(MagicBranch.isMagicBranch(cmd.getRefName())
+            || NEW_PATCHSET.matcher(cmd.getRefName()).matches())) {
+      return;
+    }
+
     final RevWalk walk = rp.getRevWalk();
     walk.reset();
     walk.sort(RevSort.NONE);
@@ -1690,7 +1962,7 @@
     }
 
     final List<String> idList = c.getFooterLines(CHANGE_ID);
-    if ((MagicBranch.isMagicBranch(cmd.getRefName()) || NEW_PATCHSET.matcher(cmd.getRefName()).matches())) {
+    if (MagicBranch.isMagicBranch(cmd.getRefName()) || NEW_PATCHSET.matcher(cmd.getRefName()).matches()) {
       if (idList.isEmpty()) {
         if (project.isRequireChangeID()) {
           String errMsg = "missing Change-Id in commit message";
@@ -1862,8 +2134,12 @@
         final Ref ref = byCommit.get(c.copy());
         if (ref != null) {
           rw.parseBody(c);
-          closeChange(cmd, PatchSet.Id.fromRef(ref.getName()), c);
+          Change.Key closedChange =
+              closeChange(cmd, PatchSet.Id.fromRef(ref.getName()), c);
           closeProgress.update(1);
+          if (closedChange != null) {
+            byKey.remove(closedChange);
+          }
         }
 
         rw.parseBody(c);
@@ -1877,9 +2153,11 @@
       }
 
       for (final ReplaceRequest req : toClose) {
-        final PatchSet.Id psi = doReplace(req, true);
+        final PatchSet.Id psi = req.validate(true)
+            ? req.insertPatchSet().checkedGet()
+            : null;
         if (psi != null) {
-          closeChange(req.cmd, psi, req.newCommit);
+          closeChange(req.inputCommand, psi, req.newCommit);
           closeProgress.update(1);
         }
       }
@@ -1904,7 +2182,7 @@
     }
   }
 
-  private void closeChange(final ReceiveCommand cmd, final PatchSet.Id psi,
+  private Change.Key closeChange(final ReceiveCommand cmd, final PatchSet.Id psi,
       final RevCommit commit) throws OrmException {
     final String refName = cmd.getRefName();
     final Change.Id cid = psi.getParentKey();
@@ -1913,7 +2191,7 @@
     final PatchSet ps = db.patchSets().get(psi);
     if (change == null || ps == null) {
       log.warn(project.getName() + " " + psi + " is missing");
-      return;
+      return null;
     }
 
     if (change.getStatus() == Change.Status.MERGED ||
@@ -1922,17 +2200,19 @@
       // might just be moving from an experimental branch into
       // a more stable branch.
       //
-      return;
+      return null;
     }
 
-    final ReplaceResult result = new ReplaceResult();
+    ReplaceRequest result = new ReplaceRequest(cid, commit, cmd, false);
     result.change = change;
-    result.patchSet = ps;
+    result.newPatchSet = ps;
     result.info = patchSetInfoFactory.get(commit, psi);
     result.mergedIntoRef = refName;
-
     markChangeMergedByPush(db, result);
+    hooks.doChangeMergedHook(
+        change, currentUser.getAccount(), result.newPatchSet, db);
     sendMergedEmail(result);
+    return change.getKey();
   }
 
   private Map<ObjectId, Ref> changeRefsById() throws IOException {
@@ -1957,7 +2237,7 @@
   }
 
   private void markChangeMergedByPush(final ReviewDb db,
-      final ReplaceResult result) throws OrmException {
+      final ReplaceRequest result) throws OrmException {
     final Change change = result.change;
     final String mergedIntoRef = result.mergedIntoRef;
 
@@ -1999,39 +2279,30 @@
     });
   }
 
-  private void sendMergedEmail(final ReplaceResult result) {
-    if (result != null && result.mergedIntoRef != null) {
-      workQueue.getDefaultQueue()
-          .submit(requestScopePropagator.wrap(new Runnable() {
-        @Override
-        public void run() {
-          try {
-            final MergedSender cm = mergedSenderFactory.create(result.change);
-            cm.setFrom(currentUser.getAccountId());
-            cm.setPatchSet(result.patchSet, result.info);
-            cm.send();
-          } catch (Exception e) {
-            final PatchSet.Id psi = result.patchSet.getId();
-            log.error("Cannot send email for submitted patch set " + psi, e);
-          }
+  private void sendMergedEmail(final ReplaceRequest result) {
+    workQueue.getDefaultQueue()
+        .submit(requestScopePropagator.wrap(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          final MergedSender cm = mergedSenderFactory.create(result.change);
+          cm.setFrom(currentUser.getAccountId());
+          cm.setPatchSet(result.newPatchSet, result.info);
+          cm.send();
+        } catch (Exception e) {
+          final PatchSet.Id psi = result.newPatchSet.getId();
+          log.error("Cannot send email for submitted patch set " + psi, e);
         }
-
-        @Override
-        public String toString() {
-          return "send-email merged";
-        }
-      }));
-
-      try {
-        hooks.doChangeMergedHook(result.change, currentUser.getAccount(),
-            result.patchSet, db);
-      } catch (OrmException err) {
-        log.error("Cannot open change: " + result.change.getChangeId(), err);
       }
-    }
+
+      @Override
+      public String toString() {
+        return "send-email merged";
+      }
+    }));
   }
 
-  private void insertAncestors(PatchSet.Id id, RevCommit src)
+  private void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
       throws OrmException {
     final int cnt = src.getParentCount();
     List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java
index 063db2d..1cbd227 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsExecutorModule.java
@@ -14,15 +14,20 @@
 
 package com.google.gerrit.server.git;
 
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.git.WorkQueue.Executor;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.lib.Config;
 
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
 /** Module providing the {@link ReceiveCommitsExecutor}. */
 public class ReceiveCommitsExecutorModule extends AbstractModule {
   @Override
@@ -32,10 +37,31 @@
   @Provides
   @Singleton
   @ReceiveCommitsExecutor
-  public Executor getReceiveCommitsExecutor(@GerritServerConfig Config config,
+  public WorkQueue.Executor createReceiveCommitsExecutor(
+      @GerritServerConfig Config config,
       WorkQueue queues) {
     int poolSize = config.getInt("receive", null, "threadPoolSize",
         Runtime.getRuntime().availableProcessors());
     return queues.createQueue(poolSize, "ReceiveCommits");
   }
+
+  @Provides
+  @Singleton
+  @ChangeUpdateExecutor
+  public ListeningExecutorService createChangeUpdateExecutor(@GerritServerConfig Config config) {
+    int poolSize = config.getInt("receive", null, "changeUpdateThreads", 1);
+    if (poolSize <= 1) {
+      return MoreExecutors.sameThreadExecutor();
+    }
+    return MoreExecutors.listeningDecorator(
+        MoreExecutors.getExitingExecutorService(
+          new ThreadPoolExecutor(1, poolSize,
+              10, TimeUnit.MINUTES,
+              new ArrayBlockingQueue<Runnable>(poolSize),
+              new ThreadFactoryBuilder()
+                .setNameFormat("ChangeUpdate-%d")
+                .setDaemon(true)
+                .build(),
+              new ThreadPoolExecutor.CallerRunsPolicy())));
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index 44536e2..e9c5536 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -254,6 +254,8 @@
         RefUpdate ru = db.updateRef(refName);
         ru.setExpectedOldObjectId(ObjectId.zeroId());
         ru.setNewObjectId(src);
+        ru.disableRefLog();
+        inserter.flush();
         RefUpdate.Result result = ru.update();
         switch (result) {
           case NEW:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index 76cee02..d1c2aa4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -148,6 +148,11 @@
     }
   }
 
+  public static File storeInTemp(String pluginName, InputStream in,
+      SitePaths sitePaths) throws IOException {
+    return asTemp(in, tempNameFor(pluginName), ".jar", sitePaths.tmp_dir);
+  }
+
   private static File asTemp(InputStream in,
       String prefix, String suffix,
       File dir) throws IOException {
@@ -201,6 +206,25 @@
     }
   }
 
+  public void enablePlugins(Set<String> names) throws PluginInstallException {
+    synchronized (this) {
+      for (String name : names) {
+        Plugin off = disabled.get(name);
+        if (off == null) {
+          continue;
+        }
+
+        log.info(String.format("Enabling plugin %s", name));
+        File on = new File(pluginsDir, off.getName() + ".jar");
+        off.getSrcJar().renameTo(on);
+
+        disabled.remove(name);
+        runPlugin(name, on, null);
+      }
+      cleanInBackground();
+    }
+  }
+
   @Override
   public synchronized void start() {
     log.info("Loading plugins from " + pluginsDir.getAbsolutePath());
@@ -288,8 +312,8 @@
       }
 
       try {
-        runPlugin(name, jar, active);
-        if (active == null) {
+        Plugin loadedPlugin = runPlugin(name, jar, active);
+        if (active == null && !loadedPlugin.isDisabled()) {
           log.info(String.format("Loaded plugin %s", name));
         }
       } catch (PluginInstallException e) {
@@ -300,7 +324,7 @@
     cleanInBackground();
   }
 
-  private void runPlugin(String name, File jar, Plugin oldPlugin)
+  private Plugin runPlugin(String name, File jar, Plugin oldPlugin)
       throws PluginInstallException {
     FileSnapshot snapshot = FileSnapshot.save(jar);
     try {
@@ -327,6 +351,7 @@
         disabled.put(name, newPlugin);
       }
       broken.remove(name);
+      return newPlugin;
     } catch (Throwable err) {
       broken.put(name, snapshot);
       throw new PluginInstallException(err);
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 ddc4c28..e80ad67 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
@@ -71,6 +71,9 @@
   private static final Pattern PAT_LABEL =
       Pattern.compile("^[a-zA-Z][a-zA-Z0-9]*((=|>=|<=)[+-]?|[+-])\\d+$");
 
+  // NOTE: As new search operations are added, please keep the
+  // SearchSuggestOracle up to date.
+
   public static final String FIELD_AGE = "age";
   public static final String FIELD_BRANCH = "branch";
   public static final String FIELD_CHANGE = "change";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
index d6a9d4f..f0ed66a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
@@ -14,31 +14,60 @@
 
 package com.google.gerrit.server.query.change;
 
+import static com.google.gerrit.common.changes.ListChangesOption.ALL_COMMITS;
+import static com.google.gerrit.common.changes.ListChangesOption.ALL_FILES;
+import static com.google.gerrit.common.changes.ListChangesOption.ALL_REVISIONS;
+import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_COMMIT;
+import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_FILES;
+import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_REVISION;
+import static com.google.gerrit.common.changes.ListChangesOption.LABELS;
+
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.gerrit.common.changes.ListChangesOption;
 import com.google.gerrit.common.data.ApprovalType;
 import com.google.gerrit.common.data.ApprovalTypes;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.client.PatchSetInfo.ParentInfo;
+import com.google.gerrit.reviewdb.client.UserIdentity;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.events.AccountAttribute;
+import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
+import com.jcraft.jsch.HostKey;
+
+import org.eclipse.jgit.lib.Config;
 import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.Writer;
@@ -46,17 +75,47 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 
 public class ListChanges {
+  private static final Logger log = LoggerFactory.getLogger(ListChanges.class);
+
+  @Singleton
+  static class Urls {
+    final String git;
+    final String http;
+
+    @Inject
+    Urls(@GerritServerConfig Config cfg) {
+      this.git = ensureSlash(cfg.getString("gerrit", null, "canonicalGitUrl"));
+      this.http = ensureSlash(cfg.getString("gerrit", null, "gitHttpUrl"));
+    }
+
+    private static String ensureSlash(String in) {
+      if (in != null && !in.endsWith("/")) {
+        return in + "/";
+      }
+      return in;
+    }
+  }
+
   private final QueryProcessor imp;
   private final Provider<ReviewDb> db;
   private final ApprovalTypes approvalTypes;
   private final CurrentUser user;
+  private final AnonymousUser anonymous;
   private final ChangeControl.Factory changeControlFactory;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+  private final PatchListCache patchListCache;
+  private final SshInfo sshInfo;
+  private final Provider<String> urlProvider;
+  private final Urls urls;
   private boolean reverse;
   private Map<Account.Id, AccountAttribute> accounts;
+  private Map<Change.Id, ChangeControl> controls;
+  private EnumSet<ListChangesOption> options;
 
   @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
   private OutputFormat format = OutputFormat.TEXT;
@@ -65,12 +124,22 @@
   private List<String> queries;
 
   @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "Maximum number of results to return")
-  void setLimit(int limit) {
+  public void setLimit(int limit) {
     imp.setLimit(limit);
   }
 
+  @Option(name = "-o", multiValued = true, usage = "Output options per change")
+  public void addOption(ListChangesOption o) {
+    options.add(o);
+  }
+
+  @Option(name = "-O", usage = "Output option flags, in hex")
+  void setOptionFlagsHex(String hex) {
+    options.addAll(ListChangesOption.fromBits(Integer.parseInt(hex, 16)));
+  }
+
   @Option(name = "-P", metaVar = "SORTKEY", usage = "Previous changes before SORTKEY")
-  void setSortKeyAfter(String key) {
+  public void setSortKeyAfter(String key) {
     // Querying for the prior page of changes requires sortkey_after predicate.
     // Changes are shown most recent->least recent. The previous page of
     // results contains changes that were updated after the given key.
@@ -79,7 +148,7 @@
   }
 
   @Option(name = "-N", metaVar = "SORTKEY", usage = "Next changes after SORTKEY")
-  void setSortKeyBefore(String key) {
+  public void setSortKeyBefore(String key) {
     // Querying for the next page of changes requires sortkey_before predicate.
     // Changes are shown most recent->least recent. The next page contains
     // changes that were updated before the given key.
@@ -91,14 +160,28 @@
       Provider<ReviewDb> db,
       ApprovalTypes at,
       CurrentUser u,
-      ChangeControl.Factory cf) {
+      AnonymousUser au,
+      ChangeControl.Factory cf,
+      PatchSetInfoFactory psi,
+      PatchListCache plc,
+      SshInfo sshInfo,
+      @CanonicalWebUrl Provider<String> curl,
+      Urls urls) {
     this.imp = qp;
     this.db = db;
     this.approvalTypes = at;
     this.user = u;
+    this.anonymous = au;
     this.changeControlFactory = cf;
+    this.patchSetInfoFactory = psi;
+    this.patchListCache = plc;
+    this.sshInfo = sshInfo;
+    this.urlProvider = curl;
+    this.urls = urls;
 
     accounts = Maps.newHashMap();
+    controls = Maps.newHashMap();
+    options = EnumSet.noneOf(ListChangesOption.class);
   }
 
   public OutputFormat getFormat() {
@@ -110,6 +193,14 @@
     return this;
   }
 
+  public ListChanges addQuery(String query) {
+    if (queries == null) {
+      queries = Lists.newArrayList();
+    }
+    queries.add(query);
+    return this;
+  }
+
   public void query(Writer out)
       throws OrmException, QueryParseException, IOException {
     if (imp.isDisabled()) {
@@ -204,7 +295,18 @@
     out._sortkey = in.getSortKey();
     out.starred = user.getStarredChanges().contains(in.getId()) ? true : null;
     out.reviewed = in.getStatus().isOpen() && isChangeReviewed(cd) ? true : null;
-    out.labels = labelsFor(cd);
+    out.labels = options.contains(LABELS) ? labelsFor(cd) : null;
+
+    if (options.contains(ALL_REVISIONS) || options.contains(CURRENT_REVISION)) {
+      out.revisions = revisions(cd);
+      for (String commit : out.revisions.keySet()) {
+        if (out.revisions.get(commit).isCurrent) {
+          out.current_revision = commit;
+          break;
+        }
+      }
+    }
+
     return out;
   }
 
@@ -220,18 +322,37 @@
     return a;
   }
 
+  private ChangeControl control(ChangeData cd) throws OrmException {
+    ChangeControl ctrl = cd.changeControl();
+    if (ctrl != null && ctrl.getCurrentUser() == user) {
+      return ctrl;
+    }
+
+    ctrl = controls.get(cd.getId());
+    if (ctrl != null) {
+      return ctrl;
+    }
+
+    try {
+      ctrl = changeControlFactory.controlFor(cd.change(db));
+    } catch (NoSuchChangeException e) {
+      return null;
+    }
+    controls.put(cd.getId(), ctrl);
+    return ctrl;
+  }
+
   private Map<String, LabelInfo> labelsFor(ChangeData cd) throws OrmException {
-    Change in = cd.change(db);
-    ChangeControl ctl = cd.changeControl();
-    if (ctl == null || ctl.getCurrentUser() != user) {
-      try {
-        ctl = changeControlFactory.controlFor(in);
-      } catch (NoSuchChangeException e) {
-        return null;
-      }
+    ChangeControl ctl = control(cd);
+    if (ctl == null) {
+      return Collections.emptyMap();
     }
 
     PatchSet ps = cd.currentPatchSet(db);
+    if (ps == null) {
+      return Collections.emptyMap();
+    }
+
     Map<String, LabelInfo> labels = Maps.newLinkedHashMap();
     for (SubmitRecord rec : ctl.canSubmit(db.get(), ps, cd, true, false)) {
       if (rec.labels == null) {
@@ -330,6 +451,148 @@
     return false;
   }
 
+  private Map<String, RevisionInfo> revisions(ChangeData cd) throws OrmException {
+    ChangeControl ctl = control(cd);
+    if (ctl == null) {
+      return Collections.emptyMap();
+    }
+
+    Collection<PatchSet> src;
+    if (options.contains(ALL_REVISIONS)) {
+      src = cd.patches(db);
+    } else {
+      src = Collections.singletonList(cd.currentPatchSet(db));
+    }
+    Map<String, RevisionInfo> res = Maps.newLinkedHashMap();
+    for (PatchSet in : src) {
+      if (ctl.isPatchVisible(in, db.get())) {
+        res.put(in.getRevision().get(), toRevisionInfo(cd, in));
+      }
+    }
+    return res;
+  }
+
+  private RevisionInfo toRevisionInfo(ChangeData cd, PatchSet in)
+      throws OrmException {
+    RevisionInfo out = new RevisionInfo();
+    out.isCurrent = in.getId().equals(cd.change(db).currentPatchSetId());
+    out._number = in.getId().get();
+    out.draft = in.isDraft() ? true : null;
+    out.fetch = makeFetchMap(cd, in);
+
+    if (options.contains(ALL_COMMITS)
+        || (out.isCurrent && options.contains(CURRENT_COMMIT))) {
+      try {
+        PatchSetInfo info = patchSetInfoFactory.get(db.get(), in.getId());
+        out.commit = new CommitInfo();
+        out.commit.parents = Lists.newArrayListWithCapacity(info.getParents().size());
+        out.commit.author = toGitPerson(info.getAuthor());
+        out.commit.committer = toGitPerson(info.getCommitter());
+        out.commit.subject = info.getSubject();
+        out.commit.message = info.getMessage();
+
+        for (ParentInfo parent : info.getParents()) {
+          CommitInfo i = new CommitInfo();
+          i.commit = parent.id.get();
+          i.subject = parent.shortMessage;
+          out.commit.parents.add(i);
+        }
+      } catch (PatchSetInfoNotAvailableException e) {
+        log.warn("Cannot load PatchSetInfo " + in.getId(), e);
+      }
+    }
+
+    if (options.contains(ALL_FILES)
+        || (out.isCurrent && options.contains(CURRENT_FILES))) {
+      PatchList list;
+      try {
+        list = patchListCache.get(cd.change(db), in);
+      } catch (PatchListNotAvailableException e) {
+        log.warn("Cannot load PatchList " + in.getId(), e);
+        list = null;
+      }
+      if (list != null) {
+        out.files = Maps.newTreeMap();
+        for (PatchListEntry e : list.getPatches()) {
+          if (Patch.COMMIT_MSG.equals(e.getNewName())) {
+            continue;
+          }
+
+          FileInfo d = new FileInfo();
+          d.status = e.getChangeType() != Patch.ChangeType.MODIFIED
+              ? e.getChangeType().getCode()
+              : null;
+          d.oldPath = e.getOldName();
+          if (e.getPatchType() == Patch.PatchType.BINARY) {
+            d.binary = true;
+          } else {
+            d.linesInserted = e.getInsertions() > 0 ? e.getInsertions() : null;
+            d.linesDeleted = e.getDeletions() > 0 ? e.getDeletions() : null;
+          }
+
+          FileInfo o = out.files.put(e.getNewName(), d);
+          if (o != null) {
+            // This should only happen on a delete-add break created by JGit
+            // when the file was rewritten and too little content survived. Write
+            // a single record with data from both sides.
+            d.status = Patch.ChangeType.REWRITE.getCode();
+            if (o.binary != null && o.binary) {
+              d.binary = true;
+            }
+            if (o.linesInserted != null) {
+              d.linesInserted = o.linesInserted;
+            }
+            if (o.linesDeleted != null) {
+              d.linesDeleted = o.linesDeleted;
+            }
+          }
+        }
+      }
+    }
+    return out;
+  }
+
+  private Map<String, FetchInfo> makeFetchMap(ChangeData cd, PatchSet in)
+      throws OrmException {
+    Map<String, FetchInfo> r = Maps.newLinkedHashMap();
+    String refName = in.getRefName();
+    ChangeControl ctl = control(cd);
+    if (ctl != null && ctl.forUser(anonymous).isPatchVisible(in, db.get())) {
+      if (urls.git != null) {
+        r.put("git", new FetchInfo(urls.git
+            + cd.change(db).getProject().get(), refName));
+      }
+    }
+    if (urls.http != null) {
+      r.put("http", new FetchInfo(urls.http
+          + cd.change(db).getProject().get(), refName));
+    } else {
+      String http = urlProvider.get();
+      if (!Strings.isNullOrEmpty(http)) {
+        r.put("http", new FetchInfo(http
+            + cd.change(db).getProject().get(), refName));
+      }
+    }
+    if (!sshInfo.getHostKeys().isEmpty()) {
+      HostKey host = sshInfo.getHostKeys().get(0);
+      r.put("ssh", new FetchInfo(String.format(
+          "ssh://%s/%s",
+          host.getHost(), cd.change(db).getProject().get()),
+          refName));
+    }
+
+    return r;
+  }
+
+  private static GitPerson toGitPerson(UserIdentity committer) {
+    GitPerson p = new GitPerson();
+    p.name = committer.getName();
+    p.email = committer.getEmail();
+    p.date = committer.getDate();
+    p.tz = committer.getTimeZone();
+    return p;
+  }
+
   static class ChangeInfo {
     String project;
     String branch;
@@ -347,9 +610,55 @@
 
     AccountAttribute owner;
     Map<String, LabelInfo> labels;
+    String current_revision;
+    Map<String, RevisionInfo> revisions;
+
     Boolean _moreChanges;
   }
 
+  static class RevisionInfo {
+    private transient boolean isCurrent;
+    Boolean draft;
+    int _number;
+    Map<String, FetchInfo> fetch;
+    CommitInfo commit;
+    Map<String, FileInfo> files;
+  }
+
+  static class FetchInfo {
+    String url;
+    String ref;
+
+    FetchInfo(String url, String ref) {
+      this.url = url;
+      this.ref = ref;
+    }
+  }
+
+  static class GitPerson {
+    String name;
+    String email;
+    Timestamp date;
+    int tz;
+  }
+
+  static class CommitInfo {
+    String commit;
+    List<CommitInfo> parents;
+    GitPerson author;
+    GitPerson committer;
+    String subject;
+    String message;
+  }
+
+  static class FileInfo {
+    Character status;
+    Boolean binary;
+    String oldPath;
+    Integer linesInserted;
+    Integer linesDeleted;
+  }
+
   static class LabelInfo {
     transient SubmitRecord.Label.Status _status;
     AccountAttribute approved;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 0a34b44..eeb0937 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
 /** A version of the database schema. */
 public abstract class SchemaVersion {
   /** The current schema version. */
-  public static final Class<Schema_69> C = Schema_69.class;
+  public static final Class<Schema_71> C = Schema_71.class;
 
   public static class Module extends AbstractModule {
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_70.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_70.java
new file mode 100644
index 0000000..03b33a0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_70.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.sql.DialectPostgreSQL;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_70 extends SchemaVersion {
+  @Inject
+  protected Schema_70(Provider<Schema_69> prior) {
+    super(prior);
+  }
+
+  @Override
+  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException,
+      SQLException {
+    Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+    try {
+      stmt.executeUpdate("UPDATE tracking_ids SET tracking_key = tracking_id");
+      execute(stmt, "DROP INDEX tracking_ids_byTrkId");
+      if (((JdbcSchema) db).getDialect() instanceof DialectPostgreSQL) {
+        execute(stmt, "ALTER TABLE tracking_ids DROP CONSTRAINT tracking_ids_pkey");
+      } else {
+        execute(stmt, "ALTER TABLE tracking_ids DROP PRIMARY KEY");
+      }
+      stmt.execute("ALTER TABLE tracking_ids"
+          + " ADD PRIMARY KEY (change_id, tracking_key, tracking_system)");
+      stmt.execute("CREATE INDEX tracking_ids_byTrkKey"
+          + " ON tracking_ids (tracking_key)");
+    } finally {
+      stmt.close();
+    }
+  }
+
+  private static final void execute(Statement stmt, String command) {
+    try {
+      stmt.execute(command);
+    } catch (SQLException e) {
+      // ignore
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_71.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_71.java
new file mode 100644
index 0000000..8d5b943
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_71.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+
+public class Schema_71 extends SchemaVersion {
+  @Inject
+  Schema_71(Provider<Schema_70> prior) {
+    super(prior);
+  }
+
+  @Override
+  protected void migrateData(final ReviewDb db, final UpdateUI ui)
+      throws SQLException {
+    final Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+    try {
+      stmt.executeUpdate("UPDATE account_diff_preferences SET show_line_endings='Y'");
+    }
+    finally {
+      stmt.close();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java
index e465247..7728d6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ThreadLocalRequestScopePropagator.java
@@ -45,19 +45,16 @@
     return new Callable<T>() {
       @Override
       public T call() throws Exception {
-        if (threadLocal.get() != null) {
-          // This is consistent with the Guice ServletScopes.continueRequest()
-          // behavior.
-          throw new IllegalStateException("Cannot continue request, "
-              + "thread already has request in progress. A new thread must "
-              + "be used to propagate the request scope context.");
-        }
-
+        C old = threadLocal.get();
         threadLocal.set(ctx);
         try {
           return callable.call();
         } finally {
-          threadLocal.remove();
+          if (old != null) {
+            threadLocal.set(old);
+          } else {
+            threadLocal.remove();
+          }
         }
       }
     };
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java b/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java
index c2c2d1c..045297b 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java
@@ -110,8 +110,8 @@
           continue;
         }
 
-        if (regex.matcher(newName).matches() ||
-            (oldName != null && regex.matcher(oldName).matches())) {
+        if (regex.matcher(newName).find() ||
+            (oldName != null && regex.matcher(oldName).find())) {
           SymbolTerm changeSym = getTypeSymbol(changeType);
           SymbolTerm newSym = SymbolTerm.create(newName);
           SymbolTerm oldSym = Prolog.Nil;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
index be6659d..7f08d49 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java
@@ -228,7 +228,7 @@
   }
 
   /** Split a command line into a string array. */
-  static String[] split(String commandLine) {
+  static public String[] split(String commandLine) {
     final List<String> list = new ArrayList<String>();
     boolean inquote = false;
     boolean inDblQuote = false;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
index 301d68d..691f3a0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -154,4 +154,8 @@
     usage.append("\n");
     return usage.toString();
   }
+
+  public String getCommandName() {
+    return commandName;
+  }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
index a55d715..cd6ae42 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.sshd;
 
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.audit.AuditEvent;
+import com.google.gerrit.audit.AuditService;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PeerDaemonUser;
@@ -40,6 +42,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.TimeZone;
@@ -58,12 +61,14 @@
   private final Provider<SshSession> session;
   private final Provider<Context> context;
   private final AsyncAppender async;
+  private final AuditService auditService;
 
   @Inject
   SshLog(final Provider<SshSession> session, final Provider<Context> context,
-      final SitePaths site, @GerritServerConfig Config config) {
+      final SitePaths site, @GerritServerConfig Config config, AuditService auditService) {
     this.session = session;
     this.context = context;
+    this.auditService = auditService;
 
     final DailyRollingFileAppender dst = new DailyRollingFileAppender();
     dst.setName(LOG_NAME);
@@ -96,6 +101,7 @@
 
   void onLogin() {
     async.append(log("LOGIN FROM " + session.get().getRemoteAddressAsString()));
+    audit("0", "LOGIN", new String[] {});
   }
 
   void onAuthFail(final SshSession sd) {
@@ -121,6 +127,7 @@
     }
 
     async.append(event);
+    audit("FAIL", "AUTH", new String[] {sd.getRemoteAddressAsString()});
   }
 
   void onExecute(int exitValue) {
@@ -158,10 +165,18 @@
     event.setProperty(P_STATUS, status);
 
     async.append(event);
+    audit(status, getCommand(commandLine), CommandFactoryProvider.split(commandLine));
+  }
+
+  private String getCommand(String commandLine) {
+    commandLine = commandLine.trim();
+    int spacePos = commandLine.indexOf(' ');
+    return (spacePos > 0 ? commandLine.substring(0, spacePos):commandLine);
   }
 
   void onLogout() {
     async.append(log("LOGOUT"));
+    audit("0", "LOGOUT", new String[] {});
   }
 
   private LoggingEvent log(final String msg) {
@@ -192,7 +207,6 @@
 
     } else if (user instanceof PeerDaemonUser) {
       userName = PeerDaemonUser.USER_NAME;
-
     }
 
     event.setProperty(P_USER_NAME, userName);
@@ -400,4 +414,44 @@
     public void setLogger(Logger logger) {
     }
   }
+
+  void audit(Object result, String commandName, String[] args) {
+    final Context ctx = context.get();
+    final String sid = extractSessionId(ctx);
+    final long created = extractCreated(ctx);
+    final String what = extractWhat(commandName, args);
+    auditService.dispatch(new AuditEvent(sid, extractCurrentUser(ctx), "ssh:"
+        + what, created, Arrays.asList(args), result));
+  }
+
+  private String extractWhat(String commandName, String[] args) {
+    String result = commandName;
+    if ("gerrit".equals(commandName)) {
+      if (args.length > 1)
+        result = "gerrit"+"."+args[1];
+    }
+    return result;
+  }
+
+  private long extractCreated(final Context ctx) {
+    return (ctx != null) ? ctx.created : System.currentTimeMillis();
+  }
+
+  private CurrentUser extractCurrentUser(final Context ctx) {
+    if (ctx != null) {
+      SshSession session = ctx.getSession();
+      return (session == null) ? null : session.getCurrentUser();
+    } else {
+      return null;
+    }
+  }
+
+  private String extractSessionId(final Context ctx) {
+    if (ctx != null) {
+      SshSession session = ctx.getSession();
+      return (session == null) ? null : IdGenerator.format(session.getSessionId());
+    } else {
+      return null;
+    }
+  }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 64e7289..2a4dedc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -49,6 +49,7 @@
 
     command(gerrit, "plugin").toProvider(new DispatchCommandProvider(plugin));
     command(plugin, "ls").to(PluginLsCommand.class);
+    command(plugin, "enable").to(PluginEnableCommand.class);
     command(plugin, "install").to(PluginInstallCommand.class);
     command(plugin, "reload").to(PluginReloadCommand.class);
     command(plugin, "remove").to(PluginRemoveCommand.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index aa439c6..f8856f2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.sshd.commands;
 
-import com.google.gerrit.common.data.GroupDetail;
 import com.google.gerrit.common.data.GroupList;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.reviewdb.client.Account;
@@ -26,7 +25,6 @@
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.gwtorm.client.KeyUtil;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
 import org.kohsuke.args4j.Option;
@@ -84,8 +82,7 @@
       }
 
       final ColumnFormatter formatter = new ColumnFormatter(stdout, '\t');
-      for (final GroupDetail groupDetail : groupList.getGroups()) {
-        final AccountGroup g = groupDetail.group;
+      for (final AccountGroup g : groupList.getGroups()) {
         formatter.addColumn(g.getName());
         if (verboseOutput) {
           formatter.addColumn(KeyUtil.decode(g.getGroupUUID().toString()));
@@ -102,8 +99,6 @@
         formatter.nextLine();
       }
       formatter.finish();
-    } catch (OrmException e) {
-      throw die(e);
     } catch (NoSuchGroupException e) {
       throw die(e);
     }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
new file mode 100644
index 0000000..4df3aee
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.server.plugins.PluginInstallException;
+import com.google.gerrit.server.plugins.PluginLoader;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+import org.kohsuke.args4j.Argument;
+
+import java.util.List;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+final class PluginEnableCommand extends SshCommand {
+  @Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin(s) to enable")
+  List<String> names;
+
+  @Inject
+  private PluginLoader loader;
+
+  @Override
+  protected void run() throws UnloggedFailure {
+    if (names != null && !names.isEmpty()) {
+      try {
+        loader.enablePlugins(Sets.newHashSet(names));
+      } catch (PluginInstallException e) {
+        e.printStackTrace(stderr);
+        throw die("plugin failed to enable");
+      }
+    }
+  }
+}
diff --git a/tools/gwtui_dbg.launch b/tools/gwtui_dbg.launch
index ea76ee1..f007da4 100644
--- a/tools/gwtui_dbg.launch
+++ b/tools/gwtui_dbg.launch
@@ -10,6 +10,10 @@
 <booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
 <stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"/>
 <stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;sourceLookupDirector&gt;&#10;&lt;sourceContainers duplicates=&quot;false&quot;&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-common&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-httpd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-commonsnet&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-prettify&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-jgit&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-pgm&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-server&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-sshd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-cli&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-ssl&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-reviewdb&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-gwtui&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-gwtdebug&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;default/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.debug.core.containerType.default&quot;/&gt;&#10;&lt;/sourceContainers&gt;&#10;&lt;/sourceLookupDirector&gt;&#10;"/>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
+<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
+</listAttribute>
 <booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
 <listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
@@ -32,5 +36,5 @@
 <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-startupUrl /&#10;-war ${resource_loc:/gerrit-gwtui/target}/gwt-hosted-mode&#10;-server com.google.gerrit.gwtdebug.GerritDebugLauncher&#10;com.google.gerrit.GerritGwtUI"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit-gwtdebug"/>
 <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M&#10;&#10;-Dgerrit.site_path=${resource_loc:/gerrit-parent}/../test_site"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M&#10;-da:com.google.gwtexpui.globalkey.client.KeyCommandSet&#10;&#10;-Dgerrit.site_path=${resource_loc:/gerrit-parent}/../test_site"/>
 </launchConfiguration>
diff --git a/tools/pgm_daemon.launch b/tools/pgm_daemon.launch
index fd7b50f..cedf470 100644
--- a/tools/pgm_daemon.launch
+++ b/tools/pgm_daemon.launch
@@ -10,6 +10,10 @@
 <booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
 <stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"/>
 <stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;sourceLookupDirector&gt;&#10;&lt;sourceContainers duplicates=&quot;false&quot;&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-common&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-httpd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-commonsnet&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-patch-jgit&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-pgm&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-server&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-sshd&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-cli&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-util-ssl&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-war&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-reviewdb&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;javaProject name=&amp;quot;gerrit-main&amp;quot;/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.javaProject&quot;/&gt;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#10;&amp;lt;default/&amp;gt;&amp;#10;&quot; typeId=&quot;org.eclipse.debug.core.containerType.default&quot;/&gt;&#10;&lt;/sourceContainers&gt;&#10;&lt;/sourceLookupDirector&gt;&#10;"/>
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
+<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
+</listAttribute>
 <booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
 <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.m2e.launchconfig.classpathProvider"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
diff --git a/tools/release.sh b/tools/release.sh
index 79e6605..de18357 100755
--- a/tools/release.sh
+++ b/tools/release.sh
@@ -25,7 +25,7 @@
 fi
 
 ./tools/version.sh --release &&
-mvn clean package $include_docs -P all
+mvn clean install $include_docs -P all
 rc=$?
 ./tools/version.sh --reset
 
diff --git a/tools/version.sh b/tools/version.sh
index c3d9417..d3e4cd5 100755
--- a/tools/version.sh
+++ b/tools/version.sh
@@ -6,7 +6,7 @@
 # Java based Maven plugin so its fully portable.
 #
 
-POM_FILES=$(git ls-files | grep pom.xml)
+POM_FILES=$(git ls-files | grep pom.xml | grep -v gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml)
 
 case "$1" in
 --snapshot=*)