Merge branch 'stable-2.8' into stable-2.9

* stable-2.8:
  Fix: Wrong exception mapping in ReceiveCommmits
  Fix REST example for removing included groups from a group
  rest-api-groups.txt: Correct input examples to use [] for lists

Change-Id: I36518fec208e4ab101192118aebeb6c5e0d7e34c
diff --git a/.buckconfig b/.buckconfig
index 1bc29ac..4eb5e85 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -5,6 +5,7 @@
   docs = //Documentation:html
   gerrit = //:gerrit
   release = //:release
+  withdocs = //:withdocs
 
 [buildfile]
   includes = //tools/default.defs
@@ -14,3 +15,7 @@
 
 [project]
   ignore = .git
+
+[cache]
+  mode = dir
+  dir = buck-out/cache
diff --git a/.buckversion b/.buckversion
index e39bcf8..ff1c137 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-274acb17e9b6dc9ee60bc1371c47a7f49640c24c
+2b80cf780ae31bee6609ebc1bbab9ce6fd004dbe
diff --git a/.gitignore b/.gitignore
index 1b4c29c..efff6da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
 *.sublime-*
 /gerrit-package-plugins
 /.buckconfig.local
+/.buckjavaargs
 /.buckd
 /buck-cache
 /buck-out
diff --git a/.gitmodules b/.gitmodules
index 6476c4c..d75c98c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -18,3 +18,6 @@
 	path = plugins/reviewnotes
 	url = ../plugins/reviewnotes
 
+[submodule "plugins/singleusergroup"]
+	path = plugins/singleusergroup
+	url = ../plugins/singleusergroup
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index dbc83d5..2a585e4 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -4,8 +4,8 @@
 org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
 org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
 org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
 org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
 org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
@@ -85,7 +85,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
 org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.compiler.source=1.7
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/BUCK b/BUCK
index a278127..2d07758 100644
--- a/BUCK
+++ b/BUCK
@@ -3,16 +3,19 @@
 gerrit_war(name = 'gerrit')
 gerrit_war(name = 'chrome',   ui = 'ui_chrome')
 gerrit_war(name = 'firefox',  ui = 'ui_firefox')
-gerrit_war(name = 'withdocs', context = DOCS)
-gerrit_war(name = 'release',  context = DOCS + ['//plugins:core.zip'])
+gerrit_war(name = 'withdocs', docs = True)
+gerrit_war(name = 'release',  docs = True, context = ['//plugins:core.zip'])
 
 API_DEPS = [
-  ':extension-api',
-  ':extension-api-src',
-  ':plugin-api',
-  ':plugin-api-src',
-  ':plugin-gwtui',
-  ':plugin-gwtui-src',
+  '//gerrit-extension-api:extension-api',
+  '//gerrit-extension-api:extension-api-src',
+  '//gerrit-extension-api:extension-api-javadoc',
+  '//gerrit-plugin-api:plugin-api',
+  '//gerrit-plugin-api:plugin-api-src',
+  '//gerrit-plugin-api:plugin-api-javadoc',
+  '//gerrit-plugin-gwtui:gwtui-api',
+  '//gerrit-plugin-gwtui:gwtui-api-src',
+  '//gerrit-plugin-gwtui:gwtui-api-javadoc',
 ]
 
 genrule(
@@ -24,73 +27,3 @@
   deps = API_DEPS,
   out = 'api.zip',
 )
-
-java_binary(
-  name = 'extension-api',
-  deps = [':extension-lib'],
-  visibility = ['//tools/maven:'],
-)
-
-java_library(
-  name = 'extension-lib',
-  deps = [
-    '//gerrit-extension-api:api',
-    '//lib/guice:guice',
-    '//lib/guice:guice-servlet',
-    '//lib:servlet-api-3_0',
-  ],
-  export_deps = True,
-  visibility = ['PUBLIC'],
-)
-
-genrule(
-  name = 'extension-api-src',
-  cmd = 'ln -s $(location //gerrit-extension-api:api-src) $OUT',
-  deps = ['//gerrit-extension-api:api-src'],
-  out = 'extension-api-src.jar',
-  visibility = ['//tools/maven:'],
-)
-
-PLUGIN_API = [
-  '//gerrit-server:server',
-  '//gerrit-pgm:init-api',
-  '//gerrit-sshd:sshd',
-  '//gerrit-httpd:httpd',
-]
-
-java_binary(
-  name = 'plugin-api',
-  deps = [':plugin-lib'],
-  visibility = ['//tools/maven:'],
-)
-
-java_library(
-  name = 'plugin-lib',
-  deps = PLUGIN_API + ['//lib:servlet-api-3_0'],
-  export_deps = True,
-  visibility = ['PUBLIC'],
-)
-
-java_binary(
-  name = 'plugin-api-src',
-  deps = [
-    '//gerrit-extension-api:api-src',
-  ] + [d + '-src' for d in PLUGIN_API],
-  visibility = ['//tools/maven:'],
-)
-
-genrule(
-  name = 'plugin-gwtui',
-  cmd = 'ln -s $(location //gerrit-plugin-gwtui:client) $OUT',
-  deps = ['//gerrit-plugin-gwtui:client'],
-  out = 'plugin-gwtui.jar',
-  visibility = ['//tools/maven:'],
-)
-
-genrule(
-  name = 'plugin-gwtui-src',
-  cmd = 'ln -s $(location //gerrit-plugin-gwtui:src) $OUT',
-  deps = ['//gerrit-plugin-gwtui:src'],
-  out = 'plugin-gwtui-src.jar',
-  visibility = ['//tools/maven:'],
-)
diff --git a/Documentation/BUCK b/Documentation/BUCK
index 71d8664..9c2aea8 100644
--- a/Documentation/BUCK
+++ b/Documentation/BUCK
@@ -3,7 +3,6 @@
 include_defs('//tools/git.defs')
 
 DOC_DIR = 'Documentation'
-INDEX_DIR = DOC_DIR + '/.index'
 MAIN = ['//gerrit-pgm:pgm', '//gerrit-gwtui:ui_module']
 SRCS = glob(['*.txt'], excludes = ['licenses.txt'])
 
@@ -11,12 +10,10 @@
   name = 'html',
   cmd = 'cd $TMP;' +
     'mkdir -p %s/images;' % DOC_DIR +
-    'unzip -q $SRCDIR/index.zip -d %s/;' % INDEX_DIR +
     'unzip -q $SRCDIR/only_html.zip -d %s/;' % DOC_DIR +
     'for s in $SRCS;do ln -s $s %s;done;' % DOC_DIR +
     'mv %s/*.{jpg,png} %s/images;' % (DOC_DIR, DOC_DIR) +
     'rm %s/only_html.zip;' % DOC_DIR +
-    'rm %s/index.zip;' % DOC_DIR +
     'rm %s/licenses.txt;' % DOC_DIR +
     'cp $SRCDIR/licenses.txt LICENSES.txt;' +
     'zip -qr $OUT *',
@@ -27,11 +24,9 @@
       'doc.css',
       genfile('licenses.txt'),
       genfile('only_html.zip'),
-      genfile('index.zip'),
     ],
   deps = [
     ':generate_html',
-    ':index',
     ':licenses.txt',
   ],
   out = 'html.zip',
@@ -67,7 +62,7 @@
 genrule(
   name = 'index',
   cmd = '$(exe //lib/asciidoctor:doc_indexer) ' +
-      '-z $OUT ' +
+      '-o $OUT ' +
       '--prefix "%s/" ' % DOC_DIR +
       '--in-ext ".txt" ' +
       '--out-ext ".html" ' +
@@ -77,5 +72,12 @@
     ':licenses.txt',
     '//lib/asciidoctor:doc_indexer',
   ],
-  out = 'index.zip',
+  out = 'index.jar',
+)
+
+prebuilt_jar(
+  name = 'index_lib',
+  binary_jar = genfile('index.jar'),
+  deps = [':index'],
+  visibility = ['PUBLIC'],
 )
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 8076a33..4099ed4 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Access Controls
-====================================
+= Gerrit Code Review - Access Controls
 
 Access controls in Gerrit are group based.  Every user account is a
 member of one or more groups, and access and privileges are granted
@@ -7,18 +6,26 @@
 users.
 
 
-System Groups
--------------
+[[system_groups]]
+== System Groups
 
-Gerrit comes with 4 system groups, with special access privileges
-and membership management.  The identity of these groups is set
-in the `system_config` table within the database, so the groups
-can be renamed after installation if desired.
+Gerrit comes with following system groups:
+
+* Administrators
+* Anonymous Users
+* Change Owner
+* Non-Interactive Users
+* Project Owners
+* Registered Users
+
+The system groups are assigned special access and membership management
+privileges.  The identity of these groups is set in the `system_config`
+table within the database, so the groups can be renamed after installation
+if desired.
 
 
 [[administrators]]
-Administrators
-~~~~~~~~~~~~~~
+=== Administrators
 
 This is the Gerrit "root" identity.
 
@@ -36,8 +43,7 @@
 
 
 [[anonymous_users]]
-Anonymous Users
-~~~~~~~~~~~~~~~
+=== Anonymous Users
 
 All users are automatically a member of this group.  Users who are
 not signed in are a member of only this group, and no others.
@@ -52,8 +58,7 @@
 
 
 [[non-interactive_users]]
-Non-Interactive Users
-~~~~~~~~~~~~~~~~~~~~~
+=== Non-Interactive Users
 
 This is an internal user group, members of this group are not expected
 to perform interactive operations on the Gerrit web front-end.
@@ -68,8 +73,7 @@
 
 
 [[project_owners]]
-Project Owners
-~~~~~~~~~~~~~~
+=== Project Owners
 
 Access rights assigned to this group are always evaluated within the
 context of a project to which the access rights apply. These rights
@@ -85,9 +89,19 @@
 newly created child projects.
 
 
+[[change_owner]]
+=== Change Owner
+
+Access rights assigned to this group are always evaluated within the
+context of a change to which the access rights apply. These rights
+therefore apply to the user who is the owner of this change.
+
+It is typical to assign a label to this group, allowing the change
+owner to vote on his change, but not actually cause it to become
+approved or rejected.
+
 [[registered_users]]
-Registered Users
-~~~~~~~~~~~~~~~~
+=== Registered Users
 
 All signed-in users are automatically a member of this group (and
 also <<anonymous_users,'Anonymous Users'>>, see above).
@@ -106,8 +120,7 @@
 on any change in any project they have `Read` access to.
 
 
-Account Groups
---------------
+== Account Groups
 
 Account groups contain a list of zero or more user account members,
 added individually by a group owner.  Any user account listed as
@@ -141,8 +154,7 @@
 
 
 [[ldap_groups]]
-LDAP Groups
------------
+== LDAP Groups
 
 LDAP groups are Account Groups that are maintained inside of your
 LDAP instance. If you are using LDAP to manage your groups they will
@@ -152,8 +164,7 @@
 add the LDAP "foo-project" group to the access list.
 
 
-Project Access Control Lists
-----------------------------
+== Project Access Control Lists
 
 A system wide access control list affecting all projects is stored in
 project "`All-Projects`".  This inheritance can be configured
@@ -262,8 +273,7 @@
 |==============================================================
 
 
-OpenID Authentication
-~~~~~~~~~~~~~~~~~~~~~
+=== OpenID Authentication
 
 If the Gerrit instance is configured to use OpenID authentication,
 an account's effective group membership will be restricted to only
@@ -272,8 +282,7 @@
 in the `auth.trustedOpenID` list from `gerrit.config`.
 
 
-All Projects
-~~~~~~~~~~~~
+=== All Projects
 
 Any access right granted to a group within `All-Projects`
 is automatically inherited by every other project in the same
@@ -292,8 +301,7 @@
 permissions for every managed project including global capabilities.
 
 
-Per-Project
-~~~~~~~~~~~
+=== Per-Project
 
 The per-project ACL is evaluated before the global `All-Projects` ACL,
 permitting some limited override capability to project owners. This
@@ -302,8 +310,7 @@
 
 
 [[references]]
-Special and magic references
-----------------------------
+== Special and magic references
 
 The reference namespaces used in git are generally two, one for branches and
 one for tags:
@@ -318,8 +325,7 @@
 
 
 [[references_special]]
-Special references
-~~~~~~~~~~~~~~~~~~
+=== Special references
 
 The special references have content that's either generated by Gerrit or
 contains important project configuration that Gerrit needs. When making
@@ -327,24 +333,23 @@
 contents compatibility at upload time.
 
 
-refs/changes/*
-^^^^^^^^^^^^^^
+==== refs/changes/*
 
 Under this namespace each uploaded patch set for every change gets a static
 reference in their git.  The format is convenient but still intended to scale to
 hundreds of thousands of patch sets.  To access a given patch set you will
 need the change number and patch set number.
 
-[verse]
+--
 'refs/changes/'<last two digits of change number>/
   <change number>/
   <patch set number>
+--
 
 You can also find these static references linked on the page of each change.
 
 
-refs/meta/config
-^^^^^^^^^^^^^^^^
+==== refs/meta/config
 
 This is where the Gerrit configuration of each project resides.  This
 branch contains several files of importance: +project.config+, +groups+ and
@@ -352,15 +357,13 @@
 review process.
 
 
-refs/meta/dashboards/*
-^^^^^^^^^^^^^^^^^^^^^^
+==== refs/meta/dashboards/*
 
 There's a dedicated page where you can read more about
 link:user-dashboards.html[User Dashboards].
 
 
-refs/notes/review
-^^^^^^^^^^^^^^^^^
+==== refs/notes/review
 
 Autogenerated copy of review notes for all changes in the git.  Each log entry
 on the refs/notes/review branch also references the patch set on which the
@@ -368,14 +371,13 @@
 
 
 [[references_magic]]
-Magic references
-~~~~~~~~~~~~~~~~
+=== Magic references
 
 These are references with added functionality to them compared to a regular
 git push operation.
 
-refs/for/<branch ref>
-^^^^^^^^^^^^^^^^^^^^^
+[[refs_for]]
+==== refs/for/<branch ref>
 
 Most prominent is the `refs/for/<branch ref>` reference which is the reference
 upon which we build the code review intercept before submitting a commit to
@@ -385,15 +387,13 @@
 link:user-upload.html#push_create[Upload changes] page.
 
 
-refs/publish/*
-^^^^^^^^^^^^^^
+==== refs/publish/*
 
 `refs/publish/*` is an alternative name to `refs/for/*` when pushing new changes
 and patch sets.
 
 
-refs/drafts/*
-^^^^^^^^^^^^^
+==== refs/drafts/*
 
 Push to `refs/drafts/*` creates a change like push to `refs/for/*`, except the
 resulting change remains hidden from public review.  You then have the option
@@ -411,8 +411,7 @@
 
 
 [[access_categories]]
-Access Categories
------------------
+== Access Categories
 
 Gerrit has several permission categories that can be granted to groups
 within projects, enabling functionality for that group's members.
@@ -420,8 +419,7 @@
 
 
 [[category_abandon]]
-Abandon
-~~~~~~~
+=== Abandon
 
 This category controls whether users are allowed to abandon changes
 to projects in Gerrit. It can give permission to abandon a specific
@@ -433,8 +431,7 @@
 
 
 [[category_create]]
-Create reference
-~~~~~~~~~~~~~~~~
+=== Create Reference
 
 The create reference category controls whether it is possible to
 create new references, branches or tags.  This implies that the
@@ -468,8 +465,7 @@
 
 
 [[category_forge_author]]
-Forge Author
-~~~~~~~~~~~~
+=== Forge Author
 
 Normally Gerrit requires the author and the committer identity
 lines in a Git commit object (or tagger line in an annotated tag) to
@@ -489,8 +485,7 @@
 
 
 [[category_forge_committer]]
-Forge Committer
-~~~~~~~~~~~~~~~
+=== Forge Committer
 
 Normally Gerrit requires the author and the committer identity
 lines in a Git commit object (or tagger line in an annotated tag) to
@@ -504,8 +499,7 @@
 
 
 [[category_forge_server]]
-Forge Server
-~~~~~~~~~~~~
+=== Forge Server
 
 Normally Gerrit requires the author and the committer identity
 lines in a Git commit object (or tagger line in an annotated tag) to
@@ -521,8 +515,7 @@
 
 
 [[category_owner]]
-Owner
-~~~~~
+=== Owner
 
 The `Owner` category controls which groups can modify the project's
 configuration.  Users who are members of an owner group can:
@@ -532,10 +525,8 @@
 * Create/delete a branch through the web UI
 * Grant/revoke any access rights, including `Owner`
 
-Note that project owners implicitly have branch creation or deletion
-through the web UI, but not through SSH.  To get SSH branch access
-project owners must grant an access right to a group they are a
-member of, just like for any other user.
+To get SSH branch access project owners must grant an access right to a group
+they are a member of, just like for any other user.
 
 Ownership over a particular branch subspace may be delegated by
 entering a branch pattern.  To delegate control over all branches
@@ -547,8 +538,7 @@
 
 
 [[category_push]]
-Push
-~~~~
+=== Push
 
 This category controls how users are allowed to upload new commits
 to projects in Gerrit. It can either give permission to push
@@ -559,8 +549,7 @@
 
 
 [[category_push_direct]]
-Direct Push
-^^^^^^^^^^^
+==== Direct Push
 
 Any existing branch can be fast-forwarded to a new commit.
 Creation of new branches is controlled by the
@@ -583,8 +572,7 @@
 
 
 [[category_push_review]]
-Upload To Code Review
-^^^^^^^^^^^^^^^^^^^^^
+==== Upload To Code Review
 
 The `Push` access right granted on the namespace
 `refs/for/refs/heads/BRANCH` permits the user to upload a non-merge
@@ -608,8 +596,7 @@
 
 
 [[category_push_merge]]
-Push Merge Commits
-~~~~~~~~~~~~~~~~~~
+=== Push Merge Commits
 
 The `Push Merge Commit` access right permits the user to upload merge
 commits.  It's an add-on to the <<category_push,Push>> access right, and
@@ -627,8 +614,7 @@
 
 
 [[category_push_annotated]]
-Push Annotated Tag
-~~~~~~~~~~~~~~~~~~
+=== Push Annotated Tag
 
 This category permits users to push an annotated tag object into the
 project's repository.  Typically this would be done with a command line
@@ -668,8 +654,7 @@
 
 
 [[category_push_signed]]
-Push Signed Tag
-~~~~~~~~~~~~~~~
+=== Push Signed Tag
 
 This category permits users to push a PGP signed tag object into the
 project's repository.  Typically this would be done with a command
@@ -690,8 +675,7 @@
 
 
 [[category_read]]
-Read
-~~~~
+=== Read
 
 The `Read` category controls visibility to the project's
 changes, comments, code diffs, and Git access over SSH or HTTP.
@@ -722,8 +706,7 @@
 
 
 [[category_rebase]]
-Rebase
-~~~~~~
+=== Rebase
 
 This category permits users to rebase changes via the web UI by pushing
 the `Rebase Change` button.
@@ -737,8 +720,7 @@
 
 
 [[category_remove_reviewer]]
-Remove Reviewer
-~~~~~~~~~~~~~~~
+=== Remove Reviewer
 
 This category permits users to remove other users from the list of
 reviewers on a change.
@@ -752,8 +734,7 @@
 
 
 [[category_review_labels]]
-Review Labels
-~~~~~~~~~~~~~
+=== Review Labels
 
 For every configured label `My-Name` in the project, there is a
 corresponding permission `label-My-Name` with a range corresponding to
@@ -767,8 +748,7 @@
 
 
 [[category_submit]]
-Submit
-~~~~~~
+=== Submit
 
 This category permits users to push the `Submit Patch Set n` button
 on the web UI.
@@ -785,10 +765,17 @@
 the caller needs to have the Submit permission on `refs/for/<ref>`
 (e.g. on `refs/for/refs/heads/master`).
 
+[[category_submit_on_behalf_of]]
+=== Submit (On Behalf Of)
+
+This category permits users who have also been granted the `Submit`
+permission to submit changes on behalf of another user.
+
+Note that this permission is named `submitAs` in the `project.config`
+file.
 
 [[category_view_drafts]]
-View Drafts
-~~~~~~~~~~~
+=== View Drafts
 
 This category permits users to view draft changes uploaded by other
 users.
@@ -799,8 +786,7 @@
 
 
 [[category_publish_drafts]]
-Publish Drafts
-~~~~~~~~~~~~~~
+=== Publish Drafts
 
 This category permits users to publish draft changes uploaded by other
 users.
@@ -810,8 +796,7 @@
 
 
 [[category_delete_drafts]]
-Delete Drafts
-~~~~~~~~~~~~~
+=== Delete Drafts
 
 This category permits users to delete draft changes uploaded by other
 users.
@@ -821,8 +806,7 @@
 
 
 [[category_edit_topic_name]]
-Edit Topic Name
-~~~~~~~~~~~~~~~
+=== Edit Topic Name
 
 This category permits users to edit the topic name of a change that
 is uploaded for review.
@@ -836,8 +820,8 @@
 edited on open changes.
 
 
-Examples of typical roles in a project
---------------------------------------
+[[example_roles]]
+== Examples of typical roles in a project
 
 Below follows a set of typical roles on a server and which access
 rights these roles typically should be granted. You may see them as
@@ -846,8 +830,7 @@
 
 
 [[examples_contributor]]
-Contributor
-~~~~~~~~~~~
+=== Contributor
 
 This is the typical user on a public server. They are able to read
 your project and upload new changes to it. They are able to give
@@ -873,8 +856,7 @@
 
 
 [[examples_developer]]
-Developer
-~~~~~~~~~
+=== Developer
 
 This is the typical core developer on a public server.  They are able
 to read the project, upload changes to a branch.  They are allowed to
@@ -909,8 +891,7 @@
 
 
 [[examples_cisystem]]
-CI system
-~~~~~~~~~
+=== CI system
 
 A typical Continuous Integration system should be able to download new changes
 to build and then leave a verdict somehow.
@@ -958,8 +939,7 @@
 
 
 [[examples_integrator]]
-Integrator
-~~~~~~~~~~
+=== Integrator
 
 Integrators are like developers but with some additional rights granted due
 to their administrative role in a project.  They can upload or push any commit
@@ -977,8 +957,7 @@
 
 
 [[examples_project-owner]]
-Project owner
-~~~~~~~~~~~~~
+=== Project owner
 
 The project owner is almost like an integrator but with additional destructive
 power in the form of being able to delete branches.  Optionally these users
@@ -1001,8 +980,7 @@
 
 
 [[examples_administrator]]
-Administrator
-~~~~~~~~~~~~~
+=== Administrator
 
 The administrator role is the most powerful role known in the Gerrit universe.
 This role may grant itself (or others) any access right.  By default the
@@ -1018,8 +996,7 @@
 * Any <<global_capabilities,`capabilities`>> needed by the administrator
 
 
-Enforcing site wide access policies
------------------------------------
+== Enforcing site wide access policies
 
 By granting the <<category_owner,`Owner`>> access right on the `refs/*` to a
 group, Gerrit administrators can delegate the responsibility of maintaining
@@ -1044,8 +1021,7 @@
 
 
 [[block]]
-'BLOCK' access rule
-~~~~~~~~~~~~~~~~~~~
+=== 'BLOCK' access rule
 
 The 'BLOCK' rule blocks a permission globally. An inherited 'BLOCK' rule cannot
 be overridden in the inheriting project. Any 'ALLOW' rule, from a different
@@ -1071,8 +1047,7 @@
 every vote from '-INFINITE..min' and 'max..INFINITE'. For the example above it
 means that the range '-1..+1' is not affected by this block.
 
-'BLOCK' and 'ALLOW' rules in the same access section
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== 'BLOCK' and 'ALLOW' rules in the same access section
 
 When an access section of a project contains a 'BLOCK' and an 'ALLOW' rule for
 the same permission then this 'ALLOW' rule overrides the 'BLOCK' rule:
@@ -1092,13 +1067,11 @@
 inheriting project cannot override a 'BLOCK' rule.
 
 
-Examples
-~~~~~~~~
+=== Examples
 
 The following examples show some possible use cases for the 'BLOCK' rules.
 
-Make sure no one can update or delete a tag
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Make sure no one can update or delete a tag
 
 This requirement is quite common in a corporate deployment where
 reproducibility of a build must be guaranteed. To achieve that we block 'push'
@@ -1122,8 +1095,7 @@
 ====
 
 
-Let only a dedicated group vote in a special category
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Let only a dedicated group vote in a special category
 
 Assume there is a more restrictive process for submitting changes in stable
 release branches which is manifested as a new voting category
@@ -1140,8 +1112,7 @@
 ====
 
 [[global_capabilities]]
-Global Capabilities
--------------------
+== Global Capabilities
 
 The global capabilities control actions that the administrators of
 the server can perform which usually affect the entire
@@ -1161,15 +1132,13 @@
 
 
 [[capability_accessDatabase]]
-Access Database
-~~~~~~~~~~~~~~~
+=== Access Database
 
 Allow users to access the database using the `gsql` command.
 
 
 [[capability_administrateServer]]
-Administrate Server
-~~~~~~~~~~~~~~~~~~~
+=== Administrate Server
 
 This is in effect the owner and administrator role of the Gerrit
 instance.  Any members of a group granted this capability will be
@@ -1178,8 +1147,7 @@
 
 
 [[capability_createAccount]]
-Create Account
-~~~~~~~~~~~~~~
+=== Create Account
 
 Allow link:cmd-create-account.html[account creation over the ssh prompt].
 This capability allows the granted group members to create non-interactive
@@ -1189,8 +1157,7 @@
 
 
 [[capability_createGroup]]
-Create Group
-~~~~~~~~~~~~
+=== Create Group
 
 Allow group creation.  Groups are used to grant users access to different
 actions in projects.  This capability allows the granted group members to
@@ -1198,8 +1165,7 @@
 
 
 [[capability_createProject]]
-Create Project
-~~~~~~~~~~~~~~
+=== Create Project
 
 Allow project creation.  This capability allows the granted group to
 either link:cmd-create-project.html[create new git projects via ssh]
@@ -1207,8 +1173,7 @@
 
 
 [[capability_emailReviewers]]
-Email Reviewers
-~~~~~~~~~~~~~~~
+=== Email Reviewers
 
 Allow or deny sending email to change reviewers and watchers.  This can be used
 to deny build bots from emailing reviewers and people who watch the change.
@@ -1218,8 +1183,7 @@
 
 
 [[capability_flushCaches]]
-Flush Caches
-~~~~~~~~~~~~
+=== Flush Caches
 
 Allow the flushing of Gerrit's caches.  This capability allows the granted
 group to link:cmd-flush-caches.html[flush some or all Gerrit caches via ssh].
@@ -1230,16 +1194,14 @@
 
 
 [[capability_generateHttpPassword]]
-Generate HTTP Password
-~~~~~~~~~~~~~~~~~~~~~~
+=== Generate HTTP Password
 
 Allow the user to generate HTTP passwords for other users.  Typically this would
 be assigned to a non-interactive users group.
 
 
 [[capability_kill]]
-Kill Task
-~~~~~~~~~
+=== Kill Task
 
 Allow the operation of the link:cmd-kill.html[kill command over ssh].  The
 kill command ends tasks that currently occupy the Gerrit server, usually
@@ -1248,8 +1210,7 @@
 
 
 [[capability_priority]]
-Priority
-~~~~~~~~
+=== Priority
 
 This capability allows users to use
 link:config-gerrit.html#sshd.batchThreads[the thread pool reserved] for
@@ -1277,8 +1238,7 @@
 
 
 [[capability_queryLimit]]
-Query Limit
-~~~~~~~~~~~
+=== Query Limit
 
 Allow site administrators to configure the query limit for users to
 be above the default hard-coded value of 500.  Administrators can add
@@ -1293,8 +1253,7 @@
 
 
 [[capability_runAs]]
-Run As
-~~~~~~
+=== Run As
 
 Allow users to impersonate any other user with the `X-Gerrit-RunAs`
 HTTP header on REST API calls, or the link:cmd-suexec.html[suexec]
@@ -1314,25 +1273,30 @@
 
 
 [[capability_runGC]]
-Run Garbage Collection
-~~~~~~~~~~~~~~~~~~~~~~
+=== Run Garbage Collection
 
 Allow users to run the Git garbage collection for the repositories of
 all projects.
 
 
 [[capability_streamEvents]]
-Stream Events
-~~~~~~~~~~~~~
+=== Stream Events
 
 Allow performing streaming of Gerrit events. This capability
 allows the granted group to
 link:cmd-stream-events.html[stream Gerrit events via ssh].
 
 
+[[capability_viewAllAccounts]]
+=== View All Accounts
+
+Allow viewing all accounts for purposes of auto-completion, regardless
+of link:config-gerrit.html#accounts.visibility[accounts.visibility]
+setting.
+
+
 [[capability_viewCaches]]
-View Caches
-~~~~~~~~~~~
+=== View Caches
 
 Allow querying for status of Gerrit's internal caches.  This capability allows
 the granted group to
@@ -1340,8 +1304,7 @@
 
 
 [[capability_viewConnections]]
-View Connections
-~~~~~~~~~~~~~~~~
+=== View Connections
 
 Allow querying for status of Gerrit's current client connections.  This
 capability allows the granted group to
@@ -1349,8 +1312,7 @@
 
 
 [[capability_viewQueue]]
-View Queue
-~~~~~~~~~~
+=== View Queue
 
 Allow querying for status of Gerrit's internal task queue.  This capability
 allows the granted group to
@@ -1360,3 +1322,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
index 44313d2..0175431 100644
--- a/Documentation/asciidoc.defs
+++ b/Documentation/asciidoc.defs
@@ -26,6 +26,7 @@
       'cd $SRCDIR;',
       '$(exe //lib/asciidoctor:asciidoc)',
       '-z', '$OUT',
+      '--tmp', '$TMP',
       '--in-ext', '".txt%s"' % EXPN,
       '--out-ext', '".html"',
   ]
diff --git a/Documentation/cmd-apropos.txt b/Documentation/cmd-apropos.txt
new file mode 100644
index 0000000..8882af18
--- /dev/null
+++ b/Documentation/cmd-apropos.txt
@@ -0,0 +1,59 @@
+= gerrit apropos
+
+== NAME
+gerrit apropos - Search Gerrit documentation index
+
+== SYNOPSIS
+--
+'ssh' -p <port> <host> 'gerrit apropos'
+  <query>
+--
+
+== DESCRIPTION
+Queries the documentation index and returns results with the title and URL
+from the matched documents.
+
+== ACCESS
+Any user who has configured an SSH key.
+
+== SCRIPTING
+This command is intended to be used in scripts.
+
+Note: this feature is only available if documentation index was built.
+
+== EXAMPLES
+
+=====
+$ ssh -p 29418 review.example.com gerrit apropos capabilities
+    Gerrit Code Review - /config/ REST API:
+    http://localhost:8080/Documentation/rest-api-config.html
+
+    Gerrit Code Review - /accounts/ REST API:
+    http://localhost:8080/Documentation/rest-api-accounts.html
+
+    Gerrit Code Review - Project Configuration File Format:
+    http://localhost:8080/Documentation/config-project-config.html
+
+    Gerrit Code Review - Access Controls:
+    http://localhost:8080/Documentation/access-control.html
+
+    Gerrit Code Review - Plugin Development:
+    http://localhost:8080/Documentation/dev-plugins.html
+
+    Gerrit Code Review - REST API:
+    http://localhost:8080/Documentation/rest-api.html
+
+    Gerrit Code Review - /access/ REST API:
+    http://localhost:8080/Documentation/rest-api-access.html
+=====
+
+== SEE ALSO
+
+* link:access-control.html[Access Controls]
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-ban-commit.txt b/Documentation/cmd-ban-commit.txt
index fb4a2ac..d5c09af 100644
--- a/Documentation/cmd-ban-commit.txt
+++ b/Documentation/cmd-ban-commit.txt
@@ -1,20 +1,17 @@
-gerrit ban-commit
-=================
+= gerrit ban-commit
 
-NAME
-----
+== NAME
 gerrit ban-commit - Bans a commit from a project's repository.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit ban-commit'
   [--reason <REASON>]
   <PROJECT>
   <COMMIT> ...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Marks a commit as banned for the specified repository.  If a commit is
 banned Gerrit rejects every push that includes this commit with
 link:error-contains-banned-commit.html[contains banned commit ...].
@@ -24,17 +21,14 @@
 the commit from the history of any central branch.  This needs to be
 done manually.
 
-ACCESS
-------
+== ACCESS
 Caller must be owner of the project or be a member of the privileged
 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <PROJECT>::
 	Required; name of the project for which the commit should be
 	banned.
@@ -45,8 +39,7 @@
 --reason::
 	Reason for banning the commit.
 
-EXAMPLES
---------
+== EXAMPLES
 Ban commit `421919d015c062fd28901fe144a78a555d0b5984` from project
 `myproject`:
 
@@ -58,3 +51,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-cherry-pick.txt b/Documentation/cmd-cherry-pick.txt
index 15a8524..0c4cf91 100644
--- a/Documentation/cmd-cherry-pick.txt
+++ b/Documentation/cmd-cherry-pick.txt
@@ -1,19 +1,16 @@
-gerrit-cherry-pick
-==================
+= gerrit-cherry-pick
 
-NAME
-----
+== NAME
 gerrit-cherry-pick - Download and cherry pick one or more changes
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'gerrit-cherry-pick' <remote> <changeid>...
 'gerrit-cherry-pick' --continue | --skip | --abort
 'gerrit-cherry-pick' --close <remote>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Downloads the listed changes specified on the command line and
 proceeds to cherry-pick them (rewriting commit SHA-1s as it goes)
 onto the current branch.
@@ -31,8 +28,7 @@
 ensuring link:user-changeid.html[Change-Id lines] are present in
 each commit message.
 
-OBTAINING
----------
+== OBTAINING
 To obtain the 'gerrit-cherry-pick' script use scp, curl or wget to
 copy it to your local system:
 
@@ -45,3 +41,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-create-account.txt b/Documentation/cmd-create-account.txt
index 3ecb764..2159e0e 100644
--- a/Documentation/cmd-create-account.txt
+++ b/Documentation/cmd-create-account.txt
@@ -1,13 +1,10 @@
-gerrit create-account
-=====================
+= gerrit create-account
 
-NAME
-----
+== NAME
 gerrit create-account - Create a new user account.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit create-account'
   [--group <GROUP>]
   [--full-name <FULLNAME>]
@@ -15,9 +12,9 @@
   [--ssh-key - | <KEY>]
   [--http-password <PASSWORD>]
   <USERNAME>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Creates a new internal-only user account.
 
 If the account is created without an email address, it may only be
@@ -32,18 +29,15 @@
 without checking the LDAP directory.  Consequently users can be
 created in Gerrit that do not exist in the underlying LDAP directory.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group,
 or have been granted
 link:access-control.html#capability_createAccount[the 'Create Account' global capability].
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <USERNAME>::
 	Required; SSH username of the user account.
 
@@ -69,8 +63,7 @@
 --http-password::
     HTTP password for the user account.
 
-EXAMPLES
---------
+== EXAMPLES
 Create a new batch/role access user account called `watcher` in
 the 'Non-Interactive Users' group.
 
@@ -81,3 +74,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-create-branch.txt b/Documentation/cmd-create-branch.txt
new file mode 100644
index 0000000..671adfe
--- /dev/null
+++ b/Documentation/cmd-create-branch.txt
@@ -0,0 +1,50 @@
+= gerrit create-branch
+
+== NAME
+gerrit create-branch - Create a new branch
+
+== SYNOPSIS
+--
+'ssh' -p <port> <host> 'gerrit create-branch'
+  <PROJECT>
+  <NAME>
+  <REVISION>
+--
+
+== DESCRIPTION
+Creates a new branch for a project.
+
+== ACCESS
+Caller should have link:access-control.html#category_create[Create Reference]
+permission on the project.
+
+Administrators do not automatically have permission to create branches. It must
+be granted via the Create Reference permission.
+
+== SCRIPTING
+This command is intended to be used in scripts.
+
+== OPTIONS
+<PROJECT>::
+    Required; name of the project.
+
+<NAME>::
+    Required; name of the branch to be created.
+
+<REVISION>::
+    Required; base revision of the new branch.
+
+== EXAMPLES
+Create a new branch called 'newbranch' from the 'master' branch of
+the project 'myproject'.
+
+====
+    $ ssh -p 29418 review.example.com gerrit create-branch myproject newbranch master
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-create-group.txt b/Documentation/cmd-create-group.txt
index 8dc6dcc..d02e2ea 100644
--- a/Documentation/cmd-create-group.txt
+++ b/Documentation/cmd-create-group.txt
@@ -1,13 +1,10 @@
-gerrit create-group
-===================
+= gerrit create-group
 
-NAME
-----
+== NAME
 gerrit create-group - Create a new account group.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit create-group'
   [--owner <GROUP> | -o <GROUP>]
   [--description <DESC> | -d <DESC>]
@@ -15,9 +12,9 @@
   [--group <GROUP>]
   [--visible-to-all]
   <GROUP>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Creates a new account group.  The group creating user (the user that
 fired the create-group command) is not automatically added to
 the created group.  In case the creating user wants to be a member of
@@ -25,18 +22,15 @@
 slightly different from Gerrit's Web UI where the creating user automatically
 becomes a member of the newly created group.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group,
 or have been granted
 link:access-control.html#capability_createGroup[the 'Create Group' global capability].
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <GROUP>::
 	Required; name of the new group.
 
@@ -68,8 +62,7 @@
 --visible-to-all::
 	If specified, the group members will be visible to all users.
 
-EXAMPLES
---------
+== EXAMPLES
 Create a new account group called `gerritdev` with two initial members
 `developer1` and `developer2`.  The group should be owned by itself:
 
@@ -92,3 +85,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index e3ad834..b665f9c 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -1,13 +1,10 @@
-gerrit create-project
-=====================
+= gerrit create-project
 
-NAME
-----
+== NAME
 gerrit create-project - Create a new hosted project
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit create-project'
   [--owner <GROUP> ... | -o <GROUP> ...]
   [--parent <NAME> | -p <NAME> ]
@@ -22,10 +19,11 @@
   [[--branch <REF> | -b <REF>] ...]
   [--empty-commit]
   [--max-object-size-limit <N>]
+  [--plugin-config <PARAM> ...]
   { <NAME> | --name <NAME> }
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Creates a new bare Git repository under `gerrit.basePath`, using
 the project name supplied.  The newly created repository is empty
 (has no commits), but is registered in the Gerrit database so that
@@ -37,18 +35,15 @@
 on the remote system to create the empty repository.
 
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group,
 or have been granted
 link:access-control.html#capability_createProject[the 'Create Project' global capability].
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <NAME>::
 	Required; name of the new project to create.  If name ends
 	with `.git` the suffix will be automatically removed.
@@ -114,8 +109,11 @@
 * CHERRY_PICK: always cherry-pick the commit.
 
 +
-Defaults to MERGE_IF_NECESSARY.  For more details see
-link:project-setup.html#submit_type[Change Submit Actions].
+Defaults to MERGE_IF_NECESSARY unless
+link:config-gerrit.html#repository.name.defaultSubmitType[
+repository.<name>.defaultSubmitType] is set to a different value.
+For more details see link:project-setup.html#submit_type[
+Submit Types].
 
 --use-content-merge::
 	If enabled, Gerrit will try to perform a 3-way merge of text
@@ -155,9 +153,16 @@
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
 
+--plugin-config::
+	A plugin configuration parameter that should be set for this
+	project. The plugin configuration parameter must be specified in
+	the format '<plugin-name>.<parameter-name>=<value>'. Only
+	parameters that are explicitly declared by a plugin can be set.
+	Multiple `--plugin-config` options can be specified to set multiple
+	plugin parameters.
 
-EXAMPLES
---------
+
+== EXAMPLES
 Create a new project called `tools/gerrit`:
 
 ====
@@ -175,8 +180,7 @@
 are passed through SSH as-is to the remote Gerrit server, which uses
 the single quotes to delimit the value.
 
-REPLICATION
------------
+== REPLICATION
 If the replication plugin is installed, the plugin will attempt to
 perform remote repository creation by a Bourne shell script:
 
@@ -193,11 +197,13 @@
 NewProjectCreatedListener extension point and handle custom logic
 for remote repository creation.
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:project-setup.html[Project Setup]
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-flush-caches.txt b/Documentation/cmd-flush-caches.txt
index bc6fac5..d93d47c 100644
--- a/Documentation/cmd-flush-caches.txt
+++ b/Documentation/cmd-flush-caches.txt
@@ -1,19 +1,16 @@
-gerrit flush-caches
-===================
+= gerrit flush-caches
 
-NAME
-----
+== NAME
 gerrit flush-caches - Flush some/all server caches from memory
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit flush-caches' --all
 'ssh' -p <port> <host> 'gerrit flush-caches' --list
 'ssh' -p <port> <host> 'gerrit flush-caches' --cache <NAME> ...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Clear an in-memory cache, forcing Gerrit to reconsult the ground
 truth when it needs the information again.
 
@@ -23,18 +20,15 @@
 
 If no options are supplied, defaults to `--all`.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group,
 or in a group that have been granted
 link:access-control.html#capability_flushCaches[the 'Flush Caches' global capability].
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 --all::
 	Flush all known caches.  This is like applying a big hammer,
 	it will force everything out, potentially more than was
@@ -52,8 +46,7 @@
 	than once to flush multiple caches in a single command
 	execution.
 
-EXAMPLES
---------
+== EXAMPLES
 List caches available for flushing:
 
 ====
@@ -94,8 +87,7 @@
 	$ ssh -p 29418 review.example.com gerrit flush-caches --cache web_sessions
 ====
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:cmd-show-caches.html[gerrit show-caches]
 * link:config-gerrit.html#cache[Cache Configuration]
@@ -104,3 +96,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-gc.txt b/Documentation/cmd-gc.txt
index 07b899a..a890f1b 100644
--- a/Documentation/cmd-gc.txt
+++ b/Documentation/cmd-gc.txt
@@ -1,19 +1,17 @@
-gerrit gc
-=========
+= gerrit gc
 
-NAME
-----
+== NAME
 gerrit gc - Run the Git garbage collection
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit gc'
   [--all]
+  [--show-progress]
   <NAME> ...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Runs the Git garbage collection for the specified projects.
 
 A Gerrit system administrator can define the default parameters that
@@ -26,19 +24,16 @@
 repository specific parameters for the garbage collection in the Git
 repository configuration of every project.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group,
 or have been granted the
 link:access-control.html#capability_runGC[Run Garbage Collection]
 global capability.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <NAME>::
 	Name of the projects for which the Git garbage collection should be run.
 
@@ -46,8 +41,10 @@
 	If specified the Git garbage collection is run for all projects
 	sequentially.
 
-EXAMPLES
---------
+--show-progress::
+	If specified progress information is shown.
+
+== EXAMPLES
 
 Run the Git garbage collection for the projects 'myProject' and
 'yourProject':
@@ -70,3 +67,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-gsql.txt b/Documentation/cmd-gsql.txt
index 3c1fd31..411eb00 100644
--- a/Documentation/cmd-gsql.txt
+++ b/Documentation/cmd-gsql.txt
@@ -1,25 +1,21 @@
-gerrit gsql
-===========
+= gerrit gsql
 
-NAME
-----
+== NAME
 gerrit gsql - Administrative interface to active database
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit gsql'
   [--format {PRETTY | JSON | JSON_SINGLE}]
   [-c QUERY]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Provides interactive query support directly against the underlying
 SQL database used by the host Gerrit server.  All SQL statements
 are supported, including SELECT, UPDATE, INSERT, DELETE and ALTER.
 
-OPTIONS
--------
+== OPTIONS
 --format::
 	Set the format records are output in.  In PRETTY (the
 	default) records are displayed in a tabular output suitable
@@ -32,19 +28,16 @@
 -c::
 	Execute the single query statement supplied, and then exit.
 
-ACCESS
-------
+== ACCESS
 Caller must have been granted the
 link:access-control.html#capability_accessDatabase[Access Database]
 global capability.
 
-SCRIPTING
----------
+== SCRIPTING
 Intended for interactive use only, unless format is JSON, or
 JSON_SINGLE.
 
-EXAMPLES
---------
+== EXAMPLES
 To manually correct a user's SSH user name:
 
 ====
@@ -65,3 +58,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-hook-commit-msg.txt b/Documentation/cmd-hook-commit-msg.txt
index c0c1e6c..e102186 100644
--- a/Documentation/cmd-hook-commit-msg.txt
+++ b/Documentation/cmd-hook-commit-msg.txt
@@ -1,15 +1,12 @@
-commit-msg Hook
-===============
+= commit-msg Hook
 
 
-NAME
-----
+== NAME
 
 
 commit-msg - Edit commit messages to insert a `Change-Id` tag.
 
-DESCRIPTION
------------
+== DESCRIPTION
 
 
 A Git hook automatically invoked by `git commit`, and most other
@@ -58,8 +55,7 @@
 The `Change-Id` will not be added if `gerrit.createChangeId` is set
 to `false` in the git config.
 
-OBTAINING
----------
+== OBTAINING
 
 
 To obtain the `commit-msg` script use `scp`, `wget` or `curl` to download
@@ -88,16 +84,14 @@
   $ chmod u+x ~/duhproject/.git/hooks/commit-msg
 ====
 
-SEE ALSO
---------
+== SEE ALSO
 
 
 * link:user-changeid.html[Change-Id Lines]
 * link:http://www.kernel.org/pub/software/scm/git/docs/git-commit.html[git-commit(1)]
 * link:http://www.kernel.org/pub/software/scm/git/docs/githooks.html[githooks(5)]
 
-IMPLEMENTATION
---------------
+== IMPLEMENTATION
 
 
 The hook generates unique `Change-Id` lines by creating a virtual
@@ -117,6 +111,7 @@
 
 GERRIT
 ------
-
-
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 2395fca..d4d6a579 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -1,8 +1,6 @@
-Gerrit Code Review - Command Line Tools
-=======================================
+= Gerrit Code Review - Command Line Tools
 
-Client
-------
+== Client
 
 Client commands and hooks can be downloaded via scp, wget or curl
 from Gerrit's daemon, and then executed on the client system.
@@ -18,14 +16,12 @@
 For more details on how to determine the correct SSH port number,
 see link:user-upload.html#test_ssh[Testing Your SSH Connection].
 
-[[client_commands]]Commands
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== [[client_commands]]Commands
 
 link:cmd-cherry-pick.html[gerrit-cherry-pick]::
   Download and cherry-pick one or more changes (commits).
 
-[[client_hooks]]Hooks
-~~~~~~~~~~~~~~~~~~~~~
+=== [[client_hooks]]Hooks
 
 Client hooks can be installed into a local Git repository, improving
 the developer experience when working with a Gerrit Code Review
@@ -35,8 +31,7 @@
   Automatically generate `Change-Id: ` tags in commit messages.
 
 
-Server
-------
+== Server
 
 Aside from the standard Git server side actions, Gerrit supports
 several other commands over its internal SSH daemon.  As Gerrit does
@@ -48,8 +43,10 @@
 For more details on how to determine the correct SSH port number,
 see link:user-upload.html#test_ssh[Testing Your SSH Connection].
 
-[[user_commands]]User Commands
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== [[user_commands]]User Commands
+
+link:cmd-apropos.html[gerrit apropos]::
+	Search Gerrit documentation index.
 
 link:cmd-ban-commit.html[gerrit ban-commit]::
 	Bans a commit from a project's repository.
@@ -93,6 +90,9 @@
 Also implements the magic associated with uploading commits for
 review.  See link:user-upload.html#push_create[Creating Changes].
 
+link:cmd-create-branch.html[gerrit create-branch]::
+	Create a new project branch.
+
 [[admin_commands]]Administrator Commands
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -177,3 +177,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-kill.txt b/Documentation/cmd-kill.txt
index f09053e..c64c537 100644
--- a/Documentation/cmd-kill.txt
+++ b/Documentation/cmd-kill.txt
@@ -1,30 +1,28 @@
-kill
-====
+= kill
 
-NAME
-----
+== NAME
 kill - Cancel or abort a background task
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'kill' <ID> ...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Cancels a scheduled task from the queue.  If the task has already
 been started, requests for the task to cancel as soon as it reaches
 its next cancellation point (which is usually blocking IO).
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group,
 or have been granted link:access-control.html#capability_kill[the 'Kill Task' global capability].
 
-SCRIPTING
----------
+== SCRIPTING
 Intended for interactive use only.
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-ls-groups.txt b/Documentation/cmd-ls-groups.txt
index 17ebba1..a0c7c34 100644
--- a/Documentation/cmd-ls-groups.txt
+++ b/Documentation/cmd-ls-groups.txt
@@ -1,13 +1,10 @@
-gerrit ls-groups
-================
+= gerrit ls-groups
 
-NAME
-----
+== NAME
 gerrit ls-groups - List groups visible to caller
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit ls-groups'
   [--project <NAME> | -p <NAME>]
   [--user <NAME> | -u <NAME>]
@@ -16,21 +13,19 @@
   [--type {internal | system}]
   [-q <GROUP>]
   [--verbose | -v]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Displays the list of group names, one per line, that are visible to
 the account of the calling user.
 
 If the caller is a member of the privileged 'Administrators' group,
 all groups are listed.
 
-ACCESS
-------
+== ACCESS
 Any user who has configured an SSH key.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
 All non-printable characters (ASCII value 31 or less) are escaped
@@ -39,8 +34,7 @@
 others. In shell scripts, the `printf` command can be used to unescape
 the output.
 
-OPTIONS
--------
+== OPTIONS
 --project::
 -p::
 	Name of the project for which the groups should be listed. Only
@@ -99,8 +93,7 @@
 If a group has been "orphaned", i.e. its owner group UUID refers to a
 nonexistent group, the owner group name field will read `n/a`.
 
-EXAMPLES
---------
+== EXAMPLES
 
 List visible groups:
 =====
@@ -156,3 +149,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-ls-members.txt b/Documentation/cmd-ls-members.txt
index 9814ff2..f8708d3 100644
--- a/Documentation/cmd-ls-members.txt
+++ b/Documentation/cmd-ls-members.txt
@@ -1,28 +1,23 @@
-gerrit ls-members
-================
+= gerrit ls-members
 
-NAME
-----
+== NAME
 gerrit ls-members - Show members of a given group
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit ls-members GROUPNAME'
   [--recursive]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Displays the members of the given group, one per line, so long as the given
 group is visible to the user. The users' id, username, full name and email are
 shown tab-separated.
 
-ACCESS
-------
+== ACCESS
 Any user who has configured an SSH key.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts. Output is either an error
 message or a heading followed by zero or more lines, one for each member of the
 group. If any field is not set, or if the field is the user's full name and the
@@ -34,15 +29,13 @@
 others. In shell scripts, the `printf` command can be used to unescape
 the output.
 
-OPTIONS
--------
+== OPTIONS
 --recursive::
 	If a member of the group is itself a group, the sub-group's
 	members are included in the list. Otherwise members of any sub-group
 	are not shown and no indication is given that a sub-group is present
 
-EXAMPLES
---------
+== EXAMPLES
 
 List members of the Administrators group:
 =====
@@ -62,3 +55,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-ls-projects.txt b/Documentation/cmd-ls-projects.txt
index 26530bd..26b20a7 100644
--- a/Documentation/cmd-ls-projects.txt
+++ b/Documentation/cmd-ls-projects.txt
@@ -1,13 +1,10 @@
-gerrit ls-projects
-==================
+= gerrit ls-projects
 
-NAME
-----
+== NAME
 gerrit ls-projects - List projects visible to caller
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit ls-projects'
   [--show-branch <BRANCH> ...]
   [--description | -d]
@@ -17,25 +14,22 @@
   [--all]
   [--limit <N>]
   [--has-acl-for GROUP]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Displays the list of project names, one per line, that the
 calling user account has been granted 'READ' access to.
 
 If the caller is a member of the privileged 'Administrators'
 group, all projects are listed.
 
-ACCESS
-------
+== ACCESS
 Any user who has configured an SSH key, or by an user over HTTP.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 --show-branch::
 -b::
 	Branch for which the command will display the sha of each project.
@@ -99,8 +93,7 @@
 +
 With this option you can find out on which projects a group is used.
 
-HTTP
-----
+== HTTP
 This command is also available over HTTP, as `/projects/` for
 anonymous access and `/a/projects/` for authenticated access.
 Named options are available as query parameters. Results can
@@ -117,8 +110,7 @@
 Output will be gzip compressed if `Accept-Encoding: gzip` was used
 by the client in the request headers.
 
-EXAMPLES
---------
+== EXAMPLES
 
 List visible projects:
 =====
@@ -146,11 +138,13 @@
 	done
 ====
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:access-control.html[Access Controls]
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-ls-user-refs.txt b/Documentation/cmd-ls-user-refs.txt
index 25a99d1..11781de 100644
--- a/Documentation/cmd-ls-user-refs.txt
+++ b/Documentation/cmd-ls-user-refs.txt
@@ -1,20 +1,17 @@
-gerrit ls-user-refs
-===================
+= gerrit ls-user-refs
 
-NAME
-----
+== NAME
 gerrit ls-user-refs - List refs visible to a specific user
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit ls-user-refs'
   [--project PROJECT> | -p <PROJECT>]
   [--user <USER> | -u <USER>]
   [--only-refs-heads]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Displays all refs that the specified user can see.
 
 Allows an administrator to query which refs are visible for
@@ -23,12 +20,10 @@
 verify that certain secret refs are not exposed to the wrong
 groups.
 
-ACCESS
-------
+== ACCESS
 Administrators
 
-OPTIONS
--------
+== OPTIONS
 --project::
 -p::
 	Required; Name of the project for which the refs should be listed.
@@ -42,8 +37,7 @@
 --only-refs-heads::
 	Only list the refs found under refs/heads/*
 
-EXAMPLES
---------
+== EXAMPLES
 
 List visible refs for the user "mr.developer" in project "gerrit"
 =====
@@ -53,3 +47,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-plugin-enable.txt b/Documentation/cmd-plugin-enable.txt
index da651ca..585efd8 100644
--- a/Documentation/cmd-plugin-enable.txt
+++ b/Documentation/cmd-plugin-enable.txt
@@ -1,38 +1,31 @@
-plugin enable
-=============
+= plugin enable
 
-NAME
-----
+== NAME
 plugin enable - Enable plugins.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit plugin enable'
   <NAME> ...
+--
 
-DESCRIPTION
------------
+== 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
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <NAME>::
 	Name of the plugin that should be enabled.  Multiple names of
 	plugins that should be enabled may be specified.
 
-EXAMPLES
---------
+== EXAMPLES
 Enable a plugin:
 
 ====
@@ -42,3 +35,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-plugin-install.txt b/Documentation/cmd-plugin-install.txt
index 719c2bc..daa6472 100644
--- a/Documentation/cmd-plugin-install.txt
+++ b/Documentation/cmd-plugin-install.txt
@@ -1,43 +1,37 @@
-plugin install
-==============
+= plugin install
 
-NAME
-----
+== NAME
 plugin install - Install/Add a plugin.
 
 plugin add - Install/Add a plugin.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit plugin install | add'
   [--name <NAME> | -n <NAME>]
   - | <URL> | <PATH>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Install/Add a plugin. The plugin will be copied into the site path's
 `plugins` directory.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 -::
-	Plugin jar as piped input.
+	Plugin jar or js as piped input.
 
 <URL>::
 	URL from where the plugin should be downloaded. This can be an
 	HTTP or FTP site.
 
 <PATH>::
-	Absolute file path to the plugin jar.
+	Absolute file path to the plugin jar or js.
 
 --name::
 -n::
@@ -45,29 +39,38 @@
 	provides its own name in the MANIFEST file, then the plugin name from the
 	MANIFEST file has precedence over this option.
 
-EXAMPLES
---------
+== EXAMPLES
 Install a plugin from an absolute file path on the server's host:
 
 ====
-	ssh -p 29418 localhost gerrit plugin install -n name \
+	ssh -p 29418 localhost gerrit plugin install -n name.jar \
 	  $(pwd)/my-plugin.jar
 ====
 
+Install a WebUi plugin from an absolute file path on the server's host:
+
+====
+  ssh -p 29418 localhost gerrit plugin install -n name.js \
+    $(pwd)/my-webui-plugin.js
+====
+
 Install a plugin from an HTTP site:
 
 ====
-	ssh -p 29418 localhost gerrit plugin install -n name \
-	  http://build-server/output/our-plugin.jar
+	ssh -p 29418 localhost gerrit plugin install -n name.jar \
+	  http://build-server/output/our-plugin
 ====
 
 Install a plugin from piped input:
 
 ====
-	ssh -p 29418 localhost gerrit plugin install -n name \
+	ssh -p 29418 localhost gerrit plugin install -n name.jar \
 	  - <target/name-0.1.jar
 ====
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-plugin-ls.txt b/Documentation/cmd-plugin-ls.txt
index 6cce83c..d9c997e 100644
--- a/Documentation/cmd-plugin-ls.txt
+++ b/Documentation/cmd-plugin-ls.txt
@@ -1,31 +1,25 @@
-plugin ls
-=========
+= plugin ls
 
-NAME
-----
+== NAME
 plugin ls - List the installed plugins.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit plugin ls'
   [--all | -a]
   [--format {text | json | json_compact}]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 List the installed plugins and show their version and status.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 --all::
 -a::
 	List all plugins, including disabled plugins.
@@ -42,3 +36,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-plugin-reload.txt b/Documentation/cmd-plugin-reload.txt
index 3932e30..8889307 100644
--- a/Documentation/cmd-plugin-reload.txt
+++ b/Documentation/cmd-plugin-reload.txt
@@ -1,18 +1,15 @@
-plugin reload
-=============
+= plugin reload
 
-NAME
-----
+== NAME
 plugin reload - Reload/Restart plugins.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit plugin reload'
   <NAME> ...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Reload/Restart plugins.
 
 Whether a plugin is reloaded or restarted is defined by the plugin's
@@ -21,22 +18,18 @@
 E.g. a plugin needs to be reloaded if its configuration is modified to
 make the new configuration data become active.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <NAME>::
 	Name of the plugin that should be reloaded.  Multiple names of
 	plugins that should be reloaded may be specified.
 
-EXAMPLES
---------
+== EXAMPLES
 Reload a plugin:
 
 ====
@@ -46,3 +39,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-plugin-remove.txt b/Documentation/cmd-plugin-remove.txt
index ab8f95b..3197203 100644
--- a/Documentation/cmd-plugin-remove.txt
+++ b/Documentation/cmd-plugin-remove.txt
@@ -1,39 +1,32 @@
-plugin remove
-=============
+= plugin remove
 
-NAME
-----
+== NAME
 plugin remove - Disable plugins.
 
 plugin rm - Disable plugins.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit plugin remove | rm'
   <NAME> ...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Disable plugins. The plugins will be disabled by renaming the plugin
 jars in the site path's `plugins` directory to `<plugin-jar-name>.disabled`.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <NAME>::
 	Name of the plugin that should be disabled.  Multiple names of
 	plugins that should be disabled may be specified.
 
-EXAMPLES
---------
+== EXAMPLES
 Disable a plugin:
 
 ====
@@ -43,3 +36,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
index 66bd845..7577e03 100644
--- a/Documentation/cmd-query.txt
+++ b/Documentation/cmd-query.txt
@@ -1,13 +1,10 @@
-gerrit query
-============
+= gerrit query
 
-NAME
-----
+== NAME
 gerrit query - Query the change database
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit query'
   [--format {TEXT | JSON}]
   [--current-patch-set]
@@ -17,13 +14,14 @@
   [--commit-message]
   [--dependencies]
   [--submit-records]
+  [--all-reviewers]
   [--]
   <query>
   [limit:<n>]
   [resume_sortkey:<sortKey>]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 
 Queries the change database and returns results describing changes
 that match the input query.  More recently updated changes appear
@@ -48,8 +46,7 @@
 levels of shell quoting (caller shell invoking SSH, and the SSH
 command line parser in the server).
 
-OPTIONS
--------
+== OPTIONS
 --format::
 	Formatting method for the results. `TEXT` is the default,
 	presenting a human readable display. `JSON` returns
@@ -89,6 +86,10 @@
 	Show information about patch sets which depend on, or are needed by,
 	each patch set.
 
+--all-reviewers::
+	Show the name and email of all reviewers which are added to a change
+	(irrespective of whether they have been voting on that change or not).
+
 --submit-records::
 	Show submit record information about the change, which
 	includes whether the change meets the criteria for submission
@@ -106,16 +107,13 @@
 	resume a prior query.  This is actually a query operator,
 	and not a command line option.
 
-ACCESS
-------
+== ACCESS
 Any user who has configured an SSH key.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-EXAMPLES
---------
+== EXAMPLES
 
 Find the 2 most recent open changes in the tools/gerrit project:
 ====
@@ -134,8 +132,7 @@
 ====
 
 
-SCHEMA
-------
+== SCHEMA
 The JSON messages consist of nested objects referencing the
 link:json.html#change[change],
 link:json.html#patchSet[patchset],
@@ -145,8 +142,7 @@
 Note that any field may be missing in the JSON messages, so consumers
 of this JSON stream should deal with that appropriately.
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:user-search.html[Query Operators]
 * link:json.html[JSON Data Formats]
@@ -155,3 +151,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-receive-pack.txt b/Documentation/cmd-receive-pack.txt
index 92bb65e..f3b4f02 100644
--- a/Documentation/cmd-receive-pack.txt
+++ b/Documentation/cmd-receive-pack.txt
@@ -1,28 +1,24 @@
-git-receive-pack
-================
+= git-receive-pack
 
-NAME
-----
+== NAME
 git-receive-pack - Receive what is pushed into the repository
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'git receive-pack'
   [--reviewer <address> | --re <address>]
   [--cc <address>]
   <project>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Invoked by 'git push' and updates the project's repository with
 the information fed from the 'git push' end.
 
 End users can supply options to this command by passing them through
 to 'git push', which will relay them automatically.
 
-OPTIONS
--------
+== OPTIONS
 
 <project>::
 	The remote repository that will receive the pushed objects,
@@ -39,12 +35,10 @@
 	Carbon-copy <address> on the created or updated changes.
 	Deprecated, use `refs/for/branch%cc=address` instead.
 
-ACCESS
-------
+== ACCESS
 Any user who has configured an SSH key.
 
-EXAMPLES
---------
+== EXAMPLES
 
 Send a review for a change on the master branch to charlie@example.com:
 =====
@@ -80,11 +74,13 @@
 	git push charlie
 ====
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:user-upload.html[Uploading Changes]
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-rename-group.txt b/Documentation/cmd-rename-group.txt
index e810727..9578458 100644
--- a/Documentation/cmd-rename-group.txt
+++ b/Documentation/cmd-rename-group.txt
@@ -1,40 +1,33 @@
-gerrit rename-group
-===================
+= gerrit rename-group
 
-NAME
-----
+== NAME
 gerrit rename-group - Rename an account group.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit rename-group'
   <GROUP>
   <NEWNAME>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Renames an account group.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the group owning the group to be renamed
 or be a member of the privileged 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <GROUP>::
 	Required; name of the group to be renamed.
 
 <NEWNAME>::
 	Required; new name of the group.
 
-EXAMPLES
---------
+== EXAMPLES
 Rename the group "MyGroup" to "MyCommitters".
 
 ====
@@ -44,3 +37,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index e14daef..510edce 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -1,16 +1,16 @@
 gerrit review
 ==============
 
-NAME
-----
+== NAME
 gerrit review - Verify, approve and/or submit one or more patch sets
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit review'
   [--project <PROJECT> | -p <PROJECT>]
+  [--branch <BRANCH> | -b <BRANCH>]
   [--message <MESSAGE> | -m <MESSAGE>]
+  [--notify <NOTIFYHANDLING> | -n <NOTIFYHANDLING>]
   [--submit | -s]
   [--abandon | --restore]
   [--publish]
@@ -18,9 +18,9 @@
   [--verified <N>] [--code-review <N>]
   [--label Label-Name=<N>]
   {COMMIT | CHANGEID,PATCHSET}...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Updates the current user's approval status of the specified patch
 sets and/or submits them for merging, sending out email
 notifications and updating the database.
@@ -32,10 +32,12 @@
 or abbreviated commit SHA-1 may be used.  If the same commit is available
 in multiple projects the `--project` option may be used to limit where
 Gerrit searches for the change to only the contents of the specified project.
+If the same commit is available in multiple branches the `--branch` option
+may be used to limit where Gerrit searches for changes to only the specified
+branch.
 
 
-OPTIONS
--------
+== OPTIONS
 
 --project::
 -p::
@@ -43,11 +45,29 @@
 	within.  This option must be supplied before the commit
 	SHA-1 in order to take effect.
 
+--branch::
+-b::
+	Name of the branch the intended changes are contained
+	within.  This option must be supplied before the commit
+	SHA-1 in order to take effect.
+
 --message::
 -m::
 	Optional cover letter to include as part of the message
 	sent to reviewers when the approval states are updated.
 
+--notify::
+-n::
+	Who to send email notifications to after the review is stored.
+	This option only applies for storing the review, but not for any
+	other action (abandon, restore etc.) done by this command.
++
+* NONE: send no email
+* OWNER: send email to change owners
+* OWNER_REVIEWERS: send email to change owners and reviewers
+* ALL: send email to all (change owners, reviewers, watchers and any
+  user who has starred the change)
+
 --help::
 -h::
 	Display site-specific usage information, including the
@@ -76,31 +96,37 @@
 
 --code-review::
 --verified::
-        Set the label to the value 'N'.  The exact option names
-        supported and the range of values permitted differs per site,
-        check the output of --help, or contact your site administrator
-        for further details.  These options are only available for these
-        built-in labels; for other labels, see --label.
+	Set the label to the value 'N'.  The exact option names
+	supported and the range of values permitted differs per site,
+	check the output of --help, or contact your site administrator
+	for further details.  These options are only available for the
+	labels that are defined in the 'All-Projects' root project and
+	that are valid for all projects; for other labels, see --label.
+	Votes that are not permitted for the user are silently ignored.
 
 --label::
-        Set a label by name to the value 'N'.
+	Set a label by name to the value 'N'.  Invalid votes (invalid label
+	or invalid value) and votes that are not permitted for the user are
+	silently ignored.
 
-ACCESS
-------
+== ACCESS
 Any user who has configured an SSH key.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-EXAMPLES
---------
+== EXAMPLES
 
 Approve the change with commit c0ff33 as "Verified +1"
 =====
 	$ ssh -p 29418 review.example.com gerrit review --verified +1 c0ff33
 =====
 
+Vote on the project specific label "mylabel":
+=====
+	$ ssh -p 29418 review.example.com gerrit review --label mylabel=+1 c0ff33
+=====
+
 Append the message "Build Successful". Notice two levels of quoting is
 required, one for the local shell, and another for the argument parser
 inside the Gerrit server:
@@ -124,11 +150,13 @@
   $ ssh -p 29418 review.example.com gerrit review --abandon c0ff33
 ====
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:access-control.html[Access Controls]
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-set-account.txt b/Documentation/cmd-set-account.txt
index f9855cd..897d6889 100644
--- a/Documentation/cmd-set-account.txt
+++ b/Documentation/cmd-set-account.txt
@@ -1,21 +1,18 @@
-gerrit set-account
-==================
+= gerrit set-account
 
-NAME
-----
+== NAME
 gerrit set-account - Change an account's settings.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 set-account [--full-name <FULLNAME>] [--active|--inactive] \
             [--add-email <EMAIL>] [--delete-email <EMAIL> | ALL] \
             [--add-ssh-key - | <KEY>] \
             [--delete-ssh-key - | <KEY> | ALL] \
             [--http-password <PASSWORD>] <USER>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Modifies a given user's settings. This command can be useful to
 deactivate an account, set HTTP password, add/delete ssh keys without
 going through the UI.
@@ -23,16 +20,13 @@
 It also allows managing email addresses, which bypasses the
 verification step we force within the UI.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <USER>::
     Required; Full name, email-address, SSH username or account id.
 
@@ -83,8 +77,7 @@
 --http-password::
     Set the HTTP password for the user account.
 
-EXAMPLES
---------
+== EXAMPLES
 Add an email and SSH key to `watcher`'s account:
 
 ====
@@ -94,3 +87,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-set-members.txt b/Documentation/cmd-set-members.txt
index 7524893..174a25a 100644
--- a/Documentation/cmd-set-members.txt
+++ b/Documentation/cmd-set-members.txt
@@ -1,13 +1,10 @@
-gerrit set-members
-==================
+= gerrit set-members
 
-NAME
-----
+== NAME
 gerrit set-members - Set group members
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit set-members'
   [--add USER ...]
   [--remove USER ...]
@@ -15,13 +12,12 @@
   [--exclude GROUP ...]
   [--]
   <GROUP> ...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Set the group members for the specified groups.
 
-OPTIONS
--------
+== OPTIONS
 <GROUP>::
 	Required; name of the group for which the members should be set.
 	The members for multiple groups can be set at once by specifying
@@ -51,16 +47,13 @@
 The `set-members` command is processing the options in the following
 order: `--remove`, `--exclude`, `--add`, `--include`
 
-ACCESS
-------
+== ACCESS
 Any user who has configured an SSH key.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-EXAMPLES
---------
+== EXAMPLES
 
 Add alice and bob, but remove eve from the groups my-committers and
 my-verifiers.
@@ -80,3 +73,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-set-project-parent.txt b/Documentation/cmd-set-project-parent.txt
index 1e7e6c5..70918b2 100644
--- a/Documentation/cmd-set-project-parent.txt
+++ b/Documentation/cmd-set-project-parent.txt
@@ -1,36 +1,30 @@
-gerrit set-project-parent
-=========================
+= gerrit set-project-parent
 
-NAME
-----
+== NAME
 gerrit set-project-parent - Change the project permissions are inherited from.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit set-project-parent'
   [--parent <NAME>]
   [--children-of <NAME>]
   [--exclude <NAME>]
   <NAME> ...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Changes the project that permissions are inherited through.
 Every project inherits permissions from another project, by
 default this is `All-Projects`.  This command sets
 the project to inherit through another one.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 --parent::
 	Name of the parent to inherit through. If not specified,
 	the parent is set back to the default `All-Projects`.
@@ -48,8 +42,7 @@
 	specifying the --exclude option multiple times. Excluding a
 	project that is not a child project has no effect.
 
-EXAMPLES
---------
+== EXAMPLES
 Configure `kernel/omap` to inherit permissions from `kernel/common`:
 
 ====
@@ -63,11 +56,13 @@
 	  --children-of myParent --parent myOtherParent
 ====
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:access-control.html[Access Controls]
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-set-project.txt b/Documentation/cmd-set-project.txt
index af20006..adfc364 100644
--- a/Documentation/cmd-set-project.txt
+++ b/Documentation/cmd-set-project.txt
@@ -1,13 +1,10 @@
-gerrit set-project
-==================
+= gerrit set-project
 
-NAME
-----
+== NAME
 gerrit set-project - Change a project's settings.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit set-project'
   [--description <DESC> | -d <DESC>]
   [--submit-type <TYPE> | -t <TYPE>]
@@ -18,25 +15,22 @@
   [--project-state <STATE> | --ps <STATE>]
   [--max-object-size-limit <N>]
   <NAME>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Modifies a given project's settings. This command can be useful to
 batch change projects.
 
 The command is argument-safe, that is, if no argument is given the
 previous settings are kept intact.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-OPTIONS
--------
+== OPTIONS
 <NAME>::
     Required; name of the project to edit.  If name ends
     with `.git` the suffix will be automatically removed.
@@ -63,7 +57,7 @@
 
 +
 For more details see
-link:project-setup.html#submit_type[Change Submit Actions].
+link:project-setup.html#submit_type[Submit Types].
 
 --content-merge::
     If enabled, Gerrit will try to perform a 3-way merge of text
@@ -104,8 +98,7 @@
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
 
-EXAMPLES
---------
+== EXAMPLES
 Change project `example` to be hidden, require change id, don't use content merge
 and use 'merge if necessary' as merge strategy:
 
@@ -117,3 +110,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-set-reviewers.txt b/Documentation/cmd-set-reviewers.txt
index 32fd35e..4cb7bbd 100644
--- a/Documentation/cmd-set-reviewers.txt
+++ b/Documentation/cmd-set-reviewers.txt
@@ -1,22 +1,19 @@
-gerrit set-reviewers
-====================
+= gerrit set-reviewers
 
-NAME
-----
+== NAME
 gerrit set-reviewers - Add or remove reviewers to a change
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit set-reviewers'
   [--project <PROJECT> | -p <PROJECT>]
   [--add <REVIEWER> ... | -a <REVIEWER> ...]
   [--remove <REVIEWER> ... | -r <REVIEWER> ...]
   [--]
   {COMMIT | CHANGE-ID}...
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Adds or removes reviewers to the specified change, sending email
 notifications when changes are made.
 
@@ -25,8 +22,7 @@
 identifiers, such as '8242' or by complete or abbreviated commit
 SHA-1s.
 
-OPTIONS
--------
+== OPTIONS
 
 --project::
 -p::
@@ -50,16 +46,13 @@
 -h::
 	Display site-specific usage information
 
-ACCESS
-------
+== ACCESS
 Any user who has configured an SSH key.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-EXAMPLES
---------
+== EXAMPLES
 
 Add reviewers alice and bob, but remove eve from change Iac6b2ac2.
 =====
@@ -87,3 +80,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt
index d426508..dd372fe 100644
--- a/Documentation/cmd-show-caches.txt
+++ b/Documentation/cmd-show-caches.txt
@@ -1,21 +1,18 @@
 gerrit show-caches
 ===================
 
-NAME
-----
+== NAME
 gerrit show-caches - Display current cache statistics
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit show-caches' [--gc] [--show-jvm]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Display statistics about the size and hit ratio of in-memory caches.
 
-OPTIONS
--------
+== OPTIONS
 --gc::
 	Request Java garbage collection before displaying information
 	about the Java memory heap.
@@ -29,18 +26,15 @@
 -w::
 	Width of the output table.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group,
 or have been granted
 link:access-control.html#capability_viewCaches[the 'View Caches' global capability].
 
-SCRIPTING
----------
+== SCRIPTING
 Intended for interactive use only.
 
-EXAMPLES
---------
+== EXAMPLES
 
 ====
 	$ ssh -p 29418 review.example.com gerrit show-caches
@@ -73,8 +67,7 @@
 	           0 open files,        6 cpus available,       23 threads
 ====
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:cmd-flush-caches.html[gerrit flush-caches]
 * link:config-gerrit.html#cache[Cache Configuration]
@@ -83,3 +76,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-show-connections.txt b/Documentation/cmd-show-connections.txt
index ab9fadf..a694fb3 100644
--- a/Documentation/cmd-show-connections.txt
+++ b/Documentation/cmd-show-connections.txt
@@ -1,33 +1,27 @@
-gerrit show-connections
-=======================
+= gerrit show-connections
 
-NAME
-----
+== NAME
 gerrit show-connections - Display active client SSH connections
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit show-connections' [--numeric | -n]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Presents a table of the active SSH connections, the users who
 are currently connected to the internal server and performing
 an activity.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group,
 or have been granted
 link:access-control.html#capability_viewConnections[the 'View Connections' global capability].
 
-SCRIPTING
----------
+== SCRIPTING
 Intended for interactive use only.
 
-OPTIONS
--------
+== OPTIONS
 --numeric::
 -n::
 	Show client hostnames as IP addresses instead of DNS hostname.
@@ -37,8 +31,7 @@
 	Do not format the output to the terminal width (default of
 	80 columns).
 
-DISPLAY
--------
+== DISPLAY
 
 Session::
 	Unique session identifier on this server.  Session
@@ -64,8 +57,7 @@
 	Reverse lookup hostname, or if -n option is used, the remote
 	IP address.
 
-EXAMPLES
---------
+== EXAMPLES
 
 With reverse DNS lookup (default):
 ====
@@ -88,3 +80,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-show-queue.txt b/Documentation/cmd-show-queue.txt
index 4ab3097..e3f44ab 100644
--- a/Documentation/cmd-show-queue.txt
+++ b/Documentation/cmd-show-queue.txt
@@ -1,18 +1,15 @@
-gerrit show-queue
-=================
+= gerrit show-queue
 
-NAME
-----
+== NAME
 gerrit show-queue - Display the background work queues, including replication
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit show-queue'
 'ssh' -p <port> <host> 'ps'
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Presents a table of the pending activity the Gerrit daemon
 is currently performing, or will perform in the near future.
 Gerrit contains an internal scheduler, similar to cron, that it
@@ -22,8 +19,7 @@
 once they enter this state, but it can be possible to observe tasks
 in these states.
 
-ACCESS
-------
+== ACCESS
 End-users may see a task in the queue only if they can also see
 the project the task is associated with. Tasks operating on other
 projects, or that do not have a specific project are hidden.
@@ -33,19 +29,16 @@
 link:access-control.html#capability_viewQueue[the 'View Queue' capability]
 can see all queue entries.
 
-SCRIPTING
----------
+== SCRIPTING
 Intended for interactive use only.
 
-OPTIONS
--------
+== OPTIONS
 --wide::
 -w::
 	Do not format the output to the terminal width (default of
 	80 columns).
 
-DISPLAY
--------
+== DISPLAY
 
 Task::
 	Unique task identifier on this server.	May be passed into
@@ -70,8 +63,7 @@
 	Short text description of the task that will be performed
 	at the given time.
 
-EXAMPLES
---------
+== EXAMPLES
 
 The following queue contains two tasks scheduled to replicate the
 `tools/gerrit.git` project to two different remote systems, `dst1`
@@ -90,3 +82,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 2a3265e..68e796a 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -1,17 +1,14 @@
-gerrit stream-events
-====================
+= gerrit stream-events
 
-NAME
-----
+== NAME
 gerrit stream-events - Monitor events occurring in real time
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit stream-events'
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 
 Provides a portal into the major events occurring on the server,
 outputting activity data in real-time to the client.  Events are
@@ -21,18 +18,15 @@
 
 Event output is in JSON, one event per line.
 
-ACCESS
-------
+== ACCESS
 Caller must be a member of the privileged 'Administrators' group,
 or have been granted
 link:access-control.html#capability_streamEvents[the 'Stream Events' global capability].
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-EXAMPLES
---------
+== EXAMPLES
 
 ====
   $ ssh -p 29418 review.example.com gerrit stream-events
@@ -40,8 +34,7 @@
   {"type":"comment-added",change:{"project":"tools/gerrit", ...}, ...}
 ====
 
-SCHEMA
-------
+== SCHEMA
 The JSON messages consist of nested objects referencing the *change*,
 *patchSet*, *account* involved, and other attributes as appropriate.
 The currently supported message types are *patchset-created*,
@@ -53,10 +46,8 @@
 this JSON stream should deal with that appropriately.
 
 [[events]]
-Events
-~~~~~~
-Patchset Created
-^^^^^^^^^^^^^^^^
+=== Events
+==== Patchset Created
 type:: "patchset-created"
 
 change:: link:json.html#change[change attribute]
@@ -65,8 +56,7 @@
 
 uploader:: link:json.html#account[account attribute]
 
-Draft Published
-^^^^^^^^^^^^^^^
+==== Draft Published
 type:: "draft-published"
 
 change:: link:json.html#change[change attribute]
@@ -75,8 +65,7 @@
 
 uploader:: link:json.html#account[account attribute]
 
-Change Abandoned
-^^^^^^^^^^^^^^^^
+==== Change Abandoned
 type:: "change-abandoned"
 
 change:: link:json.html#change[change attribute]
@@ -87,8 +76,7 @@
 
 reason:: Reason for abandoning the change.
 
-Change Restored
-^^^^^^^^^^^^^^^
+==== Change Restored
 type:: "change-restored"
 
 change:: link:json.html#change[change attribute]
@@ -99,8 +87,7 @@
 
 reason:: Reason for restoring the change.
 
-Change Merged
-^^^^^^^^^^^^^
+==== Change Merged
 type:: "change-merged"
 
 change:: link:json.html#change[change attribute]
@@ -109,8 +96,7 @@
 
 submitter:: link:json.html#account[account attribute]
 
-Merge Failed
-^^^^^^^^^^^^
+==== Merge Failed
 type:: "merge-failed"
 
 change:: link:json.html#change[change attribute]
@@ -121,8 +107,7 @@
 
 reason:: Reason that the merge failed.
 
-Comment Added
-^^^^^^^^^^^^^
+==== Comment Added
 type:: "comment-added"
 
 change:: link:json.html#change[change attribute]
@@ -135,16 +120,14 @@
 
 comment:: Comment text author had written
 
-Ref Updated
-^^^^^^^^^^^
+==== Ref Updated
 type:: "ref-updated"
 
 submitter:: link:json.html#account[account attribute]
 
 refUpdate:: link:json.html#refUpdate[refUpdate attribute]
 
-Reviewer Added
-^^^^^^^^^^^^^^
+==== Reviewer Added
 type:: "reviewer-added"
 
 change:: link:json.html#change[change attribute]
@@ -153,8 +136,7 @@
 
 reviewer:: link:json.html#account[account attribute]
 
-Topic Changed
-^^^^^^^^^^^^^
+==== Topic Changed
 type:: "topic-changed"
 
 change:: link:json.html#change[change attribute]
@@ -163,8 +145,7 @@
 
 oldTopic:: Topic name before it was changed.
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:json.html[JSON Data Formats]
 * link:access-control.html[Access Controls]
@@ -172,3 +153,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-suexec.txt b/Documentation/cmd-suexec.txt
index 78fc361..f6ee753 100644
--- a/Documentation/cmd-suexec.txt
+++ b/Documentation/cmd-suexec.txt
@@ -1,13 +1,10 @@
-suexec
-======
+= suexec
 
-NAME
-----
+== NAME
 suexec - Execute a command as any registered user account
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port>
   -i SITE_PATH/etc/ssh_host_rsa_key
   '"Gerrit Code Review@localhost"'
@@ -16,9 +13,9 @@
   [--from HOST:PORT]
   [--]
   [COMMAND]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 The suexec command permits executing any other command as any other
 registered user account.
 
@@ -27,8 +24,7 @@
 capability. The run as capability is permitted to be used only if
 link:config-gerrit.html[auth.enableRunAs] is true.
 
-OPTIONS
--------
+== OPTIONS
 
 --as::
 	Email address of the user you want to impersonate.
@@ -40,18 +36,15 @@
 COMMAND::
 	Gerrit command you want to run.
 
-ACCESS
-------
+== ACCESS
 Caller must be the magic user Gerrit Code Review using the SSH
 daemon's host key, or a key on this daemon's peer host key ring,
 or a user granted the Run As capability.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-EXAMPLES
---------
+== EXAMPLES
 
 Approve the change with commit c0ff33 as "Verified +1" as user bob@example.com
 =====
@@ -67,3 +60,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-test-submit-rule.txt b/Documentation/cmd-test-submit-rule.txt
index ae68b80..a9a1bc4 100644
--- a/Documentation/cmd-test-submit-rule.txt
+++ b/Documentation/cmd-test-submit-rule.txt
@@ -1,36 +1,30 @@
-gerrit test-submit rule
-=======================
+= gerrit test-submit rule
 
-NAME
-----
+== NAME
 gerrit test-submit rule - Test prolog submit rules with a chosen changeset.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit test-submit rule'
   [-s]
   [--no-filters]
   CHANGE
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Provides a way to test prolog link:prolog-cookbook.html[submit rules].
 
-OPTIONS
--------
+== OPTIONS
 -s::
 	Reads a rules.pl file from stdin instead of rules.pl in refs/meta/config.
 
 --no-filters::
 	Don't run the submit_filter/2 from the parent projects of the specified change.
 
-ACCESS
-------
+== ACCESS
 Can be used by anyone that has permission to read the specified changeset.
 
-EXAMPLES
---------
+== EXAMPLES
 
 Test submit_rule from stdin and return the results as JSON.
 ====
@@ -59,10 +53,12 @@
  ]
 ====
 
-SCRIPTING
----------
+== SCRIPTING
 Can be used either interactively for testing new prolog submit rules, or from a script to check the submit status of a change.
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-test-submit-type.txt b/Documentation/cmd-test-submit-type.txt
index f6d5fba..658d43b 100644
--- a/Documentation/cmd-test-submit-type.txt
+++ b/Documentation/cmd-test-submit-type.txt
@@ -1,36 +1,30 @@
-gerrit test-submit type
-=======================
+= gerrit test-submit type
 
-NAME
-----
+== NAME
 gerrit test-submit type - Test prolog submit type with a chosen change.
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit test-submit type'
   [-s]
   [--no-filters]
   CHANGE
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Provides a way to test prolog submit type.
 
-OPTIONS
--------
+== OPTIONS
 -s::
 	Reads a rules.pl file from stdin instead of rules.pl in refs/meta/config.
 
 --no-filters::
 	Don't run the submit_type_filter/2 from the parent projects of the specified change.
 
-ACCESS
-------
+== ACCESS
 Can be used by anyone that has permission to read the specified change.
 
-EXAMPLES
---------
+== EXAMPLES
 
 Test submit_type from stdin and return the submit type.
 ====
@@ -44,10 +38,12 @@
  "MERGE_IF_NECESSARY"
 ====
 
-SCRIPTING
----------
+== SCRIPTING
 Can be used either interactively for testing new prolog submit type, or from a script to check the submit type of a change.
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/cmd-version.txt b/Documentation/cmd-version.txt
index aa08848..d5c2263 100644
--- a/Documentation/cmd-version.txt
+++ b/Documentation/cmd-version.txt
@@ -1,17 +1,14 @@
-gerrit version
-==============
+= gerrit version
 
-NAME
-----
+== NAME
 gerrit version - Show the version of the currently executing Gerrit server
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'ssh' -p <port> <host> 'gerrit version'
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Displays a one-line response with the string `gerrit version` followed
 by the currently executing version of Gerrit.
 
@@ -27,16 +24,13 @@
 describe` documentation for details on how `<tagname>` is chosen and how
 `<n>` is computed.
 
-ACCESS
-------
+== ACCESS
 Any user who has configured an SSH key.
 
-SCRIPTING
----------
+== SCRIPTING
 This command is intended to be used in scripts.
 
-EXAMPLES
---------
+== EXAMPLES
 
 =====
 	$ ssh -p 29418 review.example.com gerrit version
@@ -46,3 +40,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-auto-site-initialization.txt b/Documentation/config-auto-site-initialization.txt
index 4c204fd..abd8d8f 100644
--- a/Documentation/config-auto-site-initialization.txt
+++ b/Documentation/config-auto-site-initialization.txt
@@ -1,13 +1,14 @@
-Gerrit Code Review - Automatic Site Initialization on Startup
-=============================================================
+= Gerrit Code Review - Automatic Site Initialization on Startup
 
-Description
------------
+== Description
 
 Gerrit supports automatic site initialization on server startup
 when Gerrit runs in a servlet container. Both creation of a new site
-and upgrade of an existing site are supported. Installation of
-plugins during the site creation/initialization is not yet supported.
+and upgrade of an existing site are supported. By default, all packaged
+plugins will be installed when Gerrit is deployed in a servlet container
+and the location of the Gerrit distribution can be determined at
+runtime. It is also possible to install only a subset of packaged
+plugins or not install any plugin.
 
 This feature may be useful for such setups where Gerrit administrators
 don't have direct access to the database and the file system of the
@@ -16,8 +17,7 @@
 server. It may also make deployment and testing in a local servlet
 container faster to setup as the init step could be skipped.
 
-Gerrit Configuration
---------------------
+== Gerrit Configuration
 
 The site initialization will be performed only if the `gerrit.init`
 system property exists (the value of the property is not used, only the
@@ -40,8 +40,24 @@
 connectivity, also for this case, is defined by the `jdbc/ReviewDb`
 JNDI property.
 
-Example 1
-~~~~~~~~~
+[WARNING]
+Defining the `jdbc/ReviewDb` JNDI property for an H2 database under the
+path defined by either `gerrit.site_path` or `gerrit.init_path` will
+cause an incomplete auto initialization and Gerrit will fail to start.
+Opening a connection to such database will create a subfolder under the
+site path folder (in order to create the H2 database) and Gerrit will
+not any more consider that site path to be new and, because of that,
+skip some required initialization steps (for example, Lucene index
+creation). In order to auto initialize Gerrit with an embedded H2
+database use the `gerrit.site_path` to define the location of the review
+site and don't define a JNDI resource with a URL under that path.
+
+If the 'gerrit.install_plugins' property is not defined then all packaged
+plugins will be installed. If it is defined then it is parsed as a
+comma separated list of plugin names to install. If the value is an
+empty string then no plugin will be installed.
+
+=== Example 1
 
 Prepare Tomcat so that a site is initialized at a given path using
 the H2 database (if the site doesn't exist yet) or using whatever
@@ -52,8 +68,7 @@
   $ catalina.sh start
 ----
 
-Example 2
-~~~~~~~~~
+=== Example 2
 
 Prepare Tomcat so that an existing site with the path defined in the
 `system_config` table is initialized (upgraded) on Gerrit startup. The
@@ -65,8 +80,7 @@
   $ catalina.sh start
 ----
 
-Example 3
-~~~~~~~~~
+=== Example 3
 
 Assuming the database schema doesn't exist in the database defined
 via the `jdbc/ReviewDb` JNDI property, initialize a new site using that
@@ -80,3 +94,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-cla.txt b/Documentation/config-cla.txt
index 6404d4e..624135c 100644
--- a/Documentation/config-cla.txt
+++ b/Documentation/config-cla.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Contributor Agreements
-===========================================
+= Gerrit Code Review - Contributor Agreements
 
 Users can be required to sign one or more contributor agreements before
 being able to submit a change in a project.
@@ -80,3 +79,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-contact.txt b/Documentation/config-contact.txt
index 4d8851f..58df8ea 100644
--- a/Documentation/config-contact.txt
+++ b/Documentation/config-contact.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Contact Information
-========================================
+= Gerrit Code Review - Contact Information
 
 To help ensure contributor privacy, but still support gathering of
 contributor agreements as necessary, Gerrit encrypts all offline
@@ -12,8 +11,7 @@
 Gerrit will not collect contact information from users.
 
 
-Setup
------
+== Setup
 
 Ensure Bouncy Castle Crypto API is available in the web application's
 CLASSPATH (e.g. in `'JETTY_HOME'/lib/plus` for Jetty).  Gerrit needs
@@ -85,8 +83,7 @@
 ====
 
 
-Contact Store Protocol
-----------------------
+== Contact Store Protocol
 
 To implement a new contact store, the following details are useful.
 
@@ -135,8 +132,7 @@
 prevents man-in-the-middle attacks from reading the shared secret
 APPSEC token, or messing with the data field.
 
-Data Format
-~~~~~~~~~~~
+=== Data Format
 
 Once decrypted the `data` field looks something like the following:
 
@@ -213,3 +209,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 038d946..27feee2 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1,8 +1,6 @@
-Gerrit Code Review - Configuration
-==================================
+= Gerrit Code Review - Configuration
 
-File `etc/gerrit.config`
-------------------------
+== File `etc/gerrit.config`
 
 The optional file `'$site_path'/etc/gerrit.config` is a Git-style
 config file that controls many host specific settings for Gerrit.
@@ -21,8 +19,8 @@
   directory = /var/cache/gerrit2
 ----
 
-[[accounts]]Section accounts
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[accounts]]
+=== Section accounts
 
 [[accounts.visibility]]accounts.visibility::
 +
@@ -42,8 +40,8 @@
 +
 Default is `ALL`.
 
-[[addreviewer]]Section addreviewer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[addreviewer]]
+=== Section addreviewer
 
 [[addreviewer.maxWithoutConfirmation]]addreviewer.maxWithoutConfirmation::
 +
@@ -69,8 +67,8 @@
 +
 Default is 20.
 
-[[auth]]Section auth
-~~~~~~~~~~~~~~~~~~~~
+[[auth]]
+=== Section auth
 
 See also link:config-sso.html[SSO configuration].
 
@@ -424,12 +422,16 @@
 [[auth.gitBasicAuth]]auth.gitBasicAuth::
 +
 If true then Git over HTTP and HTTP/S traffic is authenticated using
-standard BasicAuth and credentials validated using the same auth
-method configured for Gerrit Web UI.
+standard BasicAuth and the credentials are validated using the same
+auth method as configured for the Gerrit Web UI.
 +
-This parameter only affects git over http traffic. If set to false
-then Gerrit will authenticate through DIGEST authentication and
-the randomly generated HTTP password in Gerrit DB.
+This parameter affects git over HTTP traffic and access to the REST
+API. If set to false then Gerrit will authenticate through DIGEST
+authentication and the randomly generated HTTP password in the Gerrit
+database.
++
+When `auth.type` is `LDAP`, service users that only exist in the Gerrit
+database are still authenticated by their HTTP passwords.
 +
 By default this is set to false.
 
@@ -464,8 +466,8 @@
 +
 Default is true.
 
-[[cache]]Section cache
-~~~~~~~~~~~~~~~~~~~~~~
+[[cache]]
+=== Section cache
 
 [[cache.directory]]cache.directory::
 +
@@ -542,8 +544,7 @@
 +
 If 0, disk storage for the cache is disabled.
 
-[[cache_names]]Standard Caches
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== [[cache_names]]Standard Caches
 
 cache `"accounts"`::
 +
@@ -705,10 +706,9 @@
 
 See also link:cmd-flush-caches.html[gerrit flush-caches].
 
-[[cache_options]]Cache Options
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== [[cache_options]]Cache Options
 
-cache.diff_intraline.maxIdleWorkers::
+[[cache.diff_intraline.maxIdleWorkers]]cache.diff_intraline.maxIdleWorkers::
 +
 Number of idle worker threads to maintain for the intraline difference
 computations.  There is no upper bound on how many concurrent requests
@@ -717,7 +717,7 @@
 +
 Default is 1.5x number of available CPUs.
 
-cache.diff_intraline.timeout::
+[[cache.diff_intraline.timeout]]cache.diff_intraline.timeout::
 +
 Maximum number of milliseconds to wait for intraline difference data
 before giving up and disabling it for a particular file pair.  This is
@@ -740,7 +740,7 @@
 +
 Default is 5 seconds.
 
-cache.diff_intraline.enabled::
+[[cache.diff_intraline.enabled]]cache.diff_intraline.enabled::
 +
 Boolean to enable or disable the computation of intraline differences
 when populating a diff cache entry.  This flag is provided primarily
@@ -751,7 +751,7 @@
 +
 Default is true, enabled.
 
-cache.projects.checkFrequency::
+[[cache.projects.checkFrequency]]cache.projects.checkFrequency::
 +
 How often project configuration should be checked for update from Git.
 Gerrit Code Review caches project access rules and configuration in
@@ -767,8 +767,19 @@
 +
 Default is 5 minutes.
 
-[[change]]Section change
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[change]]
+=== Section change
+
+[[change.largeChange]]change.largeChange::
++
+Number of changed lines from which on a change is considered as a large
+change. The number of changed lines of a change is the sum of the lines
+that were inserted and deleted in the change.
++
+The specified value is used to visualize the change sizes in the web UI
+in change tables and user dashboards.
++
+By default 500.
 
 [[change.updateDelay]]change.updateDelay::
 +
@@ -789,10 +800,17 @@
 +
 Default is 30 seconds.
 
-[[changeMerge]]Section changeMerge
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[change.allowDrafts]]change.allowDrafts::
++
+Allow drafts workflow. If set to false, drafts cannot be created,
+deleted or published.
++
+Default is true.
 
-changeMerge.checkFrequency::
+[[changeMerge]]
+=== Section changeMerge
+
+[[changeMerge.checkFrequency]]changeMerge.checkFrequency::
 +
 How often the database should be rescanned for changes that have been
 submitted but not merged due to transient errors. Values can be
@@ -802,17 +820,25 @@
 +
 Default is 300 seconds (5 minutes).
 
-changeMerge.test::
+[[changeMerge.threadPoolSize]]changeMerge.threadPoolSize::
 +
-Controls whether or not the mergeability test of changes is
-enabled.  If enabled, when the change page is loaded, the test is
-triggered. The submit button will be enabled or disabled according to
-the result.
+Maximum size of the thread pool in which the mergeability flag of open
+changes is updated.
 +
-By default this is false (test is not enabled).
+Default is 1.
 
-[[commentlink]]Section commentlink
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[changeMerge.interactiveThreadPoolSize]]changeMerge.interactiveThreadPoolSize::
++
+Maximum size of the thread pool in which the mergeability flag of open
+changes is updated, when processing interactive user requests (e.g.
+pushes to refs/for/*). Set to 0 or negative to share the pool for
+background mergeability checks.
++
+Default is 1.
+
+[[commentlink]]
+=== Section commentlink
+
 Comment links are find/replace strings applied to change descriptions,
 patch comments, in-line code comments and approval category value descriptions
 to turn set strings into hyperlinks.  One common use is for linking to
@@ -829,7 +855,7 @@
 ----
 [commentlink "changeid"]
   match = (I[0-9a-f]{8,40})
-  link = "#q,$1,n,z"
+  link = "#q,$1"
 
 [commentlink "bugzilla"]
   match = "(bug\\s+#?)(\\d+)"
@@ -891,13 +917,15 @@
 user-supplied and thus can be verified to be XSS-free, but are only
 enabled for a subset of projects.
 +
+By default, true.
++
 Note that the names and contents of disabled sections are visible even
 to anonymous users via the
 link:rest-api-projects.html#get-config[REST API].
 
 
-[[contactstore]]Section contactstore
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[contactstore]]
+=== Section contactstore
 
 [[contactstore.url]]contactstore.url::
 +
@@ -912,8 +940,8 @@
 Shared secret of the web based contact store.
 
 
-[[container]]Section container
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[container]]
+=== Section container
 
 These settings are applied only if Gerrit is started as the container
 process through Gerrit's 'gerrit.sh' rc.d compatible wrapper script.
@@ -968,8 +996,8 @@
 '$HOME/gerrit.war'.
 
 
-[[core]]Section core
-~~~~~~~~~~~~~~~~~~~~
+[[core]]
+=== Section core
 
 [[core.packedGitWindowSize]]core.packedGitWindowSize::
 +
@@ -1075,8 +1103,8 @@
 +
 Default is false, but in a future release may default to true.
 
-[[database]]Section database
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[database]]
+=== Section database
 
 The database section configures where Gerrit stores its metadata
 records about user accounts and change reviews.
@@ -1213,8 +1241,15 @@
 This setting only applies if
 <<database.connectionPool,database.connectionPool>> is true.
 
-[[download]]Section download
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[database.dataSourceInterceptorClass]]database.dataSourceInterceptorClass::
+
+Class that implements DataSourceInterceptor interface to monitor SQL activity.
+This class must have default constructor and be available on Gerrit's bootstrap
+classpath, e. g. in `$gerrit_site/lib` directory. Example implementation of
+SQL monitoring can be found in javamelody-plugin.
+
+[[download]]
+=== Section download
 
 ----
 [download]
@@ -1294,8 +1329,8 @@
 If `download.scheme` is not specified, SSH, HTTP and Anonymous HTTP
 downloads are allowed.
 
-[[gerrit]]Section gerrit
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[gerrit]]
+=== Section gerrit
 
 [[gerrit.basePath]]gerrit.basePath::
 +
@@ -1369,13 +1404,19 @@
 Code Review's own bug tracker but could be directed to the system
 administrator's ticket queue.
 
+[[gerrit.reportBugText]]gerrit.reportBugText::
++
+Text to be displayed in the link to the bug report URL.
++
+Defaults to "Report Bug".
+
 [[gerrit.changeScreen]]gerrit.changeScreen::
 +
 Default change screen UI to direct users to. Valid values are
-`OLD_UI` and `CHANGE_SCREEN2`. Default is `OLD_UI`.
+`OLD_UI` and `CHANGE_SCREEN2`. Default is `CHANGE_SCREEN2`.
 
-[[gitweb]]Section gitweb
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[gitweb]]
+=== Section gitweb
 
 Gerrit can forward requests to either an internally managed gitweb
 (which allows Gerrit to enforce some access controls), or to an
@@ -1430,6 +1471,25 @@
 Valid replacements are `${project}` for the project name in Gerrit
 and `${branch}` for the name of the branch.
 
+[[gitweb.roottree]]gitweb.roottree::
++
+Optional pattern to use for constructing the gitweb URL when pointing
+at the contents of the root tree in a specific commit when `custom` is
+used above.
++
+Valid replacements are `${project}` for the project name in Gerrit
+and `${commit}` for the SHA1 hash for the commit.
+
+[[gitweb.file]]gitweb.file::
++
+Optional pattern to use for constructing the gitweb URL when pointing
+at the contents of a file in a specific commit when `custom` is used
+above.
++
+Valid replacements are `${project}` for the project name in Gerrit,
+`${file}` for the file name and `${commit}` for the SHA1 hash for
+the commit.
+
 [[gitweb.filehistory]]gitweb.filehistory::
 +
 Optional pattern to use for constructing the gitweb URL when pointing
@@ -1487,8 +1547,8 @@
 +
 Valid values are "true" and "false," default is "true".
 
-[[groups]]Section groups
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[groups]]
+=== Section groups
 
 [[groups.newGroupsVisibleToAll]]groups.newGroupsVisibleToAll::
 +
@@ -1497,8 +1557,8 @@
 +
 By default, false.
 
-[[hooks]]Section hooks
-~~~~~~~~~~~~~~~~~~~~~~
+[[hooks]]
+=== Section hooks
 
 See also link:config-hooks.html[Hooks].
 
@@ -1571,8 +1631,8 @@
 Optional timeout value in seconds for synchronous hooks, if not specified
 then 30 seconds will be used.
 
-[[http]]Section http
-~~~~~~~~~~~~~~~~~~~~
+[[http]]
+=== Section http
 
 [[http.proxy]]http.proxy::
 +
@@ -1593,8 +1653,8 @@
 appear in the http.proxy property above.
 
 
-[[httpd]]Section httpd
-~~~~~~~~~~~~~~~~~~~~~~
+[[httpd]]
+=== Section httpd
 
 The httpd section configures the embedded servlet container.
 
@@ -1725,6 +1785,9 @@
 If enabled, an NCSA combined log format request log file is written
 out by the internal HTTP daemon.
 +
+`log4j.appender` with the name `httpd_log` can be configured to overwrite
+programmatic configuration.
++
 By default, true if httpd.listenUrl uses http:// or https://,
 and false if httpd.listenUrl uses proxy-http:// or proxy-https://.
 
@@ -1753,7 +1816,7 @@
 +
 Maximum number of client connections which can enter the worker
 thread pool waiting for a worker thread to become available.
-0 disables the queue and permits infinite number of connections.
+0 sets the queue size to the Integer.MAX_VALUE.
 +
 By default 50.
 
@@ -1823,8 +1886,8 @@
 If the file doesn't exist or can't be read the default robots.txt file
 bundled with the .war will be used instead.
 
-[[index]]Section index
-~~~~~~~~~~~~~~~~~~~~~~
+[[index]]
+=== Section index
 
 The index section configures the secondary index.
 
@@ -1846,15 +1909,11 @@
 +
 * `SOLR`
 +
-A link:http://lucene.apache.org/solr/[Solr] index is used.
-+
-* `SQL`
-+
-No secondary index.  Not all query operators are supported.  Other
-query operators are routed through the standard SQL query engine.
+A link:https://cwiki.apache.org/confluence/display/solr/SolrCloud[
+SolrCloud] index is used.
 
 +
-By default, `SQL`.
+By default, `LUCENE`.
 
 [[index.threads]]index.threads::
 +
@@ -1862,6 +1921,12 @@
 +
 Defaults to 1 if not set, or set to a negative value.
 
+[[index.url]]index.url::
++
+Only used when the type is `SOLR`.
++
+URL of the index server.
+
 [[index.name.ramBufferSize]]index.name.ramBufferSize::
 +
 Only used when the type is `LUCENE`.
@@ -1921,8 +1986,8 @@
   maxBufferedDocs = 500
 ----
 
-[[ldap]]Section ldap
-~~~~~~~~~~~~~~~~~~~~
+[[ldap]]
+=== Section ldap
 
 LDAP integration is only enabled if `auth.type` is set to
 `HTTP_LDAP`, `LDAP` or `CLIENT_SSL_CERT_LDAP`.  See above for a
@@ -2208,8 +2273,8 @@
 must have the DWORD value `allowtgtsessionkey` set to 1 and the account must not
 have local administrator privileges.
 
-[[mimetype]]Section mimetype
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[mimetype]]
+=== Section mimetype
 
 [[mimetype.name.safe]]mimetype.<name>.safe::
 +
@@ -2237,8 +2302,9 @@
 ----
 
 
-[[pack]]Section pack
-~~~~~~~~~~~~~~~~~~~~
+[[pack]]
+=== Section pack
+
 Global settings controlling how Gerrit Code Review creates pack
 streams for Git clients running clone, fetch, or pull.  Most of these
 variables are per-client request, and thus should be carefully set
@@ -2262,8 +2328,8 @@
 By default, 1.
 
 
-[[plugins]]Section plugins
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[plugins]]
+=== Section plugins
 
 [[plugins.checkFrequency]]plugins.checkFrequency::
 +
@@ -2277,9 +2343,16 @@
 +
 Default is 1 minute.
 
+[[plugins.allowRemoteAdmin]]plugins.allowRemoteAdmin::
++
+Enable remote installation, enable and disable of plugins over HTTP
+and SSH.  If set to true Administrators can install new plugins
+remotely, or disable existing plugins.  Defaults to false.
 
-[[receive]]Section receive
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+[[receive]]
+=== Section receive
+
 This section is used to set who can execute the 'receive-pack' and
 to limit the maximum Git object size that 'receive-pack' will accept.
 'receive-pack' is what runs on the server during a user's push or
@@ -2379,8 +2452,9 @@
 is assumed.
 
 
-[[repository]]Section repository
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[repository]]
+=== Section repository
+
 Repositories in this sense are the same as projects.
 
 In the following example configuration `Registered Users` is set
@@ -2395,14 +2469,22 @@
 Currently only the repository name `*` is supported.
 This is a wildcard designating all repositories.
 
+[[repository.name.defaultSubmitType]]repository.<name>.defaultSubmitType::
++
+The default submit type for newly created projects. Supported values
+are `MERGE_IF_NECESSARY`, `FAST_FORWARD_ONLY`, `REBASE_IF_NECESSARY`,
+`MERGE_ALWAYS` and `CHERRY_PICK`.
++
+By default, `MERGE_IF_NECESSARY`.
+
 [[repository.name.ownerGroup]]repository.<name>.ownerGroup::
 +
 A name of a group which exists in the database. Zero, one or many
 groups are allowed.  Each on its own line.  Groups which don't exist
 in the database are ignored.
 
-[[rules]]Section rules
-~~~~~~~~~~~~~~~~~~~~~~
+[[rules]]
+=== Section rules
 
 [[rules.enable]]rules.enable::
 +
@@ -2412,8 +2494,8 @@
 +
 Default is true, to execute project specific rules.
 
-[[execution]]Section execution
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[execution]]
+=== Section execution
 
 [[execution.defaultThreadPoolSize]]execution.defaultThreadPoolSize::
 +
@@ -2422,8 +2504,8 @@
 +
 Default is 1.
 
-[[sendemail]]Section sendemail
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[sendemail]]
+=== Section sendemail
 
 [[sendemail.enable]]sendemail.enable::
 +
@@ -2571,16 +2653,8 @@
 By default, unset, so no Expiry-Date header is generated.
 
 
-[[site]]Section site
-~~~~~~~~~~~~~~~~~~~~
-
-[[site.checkUserAgent]]site.checkUserAgent::
-+
-If true the server checks the User-Agent HTTP header and sends the
-correct JavaScript to the client as part of the initial page load.
-This usually reduces a round-trip for the client, allowing the UI to
-start more quickly. If false, a tiny JavaScript loader is sent to the
-client instead to determine the correct code to use. Default is true.
+[[site]]
+=== Section site
 
 [[site.refreshHeaderFooter]]site.refreshHeaderFooter::
 +
@@ -2594,8 +2668,8 @@
 and text results for changes. If false, the URL is disabled and
 returns 404 to clients. Default is true, enabling `/query`.
 
-[[ssh-alias]] Section ssh-alias
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[ssh-alias]]
+=== Section ssh-alias
 
 Variables in section ssh-alias permit the site administrator to alias
 another command from Gerrit or a plugin into the `gerrit` command
@@ -2606,8 +2680,8 @@
   replicate = replication start
 ----
 
-[[sshd]] Section sshd
-~~~~~~~~~~~~~~~~~~~~~
+[[sshd]]
+=== Section sshd
 
 [[sshd.listenAddress]]sshd.listenAddress::
 +
@@ -2805,10 +2879,13 @@
 Enable (or disable) the `'$site_path'/logs/sshd_log` request log.
 If enabled, a request log file is written out by the SSH daemon.
 +
+`log4j.appender` with the name `sshd_log` can be configured to overwrite
+programmatic configuration.
++
 By default, true.
 
-[[suggest]] Section suggest
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[suggest]]
+=== Section suggest
 
 [[suggest.accounts]]suggest.accounts::
 +
@@ -2834,8 +2911,8 @@
 +
 By default 0.
 
-[[theme]] Section theme
-~~~~~~~~~~~~~~~~~~~~~~~
+[[theme]]
+=== Section theme
 
 [[theme.backgroundColor]]theme.backgroundColor::
 +
@@ -2932,18 +3009,15 @@
   backgroundColor = FFFFFF
 ----
 
-[[trackingid]] Section trackingid
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[trackingid]]
+=== Section trackingid
 
 Tagged footer lines containing references to external
 tracking systems, parsed out of the commit message and
 saved in Gerrit's database.
 
 After making changes to this section, existing changes
-must be reindexed with link:pgm-reindex.html[reindex]
-if index.type is `LUCENE` or `SOLR`; or with
-link:pgm-ScanTrackingIds.html[ScanTrackingIds] if index.type
-is unset or `SQL`.
+must be reindexed with link:pgm-reindex.html[reindex].
 
 The tracking ids are searchable using tr:<tracking id> or
 bug:<tracking id>.
@@ -2988,8 +3062,8 @@
 It is possible to have several trackingid entries for the same
 tracking system.
 
-[[transfer]] Section transfer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+[[transfer]]
+=== Section transfer
 
 [[transfer.timeout]]transfer.timeout::
 +
@@ -3006,8 +3080,9 @@
 Defaults to 0 seconds, wait indefinitely.
 
 
-[[upload]]Section upload
-~~~~~~~~~~~~~~~~~~~~~~~~
+[[upload]]
+=== Section upload
+
 Sets the group of users allowed to execute 'upload-pack' on the
 server, 'upload-pack' is what runs on the server during a user's
 fetch, clone or repo sync command.
@@ -3027,8 +3102,8 @@
 'upload-pack' on the server.
 
 
-[[user]] Section user
-~~~~~~~~~~~~~~~~~~~~~
+[[user]]
+=== Section user
 
 [[user.name]]user.name::
 +
@@ -3055,8 +3130,7 @@
 By default "Anonymous Coward" is used.
 
 
-File `etc/secure.config`
-------------------------
+== File `etc/secure.config`
 The optional file `'$site_path'/etc/secure.config` overrides (or
 supplements) the settings supplied by `'$site_path'/etc/gerrit.config`.
 The file should be readable only by the daemon process and can be
@@ -3086,8 +3160,7 @@
   password = s3kr3t
 ----
 
-File `etc/peer_keys`
---------------------
+== File `etc/peer_keys`
 
 The optional file `'$site_path'/etc/peer_keys` controls who can
 login as the 'Gerrit Code Review' user, required for the link:cmd-suexec.html[suexec]
@@ -3096,8 +3169,7 @@
 The format is one Base-64 encoded public key per line.
 
 
-Database system_config
-----------------------
+== Database system_config
 
 Several columns in the `system_config` table within the metadata
 database may be set to control how Gerrit behaves.
@@ -3107,8 +3179,7 @@
 by Gerrit.  If you modify any columns in this table, Gerrit needs
 to be restarted before it will use the new values.
 
-Configurable Parameters
-~~~~~~~~~~~~~~~~~~~~~~~
+=== Configurable Parameters
 
 site_path::
 +
@@ -3125,3 +3196,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-gitweb.txt b/Documentation/config-gitweb.txt
index 7ba15b8..fb7c961 100644
--- a/Documentation/config-gitweb.txt
+++ b/Documentation/config-gitweb.txt
@@ -1,12 +1,10 @@
-Gitweb Integration
-------------------
+== Gitweb Integration
 
 Gerrit Code Review can manage and generate hyperlinks to gitweb,
 allowing users to jump from Gerrit content to the same information,
 but shown by gitweb.
 
-Internal/Managed gitweb
-~~~~~~~~~~~~~~~~~~~~~~~
+=== Internal/Managed gitweb
 
 In the internal configuration, Gerrit inspects the request, enforces
 its project level access controls, and directly executes `gitweb.cgi`
@@ -37,16 +35,14 @@
 After updating `'$site_path'/etc/gerrit.config`, the Gerrit server must
 be restarted and clients must reload the host page to see the change.
 
-Configuration
-^^^^^^^^^^^^^
+==== Configuration
 
 Most of the gitweb configuration file is handled automatically
 by Gerrit Code Review.  Site specific overrides can be placed in
 `'$site_path'/etc/gitweb_config.perl`, as this file is loaded as
 part of the generated configuration file.
 
-Logo and CSS
-^^^^^^^^^^^^
+==== Logo and CSS
 
 If the package-manager installed CGI (`/usr/lib/cgi-bin/gitweb.cgi`)
 is being used, the stock CSS and logo files will be served from
@@ -56,25 +52,27 @@
 in the same directory as the CGI script itself.  This matches with
 the default source code distribution, and most custom installations.
 
-Access Control
-^^^^^^^^^^^^^^
+==== Access Control
 
 Access controls for internally managed gitweb page views are enforced
 using the standard project READ +1 permission.
 
-External/Unmanaged gitweb
-~~~~~~~~~~~~~~~~~~~~~~~~~
+Also, in order for a user to be able to view any gitweb information for a
+project, the user must be able to read all references (including
+refs/meta/config, refs/meta/dashboards/*, etc.). If you have exclusive read
+permissions for any references, make sure to include all parties that should be
+able to read the gitweb info for any of the branches in that project.
+
+=== External/Unmanaged gitweb
 
 For the external configuration, gitweb runs under the control of an
 external web server, and Gerrit access controls are not enforced. Gerrit
 provides configuration parameters for integration with GitWeb.
 
 [[linuxGitWeb]]
-Linux Installation
-^^^^^^^^^^^^^^^^^^
+==== Linux Installation
 
-Install GitWeb
-++++++++++++++
+===== Install GitWeb
 
 On Ubuntu:
 
@@ -88,8 +86,7 @@
   $ yum install gitweb
 ====
 
-Configure GitWeb
-++++++++++++++++
+===== Configure GitWeb
 
 
 Update `/etc/gitweb.conf`, add the public GIT repositories:
@@ -120,11 +117,9 @@
 $favicon = "git-favicon.png";
 ----
 
-Configure & Restart Apache Web Server
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Configure & Restart Apache Web Server
 
-Configure Apache
-++++++++++++++++
+===== Configure Apache
 
 
 Link gitweb to `/var/www/gitweb`, check `/etc/gitweb.conf` if unsure of paths:
@@ -153,8 +148,7 @@
 *NOTE* This may have already been added by yum/apt-get. If that's the case, leave as
 is.
 
-Restart the Apache Web Server
-+++++++++++++++++++++++++++++
+===== Restart the Apache Web Server
 
 ====
 $ sudo /etc/init.d/apache2 restart
@@ -165,8 +159,7 @@
 link:http://localhost/gitweb[http://localhost/gitweb]
 
 [[WindowsGitWeb]]
-Windows Installation
-^^^^^^^^^^^^^^^^^^^^
+==== Windows Installation
 
 Instructions are available for installing the GitWeb module distributed with
 MsysGit:
@@ -214,8 +207,7 @@
 
 copy the contents of lib into `msysgit/lib/perl5/5.8.8` and overwrite existing files.
 
-Enable GitWeb Integration
-^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Enable GitWeb Integration
 
 To enable the external gitweb integration, set
 link:config-gerrit.html#gitweb.url[gitweb.url] with the URL of your
@@ -239,29 +231,33 @@
 $ git config -f $site_path/etc/gerrit.config gitweb.project ?p=\${project}\;a=summary
 $ git config -f $site_path/etc/gerrit.config gitweb.revision ?p=\${project}\;a=commit\;h=\${commit}
 $ git config -f $site_path/etc/gerrit.config gitweb.branch ?p=\${project}\;a=shortlog\;h=\${branch}
+$ git config -f $site_path/etc/gerrit.config gitweb.roottree ?p=\${project}\;a=tree\;hb=\${commit}
+$ git config -f $site_path/etc/gerrit.config gitweb.file ?p=\${project}\;hb=\${commit}\;f=\${file}
 $ git config -f $site_path/etc/gerrit.config gitweb.filehistory ?p=\${project}\;a=history\;hb=\${branch}\;f=\${file}
 ----
 
 After updating `'$site_path'/etc/gerrit.config`, the Gerrit server must
 be restarted and clients must reload the host page to see the change.
 
-Access Control
-++++++++++++++
+Note that when using a custom gitweb configuration, values must be
+specified for all of the `project`, `revision`, `branch`, `roottree`,
+`file`, and `filehistory` settings, otherwise the configuration will
+not be used.
+
+===== Access Control
 
 Gitweb access controls can be implemented using standard web server
 access controls.  This isn't typically integrated with Gerrit's own
 access controls.  Caution must be taken to ensure the controls are
 consistent if access needs to be restricted.
 
-Caching Gitweb
-++++++++++++++
+===== Caching Gitweb
 
 If your repository set is large and you are expecting a lot
 of users, you may want to look at the caching forks used by
 high-traffic sites like kernel.org or repo.or.cz.
 
-Alternatives to gitweb
-~~~~~~~~~~~~~~~~~~~~~~
+=== Alternatives to gitweb
 There are other alternatives to gitweb that can also be used with
 Gerrit, such as cgit.
 
@@ -269,8 +265,7 @@
 
 It is also possible to define custom patterns.
 
-See Also
-~~~~~~~~
+=== SEE ALSO
 
 * link:config-gerrit.html#gitweb[Section gitweb]
 * link:http://hjemli.net/git/cgit/[cgit]
@@ -278,3 +273,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 5875837..0f4d094 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Hooks
-==========================
+= Gerrit Code Review - Hooks
 
 Gerrit does not run any of the standard git hooks in the
 repositories it works with, but it does have its own hook mechanism
@@ -17,11 +16,9 @@
 run in the background after the activity, a hook might not be notified
 about an event if the server is shutdown before the hook can be invoked.
 
-Supported Hooks
----------------
+== Supported Hooks
 
-ref-update
-~~~~~~~~~~
+=== ref-update
 
 This is called when a push request is received by Gerrit. It allows
 a push to be rejected before it is committed to the Gerrit repository.
@@ -38,8 +35,7 @@
   ref-update --project <project name> --refname <refname> --uploader <uploader> --oldrev <sha1> --newrev <sha1>
 ====
 
-patchset-created
-~~~~~~~~~~~~~~~~
+=== patchset-created
 
 This is called whenever a patchset is created (this includes new
 changes and drafts).
@@ -48,8 +44,7 @@
   patchset-created --change <change id> --is-draft <boolean> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --uploader <uploader> --commit <sha1> --patchset <patchset id>
 ====
 
-draft-published
-~~~~~~~~~~~~~~~
+=== draft-published
 
 This is called whenever a draft change is published.
 
@@ -57,8 +52,7 @@
   draft-published --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --uploader <uploader> --commit <sha1> --patchset <patchset id>
 ====
 
-comment-added
-~~~~~~~~~~~~~
+=== comment-added
 
 This is called whenever a comment is added to a change.
 
@@ -66,8 +60,7 @@
   comment-added --change <change id> --is-draft <boolean> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --author <comment author> --commit <commit> --comment <comment> [--<approval category id> <score> --<approval category id> <score> ...]
 ====
 
-change-merged
-~~~~~~~~~~~~~
+=== change-merged
 
 Called whenever a change has been merged.
 
@@ -75,8 +68,7 @@
   change-merged --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --submitter <submitter> --commit <sha1>
 ====
 
-merge-failed
-~~~~~~~~~~~~
+=== merge-failed
 
 Called whenever a change has failed to merge.
 
@@ -84,8 +76,7 @@
   merge-failed --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --submitter <submitter> --commit <sha1> --reason <reason>
 ====
 
-change-abandoned
-~~~~~~~~~~~~~~~~
+=== change-abandoned
 
 Called whenever a change has been abandoned.
 
@@ -93,8 +84,7 @@
   change-abandoned --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --abandoner <abandoner> --commit <sha1> --reason <reason>
 ====
 
-change-restored
-~~~~~~~~~~~~~~~
+=== change-restored
 
 Called whenever a change has been restored.
 
@@ -102,8 +92,7 @@
   change-restored --change <change id> --change-url <change url> --project <project name> --branch <branch> --topic <topic> --restorer <restorer> --commit <sha1> --reason <reason>
 ====
 
-ref-updated
-~~~~~~~~~~~
+=== ref-updated
 
 Called whenever a ref has been updated.
 
@@ -111,8 +100,7 @@
   ref-updated --oldrev <old rev> --newrev <new rev> --refname <ref name> --project <project name> --submitter <submitter>
 ====
 
-reviewer-added
-~~~~~~~~~~~~~~
+=== reviewer-added
 
 Called whenever a reviewer is added to a change.
 
@@ -120,8 +108,7 @@
   reviewer-added --change <change id> --change-url <change url> --project <project name> --branch <branch> --reviewer <reviewer>
 ====
 
-topic-changed
-~~~~~~~~~~~~~
+=== topic-changed
 
 Called whenever a change's topic is changed from the Web UI or via the REST API.
 
@@ -129,8 +116,7 @@
   topic-changed --change <change id> --project <project name> --branch <branch> --changer <changer> --old-topic <old topic> --new-topic <new topic>
 ====
 
-cla-signed
-~~~~~~~~~~
+=== cla-signed
 
 Called whenever a user signs a contributor license agreement.
 
@@ -139,8 +125,7 @@
 ====
 
 
-Configuration Settings
-----------------------
+== Configuration Settings
 
 It is possible to change where Gerrit looks for hooks, and what
 filenames it looks for, by adding a [hooks] section in gerrit.config.
@@ -152,8 +137,7 @@
 hooks.changeAbandonedHook, hooks.changeRestoredHook, hooks.refUpdatedHook,
 hooks.refUpdateHook, hooks.reviewerAddedHook and hooks.claSignedHook.
 
-Missing Change URLs
--------------------
+== Missing Change URLs
 
 If link:config-gerrit.html#gerrit.canonicalWebUrl[gerrit.canonicalWebUrl]
 is not set in `gerrit.config` the `--change-url` flag may not be
@@ -161,11 +145,13 @@
 the patchset-created hook) don't know the server's web URL, unless
 this variable is configured.
 
-See Also
---------
+== SEE ALSO
 
 * link:config-gerrit.html#hooks[Section hooks]
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index c08d484..fc25f22 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Review Labels
-==================================
+= Gerrit Code Review - Review Labels
 
 As part of the code review process, reviewers score each change with
 values for each label configured for the project.  The label values that
@@ -10,8 +9,7 @@
 
 
 [[label_Code-Review]]
-Label: Code-Review
-------------------
+== Label: Code-Review
 
 The code review label is the second of two default labels that is
 configured upon the creation of a Gerrit instance.  It may have any
@@ -21,7 +19,7 @@
 
 The range of values is:
 
-* -2 Do not submit
+* -2 This shall not be merged
 +
 The code is so horribly incorrect/buggy/broken that it must not be
 submitted to this project, or to this branch.  This value is valid
@@ -31,7 +29,7 @@
 +
 *Any -2 blocks submit.*
 
-* -1 I would prefer that you didn't submit this
+* -1 I would prefer this is not merged as is
 +
 The code doesn't look right, or could be done differently, but
 the reviewer is willing to live with it as-is if another reviewer
@@ -83,8 +81,7 @@
 required to enable submit.
 
 [[label_Verified]]
-Label: Verified
----------------
+== Label: Verified
 
 The Verified label was originally invented by the Android Open Source
 Project to mean 'compiles, passes basic unit tests'.  Some CI tools
@@ -131,8 +128,7 @@
 
 
 [[label_custom]]
-Your Label Here
----------------
+== Your Label Here
 
 Site administrators and project owners can also define their own labels.
 
@@ -159,16 +155,14 @@
 the administrator.
 
 [[label_name]]
-`label.Label-Name`
-~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name`
 
 The name for a label, consisting only of alphanumeric characters and
 `-`.
 
 
 [[label_value]]
-`label.Label-Name.value`
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name.value`
 
 A multi-valued key whose values are of the form `"<#> Value description
 text"`. The `<#>` may be any positive or negative number with an
@@ -176,8 +170,7 @@
 
 
 [[label_abbreviation]]
-`label.Label-Name.abbreviation`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name.abbreviation`
 
 An abbreviated name for a label shown as a compact column header, for
 example on project dashboards. Defaults to all the uppercase characters
@@ -185,8 +178,7 @@
 
 
 [[label_function]]
-`label.Label-Name.function`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name.function`
 
 The name of a function for evaluating multiple votes for a label.  This
 function is only applied if the default submit rule is used for a label.
@@ -221,15 +213,13 @@
 
 
 [[label_copyMinScore]]
-`label.Label-Name.copyMinScore`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name.copyMinScore`
 
 If true, the lowest possible negative value for the label is copied
 forward when a new patch set is uploaded.
 
 [[label_copyMaxScore]]
-`label.Label-Name.copyMaxScore`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name.copyMaxScore`
 
 If true, the highest possible positive value for the label is copied
 forward when a new patch set is uploaded. This can be used to enable
@@ -237,8 +227,7 @@
 submitting a change.
 
 [[label_copyAllScoresOnTrivialRebase]]
-`label.Label-Name.copyAllScoresOnTrivialRebase`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name.copyAllScoresOnTrivialRebase`
 
 If true, all scores for the label are copied forward when a new patch
 set is uploaded that is a trivial rebase. A new patch set is considered
@@ -249,8 +238,7 @@
 trivial rebases prior to submitting a change. Defaults to false.
 
 [[label_copyAllScoresIfNoCodeChange]]
-`label.Label-Name.copyAllScoresIfNoCodeChange`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name.copyAllScoresIfNoCodeChange`
 
 If true, all scores for the label are copied forward when a new patch
 set is uploaded that has the same parent commit as the previous patch
@@ -261,16 +249,14 @@
 Defaults to false.
 
 [[label_canOverride]]
-`label.Label-Name.canOverride`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name.canOverride`
 
 If false, the label cannot be overridden by child projects. Any
 configuration for this label in child projects will be ignored. Defaults
 to true.
 
 [[label_branch]]
-`label.Label-Name.branch`
-~~~~~~~~~~~~~~~~~~~~~~~~~
+=== `label.Label-Name.branch`
 
 By default a given project's label applicable scope is all changes
 on all branches of this project and its child projects.
@@ -294,8 +280,7 @@
 ignored if the label doesn't apply for that branch.
 
 [[label_example]]
-Example
-~~~~~~~
+=== Example
 
 To define a new 3-valued category that behaves exactly like `Verified`,
 but has different names/labels:
@@ -315,3 +300,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-login-register.txt b/Documentation/config-login-register.txt
index 1011985..76f47ed 100644
--- a/Documentation/config-login-register.txt
+++ b/Documentation/config-login-register.txt
@@ -1,6 +1,5 @@
 [[usersetup]]
-Initial Login
--------------
+== Initial Login
 It's time to exit the gerrit2 account as you now have Gerrit running on your
 host and setup your first workspace.
 
@@ -21,8 +20,7 @@
 If you don't see the files in your listing, your will have to generate rsa
 keys for your ssh sessions:
 
-SSH key generation
-~~~~~~~~~~~~~~~~~~
+=== SSH key generation
 
 *Please don't generate new keys if you already have a valid keypair!*
 *They will be overwritten!*
@@ -53,8 +51,7 @@
   user@host:~$
 ----
 
-Registering your key in Gerrit
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Registering your key in Gerrit
 
 Open a browser and enter the canonical url of your Gerrit server.  You can
 find the url in the settings file.
@@ -141,3 +138,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index 3b8bffa..62d0219 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Mail Templates
-===================================
+= Gerrit Code Review - Mail Templates
 
 Gerrit uses velocity templates for the bulk of the standard mails it sends out.
 There are builtin default templates which are used if they are not overridden.
@@ -7,8 +6,7 @@
 them and easily modify them to tweak their contents.
 
 
-Template Locations and Extensions:
-----------------------------------
+== Template Locations and Extensions:
 
 The default example templates reside under:  `'$site_path'/etc/mail` and are
 terminated with the double extension `.vm.example`. Modifying these example
@@ -17,77 +15,60 @@
 and modifying it will allow an administrator to customize the template.
 
 
-Supported Mail Templates:
--------------------------
+== Supported Mail Templates:
 
 Each mail that Gerrit sends out is controlled by at least one template.  These
 are listed below.  Change emails are influenced by two additional templates,
 one to set the subject line, and one to set the footer which gets appended to
 all the change emails (see `ChangeSubject.vm` and `ChangeFooter.vm` below.)
 
-Abandoned.vm
-~~~~~~~~~~~~
+=== Abandoned.vm
 
 The `Abandoned.vm` template will determine the contents of the email related
 to a change being abandoned.  It is a `ChangeEmail`: see `ChangeSubject.vm` and
 `ChangeFooter.vm`.
 
-ChangeFooter.vm
-~~~~~~~~~~~~~~~
+=== ChangeFooter.vm
 
 The `ChangeFooter.vm` template will determine the contents of the footer
 text that will be appended to emails related to changes (all `ChangeEmail`s).
 
-ChangeSubject.vm
-~~~~~~~~~~~~~~~~
+=== ChangeSubject.vm
 
 The `ChangeSubject.vm` template will determine the contents of the email
 subject line for ALL emails related to changes.
 
-Comment.vm
-~~~~~~~~~~
+=== Comment.vm
 
 The `Comment.vm` template will determine the contents of the email related to
 a user submitting comments on changes.  It is a `ChangeEmail`: see
 `ChangeSubject.vm`, `ChangeFooter.vm` and `CommentFooter.vm`.
 
-CommentFooter.vm
-~~~~~~~~~~~~~~~~
+=== CommentFooter.vm
 
 The `CommentFooter.vm` template will determine the contents of the footer
 text that will be appended to emails related to a user submitting comments on
 changes.  See `ChangeSubject.vm`, `Comment.vm` and `ChangeFooter.vm`.
 
-CommitMessageEdited.vm
-~~~~~~~~~~~~~~~~~~~~~~
-
-The `CommitMessageEdited.vm` template will determine the contents of the email
-related to a user editing the commit message through the Gerrit UI.  It is a
-`ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
-
-Footer.vm
-~~~~~~~~~
+=== Footer.vm
 
 The `Footer.vm` template will determine the contents of the footer text
 appended to the end of all outgoing emails after the ChangeFooter and
 CommentFooter.
 
-Merged.vm
-~~~~~~~~~
+=== Merged.vm
 
 The `Merged.vm` template will determine the contents of the email related to
 a change successfully merged to the head.  It is a `ChangeEmail`: see
 `ChangeSubject.vm` and `ChangeFooter.vm`.
 
-MergeFail.vm
-~~~~~~~~~~~~
+=== MergeFail.vm
 
 The `MergeFail.vm` template will determine the contents of the email related
 to a failure upon attempting to merge a change to the head.  It is a
 `ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
 
-NewChange.vm
-~~~~~~~~~~~~
+=== NewChange.vm
 
 The `NewChange.vm` template will determine the contents of the email related
 to a user submitting a new change for review. This includes changes created
@@ -95,14 +76,12 @@
 reverting a change.  It is a `ChangeEmail`: see `ChangeSubject.vm` and
 `ChangeFooter.vm`.
 
-RegisterNewEmail.vm
-~~~~~~~~~~~~~~~~~~~
+=== RegisterNewEmail.vm
 
 The `RegisterNewEmail.vm` template will determine the contents of the email
 related to registering new email accounts.
 
-ReplacePatchSet.vm
-~~~~~~~~~~~~~~~~~~
+=== ReplacePatchSet.vm
 
 The `ReplacePatchSet.vm` template will determine the contents of the email
 related to a user submitting a new patchset for a change.  This includes
@@ -110,23 +89,20 @@
 the commit message, cherry picking a commit, or rebasing a change.  It is a
 `ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
 
-Restored.vm
-~~~~~~~~~~~
+=== Restored.vm
 
 The `Restored.vm` template will determine the contents of the email related
 to a change being restored.  It is a `ChangeEmail`: see `ChangeSubject.vm` and
 `ChangeFooter.vm`.
 
-Reverted.vm
-~~~~~~~~~~~
+=== Reverted.vm
 
 The `Reverted.vm` template will determine the contents of the email related
 to a change being reverted.  It is a `ChangeEmail`: see `ChangeSubject.vm` and
 `ChangeFooter.vm`.
 
 
-Mail Variables and Methods
---------------------------
+== Mail Variables and Methods
 
 Mail templates can access and display objects currently made available to them
 via the velocity context.  While the base objects are documented here, it is
@@ -137,16 +113,14 @@
 knowledge of the class structure to be useful.  Browsing the source code might
 be necessary for anything more than a minor formatting change.
 
-Warning
-~~~~~~~
+=== Warning
 
 Be aware that modifying templates can cause them to fail to parse and therefore
 not send out the actual email, or worse, calling methods on the available
 objects could have internal side effects which would adversely affect the
 health of your Gerrit server and/or data.
 
-All OutgoingEmails
-~~~~~~~~~~~~~~~~~~
+=== All OutgoingEmails
 
 All outgoing emails have the following variables available to them:
 
@@ -165,8 +139,7 @@
 A reference to the Apache `StringUtils` class.  This can be very useful for
 formatting strings.
 
-Change Emails
-~~~~~~~~~~~~~
+=== Change Emails
 
 All change related emails have the following additional variables available to them:
 
@@ -203,11 +176,13 @@
 A reference to the current `PatchSetInfo`.
 
 
-See Also
---------
+== SEE ALSO
 
 * link:http://velocity.apache.org/[velocity]
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 8529b67..2fb256f 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Project Configuration File Format
-======================================================
+= Gerrit Code Review - Project Configuration File Format
 
 This page explains the storage format of Gerrit's project configuration
 and access control models.
@@ -10,8 +9,7 @@
 relevant in an automation scenario of the access controls.
 
 
-The +refs/meta/config+ namespace
---------------------------------
+== The +refs/meta/config+ namespace
 
 The namespace contains three different files that play different
 roles in the permission model.  With read permission to that reference,
@@ -24,8 +22,7 @@
 
 
 [[file-project_config]]
-The file +project.config+
--------------------------
+== The file +project.config+
 
 The +project.config+ file contains the link between groups and their
 permitted actions on reference patterns in this project and any projects
@@ -75,8 +72,7 @@
 
 
 [[project-section]]
-Project section
-~~~~~~~~~~~~~~~
+=== Project section
 
 The project section includes configuration of project settings.
 
@@ -86,8 +82,7 @@
 
 
 [[receive-section]]
-Receive section
-~~~~~~~~~~~~~~~
+=== Receive section
 
 The receive section includes configuration of project-specific
 receive settings:
@@ -134,9 +129,23 @@
 +
 Common unit suffixes of k, m, or g are supported.
 
+[[receive.checkReceivedObjects]]receive.checkReceivedObjects::
++
+Controls whether or not the JGit functionality for checking received objects
+is enabled.
++
+By default Gerrit checks the validity of git objects. Setting this variable to
+false should not be used unless a project with history containing invalid
+objects needs to be pushed into a Gerrit repository.
++
+This functionality is provided as some other git implementations have allowed
+bad history to be written into git repositories. If these repositories need pushing
+up to Gerrit then the JGit checks need to be disabled.
++
+The default value for this is true, false disables the checks.
+
 [[submit-section]]
-Submit section
-~~~~~~~~~~~~~~
+=== Submit section
 
 The submit section includes configuration of project-specific
 submit settings:
@@ -152,8 +161,7 @@
 
 
 [[access-section]]
-Access section
-~~~~~~~~~~~~~~
+=== Access section
 
 Each +access+ section includes a reference and access rights connected
 to groups.  Each group listed must exist in the link:#file-groups[+groups+ file].
@@ -164,8 +172,7 @@
 
 
 [[capability-section]]
-Capability section
-~~~~~~~~~~~~~~~~~~
+=== Capability section
 
 The +capability+ section only appears once, and only in the +All-Projects+
 repository.  It controls Gerrit administration capabilities that are configured
@@ -177,8 +184,7 @@
 
 
 [[file-groups]]
-The file +groups+
------------------
+== The file +groups+
 
 Each group in this list is linked with its UUID so that renaming of
 groups is possible without having to rewrite every +groups+ file
@@ -209,8 +215,7 @@
 
 
 [[file-rules_pl]]
-The file +rules.pl+
--------------------
+== The file +rules.pl+
 
 The +rules.pl+ files allows you to replace or amend the default Prolog
 rules that control e.g. what conditions need to be fulfilled for a
@@ -223,3 +228,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-reverseproxy.txt b/Documentation/config-reverseproxy.txt
index 064fe2e..0c52be6 100644
--- a/Documentation/config-reverseproxy.txt
+++ b/Documentation/config-reverseproxy.txt
@@ -1,8 +1,6 @@
-Gerrit Code Review - Reverse Proxy
-==================================
+= Gerrit Code Review - Reverse Proxy
 
-Description
------------
+== Description
 
 Gerrit can be configured to run behind a third-party web server.
 This allows the other web server to bind to the privileged port 80
@@ -10,8 +8,7 @@
 from Java to optimized native C code.
 
 
-Gerrit Configuration
---------------------
+== Gerrit Configuration
 
 Ensure `'$site_path'/etc/gerrit.config` has the property
 link:config-gerrit.html#httpd.listenUrl[httpd.listenUrl] configured
@@ -25,8 +22,7 @@
 ----
 
 
-Apache 2 Configuration
-----------------------
+== Apache 2 Configuration
 
 To run Gerrit behind an Apache server using 'mod_proxy', enable the
 necessary Apache2 modules:
@@ -62,8 +58,7 @@
 The two options 'AllowEncodedSlashes On' and 'ProxyPass .. nocanon' are required
 since Gerrit 2.6.
 
-SSL
-~~~
+=== SSL
 
 To enable Apache to perform the SSL processing, use 'proxy-https://'
 in httpd.listenUrl within Gerrit's configuration file, and enable
@@ -83,8 +78,7 @@
 configure SSL within the server, like controlling how strong of an
 encryption algorithm is required.
 
-Troubleshooting
-~~~~~~~~~~~~~~~
+=== Troubleshooting
 
 If you are encountering 'Page Not Found' errors when opening the change
 screen, your Apache proxy is very likely decoding the passed URL.
@@ -93,8 +87,7 @@
 'AllowEncodedSlashes NoDecode' set.
 
 
-Nginx Configuration
--------------------
+== Nginx Configuration
 
 To run Gerrit behind an Nginx server, use a server statement such
 as this one:
@@ -112,8 +105,7 @@
 	}
 ----
 
-SSL
-~~~
+=== SSL
 
 To enable Nginx to perform the SSL processing, use 'proxy-https://'
 in httpd.listenUrl within Gerrit's configuration file, and enable
@@ -136,8 +128,7 @@
 how to configure SSL within the server, like controlling how strong
 of an encryption algorithm is required.
 
-Troubleshooting
-~~~~~~~~~~~~~~~
+=== Troubleshooting
 
 If you are encountering 'Page Not Found' errors when opening the change
 screen, your Nginx proxy is very likely decoding the passed URL.
@@ -147,3 +138,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-sso.txt b/Documentation/config-sso.txt
index e915ffb..8c82091 100644
--- a/Documentation/config-sso.txt
+++ b/Documentation/config-sso.txt
@@ -1,12 +1,10 @@
-Gerrit Code Review - Single Sign-On Security
-============================================
+= Gerrit Code Review - Single Sign-On Security
 
 Gerrit supports integration with some types of single sign-on
 security solutions, making it possible for end-users to setup
 and manage accounts, without administrator involvement.
 
-OpenID
-------
+== OpenID
 
 By default a new Gerrit installation relies upon OpenID to perform
 user authentication services.  To enable OpenID, the auth.type
@@ -50,8 +48,7 @@
   git config --file $site_path/etc/gerrit.config auth.trustedOpenID 'https://www.google.com/accounts/o8/id?id='
 ====
 
-Database Schema
-~~~~~~~~~~~~~~~
+=== Database Schema
 
 User identities obtained from OpenID providers are stored into the
 `account_external_ids` table.  Users may link more than one OpenID
@@ -61,8 +58,7 @@
 unique OpenID accounts.
 
 
-HTTP Basic/Digest Authentication
---------------------------------
+== HTTP Basic/Digest Authentication
 
 When using HTTP authentication, Gerrit assumes that the servlet
 container or the frontend web server has performed all user
@@ -107,8 +103,7 @@
   </Location>
 ====
 
-Database Schema
-~~~~~~~~~~~~~~~
+=== Database Schema
 
 User identities are stored in the `account_external_ids` table.
 The user string obtained from the authorization header has the prefix
@@ -117,8 +112,7 @@
 with "gerrit:foo".
 
 
-Computer Associates Siteminder
-------------------------------
+== Computer Associates Siteminder
 
 Siteminder is a commercial single sign on solution marketed by
 Computer Associates.  It is very common in larger enterprise
@@ -167,8 +161,7 @@
 ====
 
 
-Database Schema
-~~~~~~~~~~~~~~~
+=== Database Schema
 
 User identities are stored in the `account_external_ids` table.
 The user string obtained from Siteminder (e.g. the value in the
@@ -179,3 +172,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-themes.txt b/Documentation/config-themes.txt
index c102381..5c3a448 100644
--- a/Documentation/config-themes.txt
+++ b/Documentation/config-themes.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Themes
-===========================
+= Gerrit Code Review - Themes
 
 Gerrit supports some customization of the HTML it sends to
 the browser, allowing organizations to alter the look and
@@ -15,8 +14,7 @@
 extend an inherited theme, you must copy it into the appropriate
 per-project directory.
 
-HTML Header/Footer
-------------------
+== HTML Header/Footer
 
 At startup Gerrit reads the following files (if they exist) and
 uses them to customize the HTML page it sends to clients:
@@ -44,8 +42,7 @@
 then inserts the root element into the host page.  If a file has
 more than one root level element, Gerrit will not start.
 
-Static Images
--------------
+== Static Images
 
 Static image files can also be served from `'$site_path'/static`,
 and may be referenced in `GerritSite{Header,Footer}.html`
@@ -58,8 +55,7 @@
 cannot contain `/` or `\`.  (Client requests for `static/foo/bar`
 will result in 404 Not Found responses.)
 
-HTTP Caching
-------------
+== HTTP Caching
 
 The header, footer, and CSS files are inlined into the host page,
 which is always sent with a no-cache header.  Clients will see any
@@ -87,8 +83,7 @@
 `my_logo2.cache.png` and update the header (or footer) HTML to
 reference the new image path.
 
-Google Analytics Integration
-----------------------------
+== Google Analytics Integration
 
 To connect Gerrit to Google Analytics add the following to your
 `GerritSiteFooter.html`:
@@ -104,12 +99,13 @@
       var pageTracker = _gat._getTracker("UA-nnnnnnn-n");
       pageTracker._trackPageview();
     </script>
-  <!-- /standard analytics code -->
+  be <!-- /standard analytics code -->
 
   <script type="text/javascript">
     window.onload = function() {
-      gerrit_addHistoryHook(function (s) {
-        pageTracker._trackPageview(s.replace(/#/, '/'))
+      var p = window.location.pathname;
+      Gerrit.on('history', function (s) {
+        pageTracker._trackPageview(p + '/' + s)
       });
     };
   </script>
@@ -124,19 +120,25 @@
 a single `<div>` tag (like above) to ensure it is a well-formed
 XHTML document file.
 
-The global function `gerrit_addHistoryHook` accepts functions that
+The global function `Gerrit.on("history")` accepts functions that
 accept a string parameter.  These functions are put into a list and
 invoked any time Gerrit shifts URLs.  You'll see page names like
-`/#change,123` be passed to these functions, which in turn
-are handed off to Google Analytics for tracking.  Our example hook
-above replaces '#' with '/' because Analytics won't track anchors.
+`/c/123` be passed to these functions, which in turn are handed off
+to Google Analytics for tracking.  Our example hook above uses '/'
+instead of '#' because Analytics won't track anchors.
 
 The `window.onload` callback is necessary to ensure that the
-`gerrit_addHistoryHook` function has actually been defined by the
+`Gerrit.on()` function has actually been defined by the
 page.  Because GWT loads the module asynchronously any `<script>`
 block in the header or footer will execute before Gerrit has defined
 the function and is ready to register the hook callback.
 
+The function `gerrit_addHistoryHook` is deprecated and may be
+removed in a future release.
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/config-validation.txt b/Documentation/config-validation.txt
index 1b09d19..b7843a7 100644
--- a/Documentation/config-validation.txt
+++ b/Documentation/config-validation.txt
@@ -1,12 +1,10 @@
-Gerrit Code Review - Commit Validation
-======================================
+= Gerrit Code Review - Commit Validation
 
 Gerrit supports link:dev-plugins.html[plugin-based] validation of
 commits.
 
 [[new-commit-validation]]
-New commit validation
----------------------
+== New commit validation
 
 
 Plugins implementing the `CommitValidationListener` interface can
@@ -24,8 +22,7 @@
 subject and body lines of commit messages on uploaded commits.
 
 [[pre-merge-validation]]
-Pre-merge validation
---------------------
+== Pre-merge validation
 
 
 Plugins implementing the `MergeValidationListener` interface can
@@ -35,8 +32,33 @@
 If the commit fails the validation, the plugin can throw an exception
 which will cause the merge to fail.
 
+[[new-project-validation]]
+== New project validation
+
+
+Plugins implementing the `ProjectCreationValidationListener` interface
+can perform additional validation on project creation based on the
+input arguments.
+
+E.g. a plugin could use this to enforce a certain name scheme for
+project names.
+
+[[new-group-validation]]
+== New group validation
+
+
+Plugins implementing the `GroupCreationValidationListener` interface
+can perform additional validation on group creation based on the
+input arguments.
+
+E.g. a plugin could use this to enforce a certain name scheme for
+group names.
+
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
 
+
+SEARCHBOX
+---------
diff --git a/Documentation/database-setup.txt b/Documentation/database-setup.txt
index 2016ac3..8fa6c4a 100644
--- a/Documentation/database-setup.txt
+++ b/Documentation/database-setup.txt
@@ -1,12 +1,10 @@
 [[createdb]]
-Database Setup
---------------
+== Database Setup
 
 During the init phase of Gerrit you will need to specify which database to use.
 
 [[createdb_h2]]
-H2
-~~
+=== H2
 
 If you choose H2, Gerrit will automatically set up the embedded H2 database as
 backend so no set up or configuration is necessary.
@@ -21,8 +19,7 @@
 If this option interests you, you might want to consider link:install-quick.html[the quick guide].
 
 [[createdb_postgres]]
-PostgreSQL
-~~~~~~~~~~
+=== PostgreSQL
 
 This option is more complicated than the H2 option but is recommended
 for larger installations. It's the database backend with the largest userbase
@@ -41,8 +38,7 @@
 using PostgreSQL.
 
 [[createdb_mysql]]
-MySQL
-~~~~~
+=== MySQL
 
 This option is also more complicated than the H2 option. Just as with
 PostgreSQL it's also recommended for larger installations.
@@ -72,8 +68,7 @@
 information regarding using MySQL.
 
 [[createdb_oracle]]
-Oracle
-~~~~~~
+=== Oracle
 
 PostgreSQL or H2 is the recommended database for Gerrit Code Review.
 Oracle is supported for environments where running on an existing Oracle
@@ -120,3 +115,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 9c993a8..db36732 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -1,13 +1,11 @@
-Gerrit Code Review - Building with Buck
-=======================================
+= Gerrit Code Review - Building with Buck
 
 
-Installation
-------------
+== Installation
 
 There is currently no binary distribution of Buck, so it has to be manually
 built and installed.  Apache Ant is required.  Currently only Linux and Mac
-OS are supported.
+OS are supported.  Gerrit's buck wrappers require Python version 2.6 or higher.
 
 Clone the git and build it:
 
@@ -57,12 +55,10 @@
 
 
 [[eclipse]]
-Eclipse Integration
--------------------
+== Eclipse Integration
 
 
-Generating the Eclipse Project
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Generating the Eclipse Project
 
 Create the Eclipse project:
 
@@ -82,8 +78,7 @@
 `tools/eclipse/project.py`.
 
 
-Refreshing the Classpath
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== Refreshing the Classpath
 
 If an updated classpath is needed, the Eclipse project can be
 refreshed and missing dependency JARs can be downloaded:
@@ -93,8 +88,7 @@
 ----
 
 
-Attaching Sources
-~~~~~~~~~~~~~~~~~
+=== Attaching Sources
 
 To save time and bandwidth source JARs are only downloaded by the buck
 build where necessary to compile Java source into JavaScript using the
@@ -107,12 +101,10 @@
 
 
 [[build]]
-Building on the Command Line
-----------------------------
+== Building on the Command Line
 
 
-Gerrit Development WAR File
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Gerrit Development WAR File
 
 To build the Gerrit web application:
 
@@ -127,28 +119,30 @@
 ----
 
 
-Extension and Plugin API JAR Files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Extension and Plugin API JAR Files
 
-To build the extension and plugin API JAR files:
+To build the extension, plugin and GWT API JAR files:
 
 ----
   buck build api
 ----
 
-The output JAR files will be placed in:
+Java binaries, Java sources and Java docs are generated into corresponding
+project directories in `buck-out/gen`, here as example for plugin API:
 
 ----
-  buck-out/gen/{extension,plugin}-api.jar
+  buck-out/gen/gerrit-plugin-api/plugin-api.jar
+  buck-out/gen/gerrit-plugin-api/plugin-api-src.jar
+  buck-out/gen/gerrit-plugin-api/plugin-api-javadoc.jar
 ----
 
-Install {extension,plugin}-api to the local maven repository:
+Install {extension,plugin,gwt}-api to the local maven repository:
 
 ----
   buck build api_install
 ----
 
-Deploy {extension,plugin}-api to the remote maven repository
+Deploy {extension,plugin,gwt}-api to the remote maven repository:
 
 ----
   buck build api_deploy
@@ -156,11 +150,15 @@
 
 The type of the repo is induced from the Gerrit version name, i.e.
 
-* `2.8-SNAPSHOT`: snapshot repo
-* `2.8`: release repo
+* `2.9-SNAPSHOT`: snapshot repo
+* `2.9`: release repo
 
-Plugins
-~~~~~~~
+Deploying to the remote repository still depends on Maven, and the credentials
+for the repository need to be
+link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
+configured in Maven's settings.xml file].
+
+=== Plugins
 
 To build all core plugins:
 
@@ -183,7 +181,7 @@
 To build a specific plugin:
 
 ----
-  buck build plugins/<name>
+  buck build plugins/<name>:<name>
 ----
 
 The output JAR file will be be placed in:
@@ -195,12 +193,27 @@
 Note that when building an individual plugin, the `core.zip` package
 is not regenerated.
 
+Additional plugins with BUCK files can be added to the build
+environment by cloning the source repository into the plugins
+subdirectory:
+
+----
+  git clone https://gerrit.googlesource.com/plugins/<name> plugins/<name>
+  echo /plugins/<name> >>.git/info/exclude
+----
+
+Additional plugin sources will be automatically added to Eclipse the
+next time project.py is run:
+
+----
+  tools/eclipse/project.py
+----
+
 
 [[documentation]]
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 
-To build the documentation:
+To build only the documentation:
 
 ----
   buck build docs
@@ -218,9 +231,20 @@
   buck-out/gen/Documentation/html.zip
 ----
 
+To build the executable WAR with the documentation included:
+
+----
+  buck build withdocs
+----
+
+The WAR file will be placed in:
+
+----
+  buck-out/gen/withdocs.war
+----
+
 [[release]]
-Gerrit Release WAR File
-~~~~~~~~~~~~~~~~~~~~~~~
+=== Gerrit Release WAR File
 
 To build the release of the Gerrit web application, including documentation and
 all core plugins:
@@ -236,8 +260,7 @@
 ----
 
 [[tests]]
-Running Unit Tests
-------------------
+== Running Unit Tests
 
 To run all tests including acceptance tests:
 
@@ -259,8 +282,7 @@
 ----
 
 
-Dependencies
-------------
+== Dependencies
 
 Dependency JARs are normally downloaded automatically, but Buck can inspect
 its graph and download any missing JAR files.  This is useful to enable
@@ -291,8 +313,7 @@
 being built, or in `~/.gerritcodereview/`.  The file in the root of the gerrit
 repository has precedence.
 
-Building against unpublished Maven JARs
----------------------------------------
+== Building against unpublished Maven JARs
 
 To build against unpublished Maven JARs, like gwtorm or PrologCafe, the custom
 JARs must be installed in the local Maven repository (`mvn clean install`) and
@@ -309,8 +330,92 @@
  )
 ----
 
-Caching Build Results
-~~~~~~~~~~~~~~~~~~~~~
+== Building against unpublished JARs, that change frequently
+
+If a dependent Gerrit library is undergoing active development it must be
+recompiled and the change must be reflected in the Buck build process. For
+example testing Gerrit against changed JGit snapshot version. After building
+JGit library, the artifacts are created in local Maven build directory, e. g.:
+
+----
+  mvn package
+  /home/<user>/projects/jgit/org.eclipse.jgit/target/org.eclipse.jgit-3.3.0-SNAPSHOT.jar
+  /home/<user>/projects/jgit/org.eclipse.jgit/target/org.eclipse.jgit-3.3.0-SNAPSHOT-sources.jar
+----
+
+If as usual, installation of the build artifacts takes place in local maven
+repository, then the Buck build must fetch them from there with normal
+`download_file.py` process. Disadvantage of this approach is that Buck cache
+invalidation must occur to refresh the artifacts after next
+change-compile-install round trip.
+
+To shorten that workflow and take the installation of the artifacts to the
+local Maven repository and fetching it again from there out of the picture,
+`local_jar()` method is used instead of `maven_jar()`:
+
+[source,python]
+----
+ local_jar(
+   name = 'jgit',
+   jar = '/home/<user>/projects/jgit/org.eclipse.jgit/target/org.eclipse.jgit-3.3.0-SNAPSHOT.jar',
+   src = '/home/<user>/projects/jgit/org.eclipse.jgit/target/org.eclipse.jgit-3.3.0-SNAPSHOT-sources.jar',
+   deps = [':ewah']
+ )
+----
+
+This creates a symlink to the Buck targets direct against artifacts in
+another project's Maven target directory:
+
+----
+  buck-out/gen/lib/jgit/jgit.jar ->
+  /home/<user>/projects/jgit/org.eclipse.jgit/target/org.eclipse.jgit-3.3.0-SNAPSHOT.jar
+----
+
+== Building against artifacts from custom Maven repositories
+
+To build against custom Maven repositories, two modes of operations are
+supported: with rewrite in local.properties and without.
+
+Without rewrite the URL of custom Maven repository can be directly passed
+to the maven_jar() function:
+
+[source,python]
+----
+  GERRIT_FORGE = 'http://gerritforge.com/snapshot'
+
+  maven_jar(
+    name = 'gitblit',
+    id = 'com.gitblit:gitblit:1.4.0',
+    sha1 = '1b130dbf5578ace37507430a4a523f6594bf34fa',
+    license = 'Apache2.0',
+    repository = GERRIT_FORGE,
+ )
+----
+
+When the custom URL has to be rewritten, then the same logic as with Gerrit
+known Maven repository is used: Repo name must be defined that matches an entry
+in local.properties file:
+
+----
+  download.GERRIT_FORGE = http://my.company.mirror/gerrit-forge
+----
+
+And corresponding BUCK excerpt:
+
+[source,python]
+----
+  GERRIT_FORGE = 'GERRIT_FORGE:'
+
+  maven_jar(
+    name = 'gitblit',
+    id = 'com.gitblit:gitblit:1.4.0',
+    sha1 = '1b130dbf5578ace37507430a4a523f6594bf34fa',
+    license = 'Apache2.0',
+    repository = GERRIT_FORGE,
+ )
+----
+
+=== Caching Build Results
 
 Build results can be locally cached, saving rebuild time when
 switching between Git branches. Buck's documentation covers
@@ -326,8 +431,7 @@
 ----
 
 [[buck-daemon]]
-Using Buck daemon
-~~~~~~~~~~~~~~~~~
+=== Using Buck daemon
 
 Buck ships with a daemon command `buckd`, which uses the
 link:https://github.com/martylamb/nailgun[Nailgun] protocol for running
@@ -353,32 +457,21 @@
   [-] BUILDING...FINISHED 0.2s
 ----
 
-Override Buck's settings
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== Override Buck's settings
 
-User-specific configuration can be placed in one of the following files:
-`/etc/buck.conf`, `$HOME/.buck/buck.conf` or `$HOME/.buckrc`.
-
-For example to override Buck's default 1GB heap size:
+Additional JVM args for Buck can be set in `.buckjavaargs` in the
+project root directory. For example to override Buck's default 1GB
+heap size:
 
 ----
-  cat > $HOME/.buckrc <<EOF
-  export BUCK_EXTRA_JAVA_ARGS="\
-  -XX:MaxPermSize=512m \
-  -Xms8000m \
-  -Xmx16000m"
-  EOF
-----
-
-Or to debug BUCK, set `BUCK_DEBUG_MODE` to anything non-empty, then connect to
-port 8888:
-
-----
-  cat > $HOME/.buckrc <<EOF
-  export BUCK_DEBUG_MODE="yes"
+  cat > .buckjavaargs <<EOF
+  -XX:MaxPermSize=512m -Xms8000m -Xmx16000m
   EOF
 ----
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-build-plugins.txt b/Documentation/dev-build-plugins.txt
new file mode 100644
index 0000000..2c04d17
--- /dev/null
+++ b/Documentation/dev-build-plugins.txt
@@ -0,0 +1,91 @@
+= Gerrit Code Review - Building plugins
+
+
+From build process perspective there are three types of plugins:
+
+* Maven driven
+* Buck in tree driven
+* Buck standalone driven
+
+These types can be combined: if both files in plugin's root directory exist:
+
+* `BUCK`
+* `pom.xml`
+
+the plugin can be built with both Buck and Maven.
+
+
+== Maven driven build
+
+If plugin contains `pom.xml` file, it can be built with Maven as usually:
+
+----
+mvn clean package
+----
+
+Exceptions from the rule above:
+
+=== Exception 1:
+
+
+Plugin's `pom.xml` references snapshot version of plugin API:
+`2.8-SNAPSHOT`. In this case there are two possibilities:
+
+* switch to release API. Change plugin API version in `pom.xml` from
+  `2.8-SNAPSHOT` to `2.8.1` and repeat step 1 above.
+* build and install `SNAPSHOT` version of plugin API in local Maven repository:
+
+----
+buck build api_install
+----
+
+=== Exception 2:
+
+Plugin's `pom.xml` references other own or foreign (unpublished) libraries or
+even other Gerrit plugins. These libraries and/or plugins must be built and
+installed in local Maven repository. Clone the related projects and issue
+
+----
+mvn install
+----
+
+Repeat step 1. above.
+
+
+== Buck in tree driven
+
+
+The fact that plugin contains `BUCK` file doesn't mean that building this
+plugin from the plugin directory works. For now it doesn't. Buck in tree driven
+means it can only be built from within Gerrit tree. Clone or link the plugin
+into gerrit/plugins directory:
+
+----
+cd gerrit
+buck build plugins/<plugin-name>:<plugin-name>
+----
+
+The output can be normally found in the following directory:
+
+----
+buck-out/gen/plugins/<plugin-name>/<plugin-name>.jar
+----
+
+Some plugins describe their build process in `src/main/resources/Documentation/build.md`
+file. It may worth checking.
+
+== Buck standalone driven
+
+Only few plugins support that mode for now:
+
+----
+cd reviewers
+buck build plugin
+----
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index edc072d..5e307f9 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Contributing
-=================================
+= Gerrit Code Review - Contributing
 
 Gerrit is developed as a self-hosting open source project and
 very much welcomes contributions from anyone with a contributor's
@@ -7,7 +6,15 @@
 
 * https://gerrit-review.googlesource.com/
 
-The Contributor License Agreements:
+A Contributor License Agreement must be completed before contributions
+are accepted.  To view and accept the agreements do the following:
+
+* Click "Sign In" at the top right corner of https://gerrit-review.googlesource.com/
+* Sign In with your Google account
+* After signing in, go to https://gerrit-review.googlesource.com/#/settings/agreements
+* Click "New Contributor Agreement" and follow the instructions
+
+For reference, the actual agreements are linked below
 
 * https://gerrit-review.googlesource.com/static/cla_individual.html
 * https://gerrit-review.googlesource.com/static/cla_corporate.html
@@ -20,7 +27,7 @@
 your change.  You can view the pending Gerrit contributions and
 their statuses here:
 
-* https://gerrit-review.googlesource.com/#/q/status:open+project:gerrit,n,z
+* https://gerrit-review.googlesource.com/#/q/status:open+project:gerrit
 
 Depending on the size of that list it might take a while for
 your change to get reviewed.  Naturally there are fewer
@@ -44,8 +51,7 @@
 missed it or decided against it.
 
 
-Review Criteria
----------------
+== Review Criteria
 
 Here are some hints as to what approvers may be looking for
 before approving or submitting changes to the Gerrit project.
@@ -58,8 +64,7 @@
 
 
 [[commit-message]]
-Commit Message
---------------
+== Commit Message
 
 It is essential to have a good commit message if you want your
 change to be reviewed.
@@ -74,8 +79,7 @@
   * Include a Change-Id line
 
 
-A sample good Gerrit commit message:
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== A sample good Gerrit commit message:
 ====
   Add sample commit message to guidelines doc
 
@@ -122,8 +126,7 @@
 Password tab of the user settings page].
 
 
-Style
------
+== Style
 
 The basic coding style is covered by the tools/GoogleFormat.xml
 doc, see the link:dev-eclipse.html#Formatting[Eclipse Setup]
@@ -157,8 +160,7 @@
     between internal ones.
 
 
-Code Organization
------------------
+== Code Organization
 
 Do your best to organize classes and methods in a logical way.
 Here are some guidelines that Gerrit uses:
@@ -191,8 +193,7 @@
 back and consult this section when creating them.
 
 
-Design
-------
+== Design
 
 Here are some design level objectives that you should keep in mind
 when coding:
@@ -224,14 +225,12 @@
   * ...and so is Guava (previously known as Google Collections).
 
 
-Tests
------
+== Tests
 
   * Tests for new code will greatly help your change get approved.
 
 
-Change Size/Number of Files Touched
------------------------------------
+== Change Size/Number of Files Touched
 
 And finally, I probably cannot say enough about change sizes.
 Generally, smaller is better, hopefully within reason.  Do try to
@@ -269,11 +268,9 @@
   * Use topic branches to link your separate changes together.
 
 [[process]]
-Process
--------
+== Process
 
-Backporting to stable branches
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Backporting to stable branches
 
 From time to time bug fix releases are made for existing stable branches.
 
@@ -288,3 +285,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-design.txt b/Documentation/dev-design.txt
index efc55c0..0a44542 100644
--- a/Documentation/dev-design.txt
+++ b/Documentation/dev-design.txt
@@ -1,8 +1,6 @@
-Gerrit Code Review - System Design
-==================================
+= Gerrit Code Review - System Design
 
-Objective
----------
+== Objective
 
 Gerrit is a web based code review system, facilitating online code
 reviews for projects using the Git version control system.
@@ -17,8 +15,7 @@
 centralized usage of Git.
 
 
-Background
-----------
+== Background
 
 Google developed Mondrian, a Perforce based code review tool to
 facilitate peer-review of changes prior to submission to the central
@@ -74,8 +71,7 @@
 * link:http://source.android.com/[Android Open Source Project]
 
 
-Overview
---------
+== Overview
 
 Developers create one or more changes on their local desktop system,
 then upload them for review to Gerrit using the standard `git push`
@@ -121,8 +117,7 @@
 to be the author of the change.
 
 
-Infrastructure
---------------
+== Infrastructure
 
 End-user web browsers make HTTP requests directly to Gerrit's
 HTTP server.  As nearly all of the user interface is implemented
@@ -184,8 +179,7 @@
 repositories for each project.
 
 
-Project Information
--------------------
+== Project Information
 
 Gerrit is developed as a self-hosting open source project:
 
@@ -196,8 +190,7 @@
 * link:https://review.source.android.com/[Change Review]
 
 
-Internationalization and Localization
--------------------------------------
+== Internationalization and Localization
 
 As a source code review system for open source projects, where the
 commonly preferred language for communication is typically English,
@@ -220,8 +213,7 @@
 * link:i18n-readme.html[Gerrit's i18n Support]
 
 
-Accessibility Considerations
-----------------------------
+== Accessibility Considerations
 
 Whenever possible Gerrit displays raw text rather than image icons,
 so screen readers should still be able to provide useful information
@@ -239,8 +231,7 @@
 provide hints to screen readers.
 
 
-Browser Compatibility
----------------------
+== Browser Compatibility
 
 Supporting non-JavaScript enabled browsers is a non-goal for Gerrit.
 
@@ -281,8 +272,7 @@
 non-issue for most Gerrit deployments.
 
 
-Product Integration
--------------------
+== Product Integration
 
 Gerrit integrates with an existing gitweb installation by optionally
 creating hyperlinks to reference changes on the gitweb server.
@@ -328,8 +318,7 @@
 services other than those listed above.
 
 
-Standards / Developer APIs
---------------------------
+== Standards / Developer APIs
 
 Gerrit uses an XSRF protected variant of JSON-RPC 1.1 to communicate
 between the browser client and the server.
@@ -356,8 +345,7 @@
 * link:http://code.google.com/p/gerrit/source/browse/README?repo=gwtjsonrpc&name=master[XSRF JSON-RPC]
 
 
-Privacy Considerations
-----------------------
+== Privacy Considerations
 
 Gerrit stores the following information per user account:
 
@@ -409,8 +397,7 @@
 to when it is reasonable to reveal this information to a 3rd party.
 
 
-Spam and Abuse Considerations
------------------------------
+== Spam and Abuse Considerations
 
 Gerrit makes no attempt to detect spam changes or comments.  The
 somewhat high barrier to entry makes it unlikely that a spammer
@@ -441,8 +428,7 @@
 public Gerrit site actually notices spam.
 
 
-Latency
--------
+== Latency
 
 Gerrit targets for sub-250 ms per page request, mostly by using
 very compact JSON payloads between client and server.  However, as
@@ -451,8 +437,7 @@
 guarantees can be made about latency.
 
 
-Scalability
------------
+== Scalability
 
 Gerrit is designed for a very large scale open source project, or
 large commercial development project.  Roughly this amounts to
@@ -477,8 +462,7 @@
 available to the JVM and the relevant cache.*.memoryLimit variables
 are increased from their defaults.
 
-Discussion
-~~~~~~~~~~
+=== Discussion
 
 Very few, if any open source projects have more than a handful of
 Git repositories associated with them.  Since Gerrit treats each
@@ -548,8 +532,7 @@
 version of an upstream library, where the reviewer has little to do
 beyond verifying the project compiles and passes a test suite.
 
-CPU Usage - Web UI
-~~~~~~~~~~~~~~~~~~
+=== CPU Usage - Web UI
 
 Gerrit's web UI would require on average `4+F+F*C` HTTP requests to
 review a change and post comments.  Here `F` is the number of files
@@ -581,8 +564,7 @@
 Linux kernel) suggests only 8,532 queries per day, and a much lower
 0.29 QPS when spread out over an 8 hour work day.
 
-CPU Usage - Git over SSH/HTTP
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== CPU Usage - Git over SSH/HTTP
 
 A 24 core server is able to handle ~25 concurrent `git fetch`
 operations per second. The issue here is each concurrent operation
@@ -609,8 +591,7 @@
 the system can really only serve about 10 concurrent clients at the
 10 MiB/sec speed, no matter how many cores it has.
 
-Disk Usage
-~~~~~~~~~~
+=== Disk Usage
 
 The average size of a revision in the Linux kernel once compressed by
 Git is 2,327 bytes, or roughly 2 KiB.  Over the course of a year a
@@ -630,8 +611,7 @@
 developer does not need every 800+ megabyte firmware image created by
 the product's quality assurance team).
 
-Redundancy & Reliability
-------------------------
+== Redundancy & Reliability
 
 Gerrit largely assumes that the local filesystem where Git repository
 data is stored is always available.  Important data written to disk
@@ -666,8 +646,7 @@
 parties around the world have had a chance to comment.  This expected
 lag largely allows for some downtime in a disaster scenario.
 
-Backups
-~~~~~~~
+=== Backups
 
 PostgreSQL and MySQL can be configured to replicate their data to
 other systems, where they are applied to a warm-standby backup in
@@ -683,8 +662,7 @@
 Amazon S3 blob storage service.
 
 
-Logging Plan
-------------
+== Logging Plan
 
 Gerrit does not maintain logs on its own.
 
@@ -711,8 +689,7 @@
 scope of Gerrit.
 
 
-Testing Plan
-------------
+== Testing Plan
 
 Gerrit is currently manually tested through its web UI.
 
@@ -721,10 +698,9 @@
 tests are included.
 
 
-Caveats
--------
+== Caveats
 
-Reitveld can't be used as it does not provide the "submit over the
+Rietveld can't be used as it does not provide the "submit over the
 web" feature that Gerrit provides for Git.
 
 Gitosis can't be used as it does not provide any code review
@@ -739,3 +715,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index d2fc8f0..384bb74 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Eclipse Setup
-==================================
+= Gerrit Code Review - Eclipse Setup
 
 This document is about configuring Gerrit Code Review into an
 Eclipse workspace for development and debugging with GWT.
@@ -9,8 +8,7 @@
 
 
 [[Formatting]]
-Code Formatter Settings
------------------------
+== Code Formatter Settings
 
 Import `tools/GoogleFormat.xml` using Window -> Preferences ->
 Java -> Code Style -> Formatter -> Import...
@@ -19,8 +17,7 @@
 settings prefer when formatting source code.
 
 
-Site Initialization
--------------------
+== Site Initialization
 
 Build once on the command line with
 link:dev-buck.html#build[Buck] and then follow
@@ -28,23 +25,21 @@
 Developer Setup guide to configure a local site for testing.
 
 
-Testing
--------
+== Testing
 
-Running the Daemon
-~~~~~~~~~~~~~~~~~~
+=== Running the Daemon
 
 Duplicate the existing launch configuration:
 
 * Run -> Debug Configurations ...
-* Java Application -> `buck_daemon_ui_*`
+* Java Application -> `gerrit_daemon`
 * Right click, Duplicate
 
 * Modify the name to be unique.
 
 * Switch to Arguments tab.
 * Edit the `-d` program argument flag to match the path used during
-  'init'.  The template launch configuration resolves to ../test_site
+  'init'.  The template launch configuration resolves to ../gerrit_testsite
   since that is what the documentation recommends.
 
 * Switch to Common tab.
@@ -52,8 +47,7 @@
 * Close the Debug Configurations dialog and save the changes when prompted.
 
 
-Running Hosted Mode
-~~~~~~~~~~~~~~~~~~~
+=== Running Hosted Mode
 
 Duplicate the existing launch configuration:
 
@@ -66,7 +60,7 @@
 * Switch to Arguments tab.
 * Edit the `-Dgerrit.site_path=` VM argument to match the path
   used during 'init'.  The template launch configuration resolves
-  to ../test_site since that is what the documentation recommends.
+  to ../gerrit_testsite since that is what the documentation recommends.
 
 * Switch to Common tab.
 * Change Save as to be Local file.
@@ -74,8 +68,7 @@
 
 
 [[known-problems]]
-Known problems
---------------
+== Known problems
 
 * OpenID authentication won't work in hosted mode, so you need to change
 the link:config-gerrit.html#auth.type[auth.type] configuration parameter
@@ -102,3 +95,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-inspector.txt b/Documentation/dev-inspector.txt
new file mode 100644
index 0000000..2d56283
--- /dev/null
+++ b/Documentation/dev-inspector.txt
@@ -0,0 +1,293 @@
+= Gerrit Inspector
+
+== NAME
+Gerrit Inspector - Interactive Jython environment for Gerrit
+
+== SYNOPSIS
+--
+'java' -jar gerrit.war 'daemon'
+	-d <SITE_PATH>
+	[\--enable-httpd | \--disable-httpd]
+	[\--enable-sshd | \--disable-sshd]
+	[\--console-log]
+	[\--slave]
+	-s
+--
+
+== DESCRIPTION
+Runs the Gerrit network daemon on the local system as described
+in the link:pgm-daemon.html[Daemon documentation], additionally
+starting an interactive Jython shell for inspection
+and troubleshooting of live data of the Gerrit instance.
+
+CAUTION: Gerrit Inspector works directly on instances of Java Virtual
+Machine objects and it is possible to read and write instance
+members as well as invoke Java functions. Access is granted
+also to 'private' and 'protected' members. Therefore it is possible
+to introduce changes to the internal state of the system in
+an inconsistent way. Care must be taken not to break the running system
+and/or destroy the data.
+
+== INSTALLATION
+
+Gerrit Inspector requires Jython library ('jython.jar') to be installed
+in the '$site_path/lib' directory. Jython, a Python interpreter for
+the Java Virtual Machine, can be obtained from the http://www.jython.org/
+website. Only 'jython.jar' file is needed, installation of Jython libraries
+is optional. Gerrit Inspector has been tested with Jython 2.5.2 but
+might work an earlier version.
+
+== STARTUP
+
+During startup Jython examines Java libraries found on the classpath.
+While libraries are inspected a large amount of messages is displayed on the console:
+
+----
+*sys-package-mgr*: processing new jar, '/home/user/.gerritcodereview/tmp/gerrit_4890671371398741854_app/sshd-core-0.5.1-r1095809.jar'
+----
+
+After this a system-wide embedded initialization script is started. This script
+is contained in the gerrit's WAR archive. This script produces output similar to
+the following on the console:
+
+----
+"Shell" is "com.google.gerrit.pgm.shell.JythonShell@61644f2d"
+"m" is "com.google.gerrit.lifecycle.LifecycleManager@6f03b248"
+"ds" is "com.google.gerrit.server.schema.DataSourceProvider@6b3592c"
+"schk" is "com.google.gerrit.server.schema.SchemaVersionCheck@5e8cb9bd"
+
+Welcome to the Gerrit Inspector
+Enter help() to see the above again, EOF to quit and stop Gerrit
+----
+
+Then an optional user startup script is processed. It should be
+located in the gerrit user home directory as '.gerritcodereview/Startup.py'.
+
+This script can access all variables defined in the system (such
+as the ones displayed by the initialization script as shown above).
+Variables and functions defined by the startup scripts are available for
+the interactive interpreter.
+
+When interactive interpreter exits (by issuing EOF on the command line),
+a whole Gerrit instance is shut down gracefully.
+
+== USING THE INTERPRETER
+
+Gerrit Inspector launches Jython interpreter in the context of the Gerrit
+Java Virtual Machine. All core facilities of the Jython (and Python)
+language are available to the user.
+
+Additional facilities can be provided, for example a 'Lib' directory from the
+Jython distribution can be installed under '$site_path/lib/Lib' to provide
+access to many standard Python modules. Jython can also use additional Java
+classes and libraries and most of the Python modules and scripts.
+
+The Inspector has by default access to classes and object instances available
+in the Java Virtual Machine. Objects are introspected and *private* and *protected*
+members are also available.
+
+For more information on using Jython, especially with regards to its limitations
+in interfacing to the Java Virtual Machine, please refer to the
+http://www.jython.org/[Jython documentation].
+
+After successful initialization it is possible to examine components of
+Java packages, classes and live instances.
+
+----
+>>> import com.google.inject
+>>> dir(com.google.inject)
+['AbstractModule', 'Binder', 'Binding', 'BindingAnnotation', 'ConfigurationException', 'CreationException', 'Exposed', 'Guice', 'ImplementedBy', 'Inject', 'Injector', 'Key', 'MembersInjector', 'Module', 'OutOfScopeException', 'PrivateBinder', 'PrivateModule', 'ProvidedBy', 'Provider', 'Provides', 'ProvisionException', 'Scope', 'ScopeAnnotation', 'Scopes', 'Singleton', 'Stage', 'TypeLiteral', '__name__', 'assistedinject', 'binder', 'internal', 'matcher', 'name', 'servlet', 'spi', 'util']
+>>> type(com.google.inject)
+<type 'javapackage'>
+>>> dir(com.google.inject.Guice)
+['__class__', '__copy__', '__deepcopy__', '__delattr__', '__doc__',
+'__eq__', '__getattribute__', '__hash__', '__init__', '__ne__',
+'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
+'__str__', '__unicode__', 'class', 'clone', 'createInjector',
+'equals', 'finalize', 'getClass', 'hashCode', 'notify', 'notifyAll',
+'registerNatives', 'toString', 'wait']
+----
+
+Startup script provides some convenient variables to access some global Gerrit components,
+for example a connection to the review database is kept open:
+
+----
+>>> ds
+org.apache.commons.dbcp.BasicDataSource@61db2215
+>>> ds.driverClassName
+u'org.postgresql.Driver'
+>>> ds.dataSource
+org.apache.commons.dbcp.PoolingDataSource@23226fe1
+>>> ds.dataSource.connection
+jdbc:postgresql://localhost/reviewdb, UserName=rv, PostgreSQL Native Driver
+----
+
+It is also possible to interact with the ORM layer:
+
+----
+>>> db = schk.schema.open()
+>>> db
+com.google.gerrit.reviewdb.server.ReviewDb_Schema_GwtOrm$$28@24cbbdf3
+>>> db.getDialect()
+com.google.gwtorm.schema.sql.DialectPostgreSQL@4de07d3e
+>>> for x in db.patchSets().iterateAllEntities():
+...     print x
+...
+[PatchSet 1,1]
+[PatchSet 2,1]
+[PatchSet 3,1]
+[PatchSet 4,1]
+[PatchSet 5,1]
+[PatchSet 6,1]
+[PatchSet 7,1]
+[PatchSet 8,1]
+[PatchSet 6,2]
+>>> for x in db.patchComments().iterateAllEntities():
+...     print x
+com.google.gerrit.reviewdb.client.PatchLineComment@5381298a
+com.google.gerrit.reviewdb.client.PatchLineComment@44ce4dda
+com.google.gerrit.reviewdb.client.PatchLineComment@44594680
+>>> dir(com.google.gerrit.reviewdb.client.PatchLineComment)
+['Key', 'STATUS_DRAFT', 'STATUS_PUBLISHED', 'Status', '__class__',
+'__copy__', '__deepcopy__', '__delattr__', '__doc__', '__eq__',
+'__getattribute__', '__hash__', '__init__', '__ne__', '__new__',
+'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__',
+'__unicode__', 'author', 'class', 'clone', 'equals', 'finalize',
+'getAuthor', 'getClass', 'getKey', 'getLine', 'getMessage',
+'getParentUuid', 'getSide', 'getStatus', 'getWrittenOn', 'hashCode',
+'key', 'line', 'lineNbr', 'message', 'notify', 'notifyAll',
+'parentUuid', 'registerNatives', 'setMessage', 'setSide', 'setStatus',
+'side', 'status', 'toString', 'updated', 'wait', 'writtenOn']
+>>> for x in db.patchComments().iterateAllEntities():
+...     print x.status, x.line, x.message
+...
+P 2 I like it!
+P 2 more
+P 1 better
+----
+
+A built-in *help()* function provides values of global variables
+defined in the interpreter:
+
+----
+>>> help()
+"schk" is "com.google.gerrit.server.schema.SchemaVersionCheck@5e8cb9bd"
+"ds" is "com.google.gerrit.server.schema.DataSourceProvider@6b3592c"
+"m" is "com.google.gerrit.lifecycle.LifecycleManager@6f03b248"
+"Shell" is "com.google.gerrit.pgm.shell.JythonShell@61644f2d"
+"d" is "com.google.gerrit.pgm.Daemon@28a3f689"
+
+Welcome to the Gerrit Inspector
+Enter help() to see the above again, EOF to quit and stop Gerrit
+----
+
+Java and Python exceptions are intercepted by the Inspector:
+----
+>>> import java.lang.RuntimeException
+>>> raise java.lang.RuntimeException("Exiting")
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
+        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
+        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
+        at java.lang.reflect.Constructor.newInstance(Constructor.java:532)
+        at org.python.core.PyReflectedConstructor.constructProxy(PyReflectedConstructor.java:210)
+
+java.lang.RuntimeException: java.lang.RuntimeException: Exiting
+>>>
+----
+
+To exit the interpreter, use EOF character (Ctrl-D on Unix systems, Ctrl-Z on Windows).
+
+It is also possible to shut down the JVM by using *System.exit()*
+
+----
+>>> import java.lang.System
+>>> java.lang.System.exit(1)
+----
+
+And Gerrit should shut down all its subsystems and exit:
+
+----
+[2012-04-17 15:31:08,458] INFO  com.google.gerrit.pgm.Daemon : caught shutdown, cleaning up
+----
+
+== TROUBLESHOOTING
+
+Gerrit Inspector is logging to the Gerrit error log.
+
+A successful startup is indicated in the logfile:
+
+----
+  [2012-04-17 13:43:44,888] INFO  com.google.gerrit.pgm.shell.JythonShell : Jython shell instance created.
+----
+
+If 'jython.jar' library is not available, Gerrit refuses to start when given *-s* option:
+
+----
+[2012-04-17 13:57:29,611] ERROR com.google.gerrit.pgm.Daemon : Unable to start daemon
+com.google.inject.ProvisionException: Guice provision errors:
+
+1) Error injecting constructor, java.lang.UnsupportedOperationException: Cannot create Jython shell: Class org.python.util.InteractiveConsole not found
+     (You might need to install jython.jar in the lib directory)
+  at com.google.gerrit.pgm.shell.JythonShell.<init>(JythonShell.java:47)
+  while locating com.google.gerrit.pgm.shell.JythonShell
+  while locating com.google.gerrit.pgm.shell.InteractiveShell
+----
+
+Errors during processing of the startup script, 'Startup.py', are logged
+to the error log:
+
+----
+[2012-04-17 14:20:30,558] INFO  com.google.gerrit.pgm.shell.JythonShell : Jython shell instance created.
+[2012-04-17 14:20:38,005] ERROR com.google.gerrit.pgm.shell.JythonShell : Exception occured while loading file Startup.py :
+java.lang.reflect.InvocationTargetException
+        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
+        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
+        at java.lang.reflect.Method.invoke(Method.java:616)
+        at com.google.gerrit.pgm.shell.JythonShell.runMethod0(JythonShell.java:112)
+        at com.google.gerrit.pgm.shell.JythonShell.execFile(JythonShell.java:194)
+        at com.google.gerrit.pgm.shell.JythonShell.reload(JythonShell.java:178)
+        at com.google.gerrit.pgm.shell.JythonShell.run(JythonShell.java:152)
+        at com.google.gerrit.pgm.Daemon.run(Daemon.java:190)
+        at com.google.gerrit.pgm.util.AbstractProgram.main(AbstractProgram.java:67)
+        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
+        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
+        at java.lang.reflect.Method.invoke(Method.java:616)
+        at com.google.gerrit.launcher.GerritLauncher.invokeProgram(GerritLauncher.java:167)
+        at com.google.gerrit.launcher.GerritLauncher.mainImpl(GerritLauncher.java:91)
+        at com.google.gerrit.launcher.GerritLauncher.main(GerritLauncher.java:49)
+        at Main.main(Main.java:25)
+Caused by: Traceback (most recent call last):
+  File "/home/user/.gerritcodereview/Startup.py", line 1, in <module>
+    Test
+NameError: name 'Test' is not defined
+----
+
+Those errors are non-fatal. System and user scripts can be loaded again
+by issuing the following command in the Gerrit Inspector console:
+
+----
+Shell.reload()
+----
+
+== LOGGING
+Error and warning messages from the server are automatically written
+to the log file under '$site_path/logs/error_log'.
+
+Output and error messages (including Java and Python exceptions)
+resulting from interactive work are logged to the console.
+
+== KNOWN ISSUES
+The Inspector does not yet recognize Google Guice bindings.
+
+IMPORTANT: Using the Inspector may void your warranty.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 39f41b1..575f5fa 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Plugin Development
-=======================================
+= Gerrit Code Review - Plugin Development
 
 The Gerrit server functionality can be extended by installing plugins.
 This page describes how plugins for Gerrit can be developed.
@@ -24,8 +23,7 @@
 Most of this documentation refers to either type as a plugin.
 
 [[getting-started]]
-Getting started
----------------
+== Getting started
 
 To get started with the development of a plugin there are two
 recommended ways:
@@ -38,9 +36,9 @@
 ----
 mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
     -DarchetypeArtifactId=gerrit-plugin-archetype \
-    -DarchetypeVersion=2.8-SNAPSHOT \
-    -DgroupId=com.google.gerrit \
-    -DartifactId=testPlugin
+    -DarchetypeVersion=2.9-SNAPSHOT \
+    -DgroupId=com.googlesource.gerrit.plugins.testplugin \
+    -DartifactId=testplugin
 ----
 +
 Maven will ask for additional properties and then create the plugin in
@@ -66,8 +64,7 @@
 `https://gerrit-api.storage.googleapis.com/release/`.
 
 [[API]]
-API
----
+== API
 
 There are two different API formats offered against which plugins can
 be developed:
@@ -88,8 +85,7 @@
   that compiles against 2.5 will probably need source code level
   changes to work with 2.6, 2.7, and so on.
 
-Manifest
---------
+== Manifest
 
 Plugins may provide optional description information with standard
 manifest fields:
@@ -101,8 +97,7 @@
   Implementation-URL: http://example.com/opensource/plugin-foo/
 ====
 
-ApiType
-~~~~~~~
+=== ApiType
 
 Plugins using the tightly coupled `gerrit-plugin-api.jar` must
 declare this API dependency in the manifest to gain access to server
@@ -114,8 +109,7 @@
   Gerrit-ApiType: plugin
 ====
 
-Explicit Registration
-~~~~~~~~~~~~~~~~~~~~~
+=== Explicit Registration
 
 Plugins that use explicit Guice registration must name the Guice
 modules in the manifest. Up to three modules can be named in the
@@ -133,8 +127,7 @@
 ====
 
 [[plugin_name]]
-Plugin Name
-~~~~~~~~~~~
+=== Plugin Name
 
 A plugin can optionally provide its own plugin name.
 
@@ -191,9 +184,31 @@
 }
 ----
 
+A plugin can get its canonical web URL injected at runtime:
+
+[source,java]
+----
+public class MyClass {
+
+  private final String url;
+
+  @Inject
+  public MyClass(@PluginCanonicalWebUrl String url) {
+    this.url = url;
+  }
+
+  [...]
+}
+----
+
+The URL is composed of the server's canonical web URL and the plugin's
+name, i.e. `http://review.example.com:8080/plugin-name`.
+
+The canonical web URL may be injected into any .jar plugin regardless of
+whether or not the plugin provides an HTTP servlet.
+
 [[reload_method]]
-Reload Method
-~~~~~~~~~~~~~
+=== Reload Method
 
 If a plugin holds an exclusive resource that must be released before
 loading the plugin again (for example listening on a network port or
@@ -235,12 +250,11 @@
 command can be used.
 
 [[init_step]]
-Init step
-~~~~~~~~~
+=== Init step
 
 Plugins can contribute their own "init step" during the Gerrit init
 wizard. This is useful for guiding the Gerrit administrator through
-the settings needed by the plugin to work propertly.
+the settings needed by the plugin to work properly.
 
 For instance plugins to integrate Jira issues to Gerrit changes may
 contribute their own "init step" to allow configuring the Jira URL,
@@ -257,6 +271,45 @@
 In addition to the standard Gerrit init injections, plugins receive
 the @PluginName String injection containing their own plugin name.
 
+During their initialization plugins may get access to the
+`project.config` file of the `All-Projects` project and they are able
+to store configuration parameters in it. For this a plugin `InitStep`
+can get `com.google.gerrit.pgm.init.AllProjectsConfig` injected:
+
+[source,java]
+----
+  public class MyInitStep implements InitStep {
+    private final String pluginName;
+    private final ConsoleUI ui;
+    private final AllProjectsConfig allProjectsConfig;
+
+    public MyInitStep(@PluginName String pluginName, ConsoleUI ui,
+        AllProjectsConfig allProjectsConfig) {
+      this.pluginName = pluginName;
+      this.ui = ui;
+      this.allProjectsConfig = allProjectsConfig;
+    }
+
+    @Override
+    public void run() throws Exception {
+    }
+
+    @Override
+    public void postRun() throws Exception {
+      ui.message("\n");
+      ui.header(pluginName + " Integration");
+      boolean enabled = ui.yesno(true, "By default enabled for all projects");
+      Config cfg = allProjectsConfig.load();
+      if (enabled) {
+        cfg.setBoolean("plugin", pluginName, "enabled", enabled);
+      } else {
+        cfg.unset("plugin", pluginName, "enabled");
+      }
+      allProjectsConfig.save(pluginName, "Initialize " + pluginName + " Integration");
+    }
+  }
+----
+
 Bear in mind that the Plugin's InitStep class will be loaded but
 the standard Gerrit runtime environment is not available and the plugin's
 own Guice modules were not initialized.
@@ -296,12 +349,15 @@
     Section mySection = getSection("myplugin", null);
     mySection.string("Link name", "linkname", "MyLink");
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
 ----
 
 [[classpath]]
-Classpath
----------
+== Classpath
 
 Each plugin is loaded into its own ClassLoader, isolating plugins
 from each other. A plugin or extension inherits the Java runtime
@@ -316,8 +372,7 @@
 should not be necessary due to the ClassLoader isolation.
 
 [[events]]
-Listening to Events
--------------------
+== Listening to Events
 
 Certain operations in Gerrit trigger events. Plugins may receive
 notifications of these events by implementing the corresponding
@@ -331,7 +386,7 @@
 
 * `com.google.gerrit.extensions.events.LifecycleListener`:
 +
-Gerrit server startup and shutdown
+Plugin start and stop
 
 * `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
 +
@@ -341,9 +396,12 @@
 +
 Project deletion
 
+* `com.google.gerrit.extensions.events.HeadUpdatedListener`:
++
+Update of HEAD on a project
+
 [[stream-events]]
-Sending Events to the Events Stream
------------------------------------
+== Sending Events to the Events Stream
 
 Plugins may send events to the events stream where consumers of
 Gerrit's `stream-events` ssh command will receive them.
@@ -353,15 +411,29 @@
 its own custom event class derived from `ChangeEvent`.
 
 [[validation]]
-Validation Listeners
---------------------
+== Validation Listeners
 
 Certain operations in Gerrit can be validated by plugins by
 implementing the corresponding link:config-validation.html[listeners].
 
+[[receive-pack]]
+== Receive Pack Initializers
+
+Plugins may provide ReceivePack initializers which will be invoked
+by Gerrit just before a ReceivePack instance will be used. Usually,
+plugins will make use of the setXXX methods on the ReceivePack to
+set additional properties on it.
+
+[[post-receive-hook]]
+== Post Receive-Pack Hooks
+
+Plugins may register PostReceiveHook instances in order to get
+notified when JGit successfully receives a pack. This may be useful
+for those plugins which would like to monitor changes in Git
+repositories.
+
 [[ssh]]
-SSH Commands
-------------
+== SSH Commands
 
 Plugins may provide commands that can be accessed through the SSH
 interface (extensions do not have this option).
@@ -371,9 +443,12 @@
 [source,java]
 ----
 import com.google.gerrit.sshd.SshCommand;
+import com.google.gerrit.sshd.CommandMetaData;
 
+@CommandMetaData(name="print", description="Print hello command")
 class PrintHello extends SshCommand {
-  protected abstract void run() {
+  @Override
+  protected void run() {
     stdout.print("Hello\n");
   }
 }
@@ -389,7 +464,8 @@
 
 @Export("print")
 class PrintHello extends SshCommand {
-  protected abstract void run() {
+  @Override
+  protected void run() {
     stdout.print("Hello\n");
   }
 }
@@ -404,8 +480,9 @@
 import com.google.gerrit.sshd.PluginCommandModule;
 
 class MyCommands extends PluginCommandModule {
+  @Override
   protected void configureCommands() {
-    command("print").to(PrintHello.class);
+    command(PrintHello.class);
   }
 }
 ----
@@ -417,6 +494,9 @@
 $ ssh -p 29418 review.example.com helloworld print
 ----
 
+[[multiple-commands]]
+=== Multiple Commands bound to one implementation
+
 Multiple SSH commands can be bound to the same implementation class. For
 example a Gerrit Shell plugin can bind different shell commands to the same
 implementation class:
@@ -455,9 +535,35 @@
 $ ssh -p 29418 review.example.com shell ps
 ----
 
-[[configuration]]
-Configuration
--------------
+[[root-level-commands]]
+=== Root Level Commands
+
+Single command plugins are also supported. In this scenario plugin binds
+SSH command to its own name. `SshModule` must inherit from
+`SingleCommandPluginModule` class:
+
+[source,java]
+----
+public class SshModule extends SingleCommandPluginModule {
+ @Override
+ protected void configure(LinkedBindingBuilder<Command> b) {
+    b.to(ShellCommand.class);
+  }
+}
+----
+
+If the plugin above is deployed under sh.jar file in `$site/plugins`
+directory, generic commands can be called without specifying the
+actual SSH command. Note in the example below, that the called commands
+`ls` and `ps` was not explicitly bound:
+
+----
+$ ssh -p 29418 review.example.com sh ls
+$ ssh -p 29418 review.example.com sh ps
+----
+
+[[simple-configuration]]
+== Simple Configuration in `gerrit.config`
 
 In Gerrit, global configuration is stored in the `gerrit.config` file.
 If a plugin needs global configuration, this configuration should be
@@ -467,12 +573,12 @@
 plugins that have a simple configuration that only consists of
 key-value pairs. With this approach it is not possible to have
 subsections in the plugin configuration. Plugins that require a complex
-configuration need to store their configuration in their own
-configuration file where they can make use of subsections. On the other
-hand storing the plugin configuration in a 'plugin' subsection in the
-`gerrit.config` file has the advantage that administrators have all
-configuration parameters in one file, instead of having one
-configuration file per plugin.
+configuration need to store their configuration in their
+link:#configuration[own configuration file] where they can make use of
+subsections. On the other hand storing the plugin configuration in a
+'plugin' subsection in the `gerrit.config` file has the advantage that
+administrators have all configuration parameters in one file, instead
+of having one configuration file per plugin.
 
 To avoid conflicts with other plugins, it is recommended that plugins
 only use the `plugin` subsection with their own name. For example the
@@ -499,9 +605,50 @@
                      .getString("language", "English");
 ----
 
-[[project-specific-configuration]]
-Project Specific Configuration
-------------------------------
+[[configuration]]
+== Configuration in own config file
+
+Plugins can store their configuration in an own configuration file.
+This makes sense if the plugin configuration is rather complex and
+requires the usage of subsections. Plugins that have a simple
+key-value pair configuration can store their configuration in a
+link:#simple-configuration[`plugin` subsection of the `gerrit.config`
+file].
+
+The plugin configuration file must be named after the plugin and must
+be located in the `etc` folder of the review site. For example a
+configuration file for a `default-reviewer` plugin could look like
+this:
+
+.$site_path/etc/default-reviewer.config
+----
+[branch "refs/heads/master"]
+  reviewer = Project Owners
+  reviewer = john.doe@example.com
+[match "file:^.*\.txt"]
+  reviewer = My Info Developers
+----
+
+Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
+plugin can easily access its configuration:
+
+[source,java]
+----
+@Inject
+private com.google.gerrit.server.config.PluginConfigFactory cfg;
+
+[...]
+
+String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
+                        .getStringList("branch", "refs/heads/master", "reviewer");
+----
+
+The plugin configuration is loaded only once and is then cached.
+Similar to changes in 'gerrit.config', changes to the plugin
+configuration file will only become effective after a Gerrit restart.
+
+[[simple-project-specific-configuration]]
+== Simple Project Specific Configuration in `project.config`
 
 In Gerrit, project specific configuration is stored in the project's
 `project.config` file on the `refs/meta/config` branch.  If a plugin
@@ -513,12 +660,12 @@
 plugins that have a simple configuration that only consists of
 key-value pairs. With this approach it is not possible to have
 subsections in the plugin configuration. Plugins that require a complex
-configuration need to store their configuration in their own
-configuration file where they can make use of subsections. On the other
-hand storing the plugin configuration in a 'plugin' subsection in the
-`project.config` file has the advantage that project owners have all
-configuration parameters in one file, instead of having one
-configuration file per plugin.
+configuration need to store their configuration in their
+link:#project-specific-configuration[own configuration file] where they
+can make use of subsections. On the other hand storing the plugin
+configuration in a 'plugin' subsection in the `project.config` file has
+the advantage that project owners have all configuration parameters in
+one file, instead of having one configuration file per plugin.
 
 To avoid conflicts with other plugins, it is recommended that plugins
 only use the `plugin` subsection with their own name. For example the
@@ -563,9 +710,147 @@
 `refs/meta/config` branch, editing the `project.config` file and
 pushing the commit back.
 
+Plugin configuration values that are stored in the `project.config`
+file can be exposed in the ProjectInfoScreen to allow project owners
+to see and edit them from the UI.
+
+For this an instance of `ProjectConfigEntry` needs to be bound for each
+parameter. The export name must be a valid Git variable name. The
+variable name is case-insensitive, allows only alphanumeric characters
+and '-', and must start with an alphabetic character.
+
+The example below shows how the parameters `plugin.helloworld.enabled`
+and `plugin.helloworld.language` are bound to be editable from the
+WebUI. For the parameter `plugin.helloworld.enabled` "Enable Greeting"
+is provided as display name and the default value is set to `true`.
+For the parameter `plugin.helloworld.language` "Preferred Language"
+is provided as display name and "en" is set as default value.
+
+[source,java]
+----
+class Module extends AbstractModule {
+  @Override
+  protected void configure() {
+    bind(ProjectConfigEntry.class)
+        .annotatedWith(Exports.named("enabled"))
+        .toInstance(new ProjectConfigEntry("Enable Greeting", true));
+    bind(ProjectConfigEntry.class)
+        .annotatedWith(Exports.named("language"))
+        .toInstance(new ProjectConfigEntry("Preferred Language", "en"));
+  }
+}
+----
+
+By overwriting the `onUpdate` method of `ProjectConfigEntry` plugins
+can be notified when this configuration parameter is updated on a
+project.
+
+[[project-specific-configuration]]
+== Project Specific Configuration in own config file
+
+Plugins can store their project specific configuration in an own
+configuration file in the projects `refs/meta/config` branch.
+This makes sense if the plugins project specific configuration is
+rather complex and requires the usage of subsections. Plugins that
+have a simple key-value pair configuration can store their project
+specific configuration in a link:#simple-project-specific-configuration[
+`plugin` subsection of the `project.config` file].
+
+The plugin configuration file in the `refs/meta/config` branch must be
+named after the plugin. For example a configuration file for a
+`default-reviewer` plugin could look like this:
+
+.default-reviewer.config
+----
+[branch "refs/heads/master"]
+  reviewer = Project Owners
+  reviewer = john.doe@example.com
+[match "file:^.*\.txt"]
+  reviewer = My Info Developers
+----
+
+Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
+plugin can easily access its project specific configuration:
+
+[source,java]
+----
+@Inject
+private com.google.gerrit.server.config.PluginConfigFactory cfg;
+
+[...]
+
+String[] reviewers = cfg.getProjectPluginConfig(project, "default-reviewer")
+                        .getStringList("branch", "refs/heads/master", "reviewer");
+----
+
+It is also possible to get missing configuration parameters inherited
+from the parent projects:
+
+[source,java]
+----
+@Inject
+private com.google.gerrit.server.config.PluginConfigFactory cfg;
+
+[...]
+
+String[] reviewers = cfg.getProjectPluginConfigWithInheritance(project, "default-reviewer")
+                        .getStringList("branch", "refs/heads/master", "reviewer");
+----
+
+Project owners can edit the project configuration by fetching the
+`refs/meta/config` branch, editing the `<plugin-name>.config` file and
+pushing the commit back.
+
+== React on changes in project configuration
+
+If a plugin wants to react on changes in the project configuration, it
+can implement a `GitReferenceUpdatedListener` and filter on events for
+the `refs/meta/config` branch:
+
+[source,java]
+----
+public class MyListener implements GitReferenceUpdatedListener {
+
+  private final MetaDataUpdate.Server metaDataUpdateFactory;
+
+  @Inject
+  MyListener(MetaDataUpdate.Server metaDataUpdateFactory) {
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+  }
+
+  @Override
+  public void onGitReferenceUpdated(Event event) {
+    if (event.getRefName().equals(RefNames.REFS_CONFIG)) {
+      Project.NameKey p = new Project.NameKey(event.getProjectName());
+      try {
+        ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
+        ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
+
+        if (oldCfg != null && newCfg != null
+            && !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())) {
+          // submit type has changed
+          ...
+        }
+      } catch (IOException | ConfigInvalidException e) {
+        ...
+      }
+    }
+  }
+
+  private ProjectConfig parseConfig(Project.NameKey p, String idStr)
+      throws IOException, ConfigInvalidException, RepositoryNotFoundException {
+    ObjectId id = ObjectId.fromString(idStr);
+    if (ObjectId.zeroId().equals(id)) {
+      return null;
+    }
+    return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
+  }
+}
+----
+
+
 [[capabilities]]
-Plugin Owned Capabilities
--------------------------
+== Plugin Owned Capabilities
 
 Plugins may provide their own capabilities and restrict usage of SSH
 commands to the users who are granted those capabilities.
@@ -654,8 +939,7 @@
 ----
 
 [[ui_extension]]
-UI Extension
-------------
+== UI Extension
 
 Plugins can contribute UI actions on core Gerrit pages. This is useful
 for workflow customization or exposing plugin functionality through the
@@ -923,8 +1207,7 @@
 ----
 
 [[top-menu-extensions]]
-Top Menu Extensions
--------------------
+== Top Menu Extensions
 
 Plugins can contribute items to Gerrit's top menu.
 
@@ -995,9 +1278,356 @@
 Gerrit-Module: com.googlesource.gerrit.plugins.helloworld.HelloWorldModule
 ----
 
+It is also possible to show some menu entries only if the user has a
+certain capability:
+
+[source,java]
+----
+public class MyTopMenuExtension implements TopMenu {
+  private final String pluginName;
+  private final Provider<CurrentUser> userProvider;
+  private final List<MenuEntry> menuEntries;
+
+  @Inject
+  public MyTopMenuExtension(@PluginName String pluginName,
+      Provider<CurrentUser> userProvider) {
+    this.pluginName = pluginName;
+    this.userProvider = userProvider;
+    menuEntries = new ArrayList<TopMenu.MenuEntry>();
+
+    // add menu entry that is only visible to users with a certain capability
+    if (canSeeMenuEntry()) {
+      menuEntries.add(new MenuEntry("Top Menu Entry", Collections
+          .singletonList(new MenuItem("Gerrit", "http://gerrit.googlecode.com/"))));
+    }
+
+    // add menu entry that is visible to all users (even anonymous users)
+    menuEntries.add(new MenuEntry("Top Menu Entry", Collections
+          .singletonList(new MenuItem("Documentation", "/plugins/myplugin/"))));
+  }
+
+  private boolean canSeeMenuEntry() {
+    if (userProvider.get().isIdentifiedUser()) {
+      CapabilityControl ctl = userProvider.get().getCapabilities();
+      return ctl.canPerform(pluginName + "-" + MyCapability.ID)
+          || ctl.canAdministrateServer();
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public List<MenuEntry> getEntries() {
+    return menuEntries;
+  }
+}
+----
+
+[[gwt_ui_extension]]
+== GWT UI Extension
+Plugins can extend the Gerrit UI with own GWT code.
+
+The Maven archetype 'gerrit-plugin-gwt-archetype' can be used to
+generate a GWT plugin skeleton. How to use the Maven plugin archetypes
+is described in the link:#getting-started[Getting started] section.
+
+The generated GWT plugin has a link:#top-menu-extensions[top menu] that
+opens a GWT dialog box when the user clicks on it.
+
+In addition to the Gerrit-Plugin API a GWT plugin depends on
+`gerrit-plugin-gwtui`. This dependency must be specified in the
+`pom.xml`:
+
+[source,xml]
+----
+<dependency>
+  <groupId>com.google.gerrit</groupId>
+  <artifactId>gerrit-plugin-gwtui</artifactId>
+  <version>${Gerrit-ApiVersion}</version>
+</dependency>
+----
+
+A GWT plugin must contain a GWT module file, e.g. `HelloPlugin.gwt.xml`,
+that bundles together all the configuration settings of the GWT plugin:
+
+[source,xml]
+----
+<?xml version="1.0" encoding="UTF-8"?>
+<module rename-to="hello_gwt_plugin">
+  <!-- Inherit the core Web Toolkit stuff. -->
+  <inherits name="com.google.gwt.user.User"/>
+  <!-- Other module inherits -->
+  <inherits name="com.google.gerrit.Plugin"/>
+  <inherits name="com.google.gwt.http.HTTP"/>
+  <!-- Using GWT built-in themes adds a number of static -->
+  <!-- resources to the plugin. No theme inherits lines were -->
+  <!-- added in order to make this plugin as simple as possible -->
+  <!-- Specify the app entry point class. -->
+  <entry-point class="${package}.client.HelloPlugin"/>
+  <stylesheet src="hello.css"/>
+</module>
+----
+
+The GWT module must inherit `com.google.gerrit.Plugin` and
+`com.google.gwt.http.HTTP`.
+
+To register the GWT module a `GwtPlugin` needs to be bound.
+
+If no Guice modules are declared in the manifest, the GWT plugin may
+use auto-registration by using the `@Listen` annotation:
+
+[source,java]
+----
+@Listen
+public class MyExtension extends GwtPlugin {
+  public MyExtension() {
+    super("hello_gwt_plugin");
+  }
+}
+----
+
+Otherwise the binding must be done in an `HttpModule`:
+
+[source,java]
+----
+public class HttpModule extends HttpPluginModule {
+
+  @Override
+  protected void configureServlets() {
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new GwtPlugin("hello_gwt_plugin"));
+  }
+}
+----
+
+The HTTP module above must be declared in the `pom.xml` for Maven
+driven plugins:
+
+[source,xml]
+----
+<manifestEntries>
+  <Gerrit-HttpModule>com.googlesource.gerrit.plugins.myplugin.HttpModule</Gerrit-HttpModule>
+</manifestEntries>
+----
+
+The name that is provided to the `GwtPlugin` must match the GWT
+module name compiled into the plugin. The name of the GWT module
+can be explicitly set in the GWT module XML file by specifying
+the `rename-to` attribute on the module. It is important that the
+module name be unique across all plugins installed on the server,
+as the module name determines the JavaScript namespace used by the
+compiled plugin code.
+
+[source,xml]
+----
+<module rename-to="hello_gwt_plugin">
+----
+
+The actual GWT code must be implemented in a class that extends
+`com.google.gerrit.plugin.client.PluginEntryPoint`:
+
+[source,java]
+----
+public class HelloPlugin extends PluginEntryPoint {
+
+  @Override
+  public void onPluginLoad() {
+    // Create the dialog box
+    final DialogBox dialogBox = new DialogBox();
+
+    // The content of the dialog comes from a User specified Preference
+    dialogBox.setText("Hello from GWT Gerrit UI plugin");
+    dialogBox.setAnimationEnabled(true);
+    Button closeButton = new Button("Close");
+    VerticalPanel dialogVPanel = new VerticalPanel();
+    dialogVPanel.setWidth("100%");
+    dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
+    dialogVPanel.add(closeButton);
+
+    closeButton.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        dialogBox.hide();
+      }
+    });
+
+    // Set the contents of the Widget
+    dialogBox.setWidget(dialogVPanel);
+
+    RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
+    rootPanel.getElement().removeAttribute("href");
+    rootPanel.addDomHandler(new ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          dialogBox.center();
+          dialogBox.show();
+        }
+    }, ClickEvent.getType());
+  }
+}
+----
+
+This class must be set as entry point in the GWT module:
+
+[source,xml]
+----
+<entry-point class="${package}.client.HelloPlugin"/>
+----
+
+In addition this class must be defined as module in the `pom.xml` for the
+`gwt-maven-plugin` and the `webappDirectory` option of `gwt-maven-plugin`
+must be set to `${project.build.directory}/classes/static`:
+
+[source,xml]
+----
+<plugin>
+  <groupId>org.codehaus.mojo</groupId>
+  <artifactId>gwt-maven-plugin</artifactId>
+  <version>2.5.1</version>
+  <configuration>
+    <module>com.googlesource.gerrit.plugins.myplugin.HelloPlugin</module>
+    <disableClassMetadata>true</disableClassMetadata>
+    <disableCastChecking>true</disableCastChecking>
+    <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
+  </configuration>
+  <executions>
+    <execution>
+      <goals>
+        <goal>compile</goal>
+      </goals>
+    </execution>
+  </executions>
+</plugin>
+----
+
+To attach a GWT widget defined by the plugin to the Gerrit core UI
+`com.google.gwt.user.client.ui.RootPanel` can be used to manipulate the
+Gerrit core widgets:
+
+[source,java]
+----
+RootPanel rootPanel = RootPanel.get(HelloMenu.MENU_ID);
+rootPanel.getElement().removeAttribute("href");
+rootPanel.addDomHandler(new ClickHandler() {
+  @Override
+  public void onClick(ClickEvent event) {
+    dialogBox.center();
+    dialogBox.show();
+  }
+}, ClickEvent.getType());
+----
+
+GWT plugins can come with their own css file. This css file must have a
+unique name and must be registered in the GWT module:
+
+[source,xml]
+----
+<stylesheet src="hello.css"/>
+----
+
+If a GWT plugin wants to invoke the Gerrit REST API it can use
+`com.google.gerrit.plugin.client.rpc.RestApi` to construct the URL
+path and to trigger the REST calls.
+
+Example for invoking a Gerrit core REST endpoint:
+
+[source,java]
+----
+new RestApi("projects").id(projectName).view("description")
+    .put("new description", new AsyncCallback<JavaScriptObject>() {
+
+  @Override
+  public void onSuccess(JavaScriptObject result) {
+    // TODO
+  }
+
+  @Override
+  public void onFailure(Throwable caught) {
+    // never invoked
+  }
+});
+----
+
+Example for invoking a REST endpoint defined by a plugin:
+
+[source,java]
+----
+new RestApi("projects").id(projectName).view("myplugin", "myview")
+    .get(new AsyncCallback<JavaScriptObject>() {
+
+  @Override
+  public void onSuccess(JavaScriptObject result) {
+    // TODO
+  }
+
+  @Override
+  public void onFailure(Throwable caught) {
+    // never invoked
+  }
+});
+----
+
+The `onFailure(Throwable)` of the provided callback is never invoked.
+If an error occurs, it is shown in an error dialog.
+
+In order to be able to do REST calls the GWT module must inherit
+`com.google.gwt.json.JSON`:
+
+[source,xml]
+----
+<inherits name="com.google.gwt.json.JSON"/>
+----
+
+== Add Screen
+A GWT plugin can add a menu item that opens a screen that is
+implemented by the plugin. This way plugin screens can be fully
+integrated into the Gerrit UI.
+
+Example menu item:
+[source,java]
+----
+public class MyMenu implements TopMenu {
+  private final List<MenuEntry> menuEntries;
+
+  @Inject
+  public MyMenu(@PluginName String name) {
+    menuEntries = Lists.newArrayList();
+    menuEntries.add(new MenuEntry("My Menu", Collections.singletonList(
+      new MenuItem("My Screen", "#/x/" + name + "/my-screen", ""))));
+  }
+
+  @Override
+  public List<MenuEntry> getEntries() {
+    return menuEntries;
+  }
+}
+----
+
+Example screen:
+[source,java]
+----
+public class MyPlugin extends PluginEntryPoint {
+  @Override
+  public void onPluginLoad() {
+    Plugin.get().screen("my-screen", new Screen.EntryPoint() {
+      @Override
+      public void onLoad(Screen screen) {
+        screen.add(new InlineLabel("My Screen");
+        screen.show();
+      }
+    });
+  }
+}
+----
+
+[[settings-screen]]
+== Plugin Settings Screen
+
+If a plugin implements a screen for administrating its settings that is
+available under "#/x/<plugin-name>/settings" it is automatically linked
+from the plugin list screen.
+
 [[http]]
-HTTP Servlets
--------------
+== HTTP Servlets
 
 Plugins or extensions may register additional HTTP servlets, and
 wrap them with HTTP filters.
@@ -1047,8 +1677,7 @@
 ----
 
 [[data-directory]]
-Data Directory
---------------
+== Data Directory
 
 Plugins can request a data directory with a `@PluginData` File
 dependency. A data directory will be created automatically by the
@@ -1065,8 +1694,7 @@
 ----
 
 [[download-commands]]
-Download Commands
------------------
+== Download Commands
 
 Gerrit offers commands for downloading changes using different
 download schemes (e.g. for downloading via different network
@@ -1079,8 +1707,7 @@
 are provided by the Gerrit core plugin `download-commands`.
 
 [[documentation]]
-Documentation
--------------
+== Documentation
 
 If a plugin does not register a filter or servlet to handle URLs
 `/Documentation/*` or `/static/*`, the core Gerrit server will
@@ -1123,8 +1750,7 @@
 even if there is an expansion for `KEEP` in the future.
 
 [[auto-index]]
-Automatic Index
-~~~~~~~~~~~~~~~
+=== Automatic Index
 
 If a plugin does not handle its `/` URL itself, Gerrit will
 redirect clients to the plugin's `/Documentation/index.html`.
@@ -1176,15 +1802,18 @@
 |===================================================
 
 [[deployment]]
-Deployment
-----------
+== Deployment
 
 Compiled plugins and extensions can be deployed to a running Gerrit
 server using the link:cmd-plugin-install.html[plugin install] command.
 
+WebUI plugins distributed as  single `.js` file can be deployed
+without the overhead of JAR packaging, for more information refer to
+link:cmd-plugin-install.html[plugin install] command.
+
 Plugins can also be copied directly into the server's
-directory at `$site_path/plugins/$name.jar`.  The name of
-the JAR file, minus the `.jar` extension, will be used as the
+directory at `$site_path/plugins/$name.(jar|js)`.  The name of
+the JAR file, minus the `.jar` or `.js` extension, will be used as the
 plugin name. Unless disabled, servers periodically scan this
 directory for updated plugins. The time can be adjusted by
 link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
@@ -1195,8 +1824,7 @@
 Disabled plugins can be re-enabled using the
 link:cmd-plugin-enable.html[plugin enable] command.
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:js-api.html[JavaScript API]
 * link:dev-rest-api.html[REST API Developers' Notes]
@@ -1204,3 +1832,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index cf6fc02..2b0cda8 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -1,13 +1,11 @@
-Gerrit Code Review - Developer Setup
-====================================
+= Gerrit Code Review - Developer Setup
 
 Facebook Buck is needed to compile the code, and an SQL database to
 house the review metadata.  H2 is recommended for development
 databases, as it requires no external server process.
 
 
-Getting the Source
-------------------
+== Getting the Source
 
 Create a new client workspace:
 
@@ -21,15 +19,29 @@
 cloned.
 
 
-Compiling
----------
+== Compiling
 
 For details on how to build the source code with Buck, refer to:
 link:dev-buck.html#build[Building on the command line with Buck].
 
 
-Configuring Eclipse
--------------------
+== Switching between branches
+
+When switching between branches with `git checkout`, be aware that
+submodule revisions are not altered.  This may result in the wrong
+plugin revisions being present, unneeded plugins being present, or
+expected plugins being missing.
+
+After switching branches, make sure the submodules are at the correct
+revisions for the new branch with the commands:
+
+----
+  git submodule update
+  git clean -fdx
+----
+
+
+== Configuring Eclipse
 
 To use the Eclipse IDE for development, please see
 link:dev-eclipse.html[Eclipse Setup].
@@ -38,8 +50,7 @@
 refer to: link:dev-buck.html#eclipse[Eclipse integration with Buck].
 
 
-Mac OS X
---------
+== Mac OS X
 
 On Mac OS X ensure "Java For Mac OS X 10.5 Upate 4" (or later) has
 been installed, and that `JAVA_HOME` is set to
@@ -50,14 +61,13 @@
 
 
 [[init]]
-Site Initialization
--------------------
+== Site Initialization
 
 After compiling (above), run Gerrit's 'init' command to create a
 testing site for development use:
 
 ----
-  java -jar buck-out/gen/gerrit.war init -d ../test_site
+  java -jar buck-out/gen/gerrit.war init -d ../gerrit_testsite
 ----
 
 Accept defaults by pressing Enter until 'init' completes, or add
@@ -72,17 +82,15 @@
 through the web interface:
 
 ----
-  ../test_site/bin/gerrit.sh stop
+  ../gerrit_testsite/bin/gerrit.sh stop
 ----
 
 
-Testing
--------
+== Testing
 
 
 [[tests]]
-Running the Acceptance Tests
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Running the Acceptance Tests
 
 Gerrit has a set of integration tests that test the Gerrit daemon via
 REST, SSH and the git protocol.
@@ -96,25 +104,59 @@
 link:dev-buck.html#tests[Running integration tests with Buck].
 
 
-Running the Daemon
-~~~~~~~~~~~~~~~~~~
+=== Running the Daemon
 
 The daemon can be directly launched from the build area, without
 copying to the test site:
 
 ----
-  java -jar buck-out/gen/gerrit.war daemon -d ../test_site
+  java -jar buck-out/gen/gerrit.war daemon -d ../gerrit_testsite
 ----
 
+=== Running the Daemon with Gerrit Inspector
 
-Querying the Database
-~~~~~~~~~~~~~~~~~~~~~
+link:dev-inspector.html[Gerrit Inspector] is an interactive scriptable
+environment to inspect and modify internal state of the system.
+
+This environment is available on the system console after
+the system starts. Leaving the Inspector will shutdown the Gerrit
+instance.
+
+The environment allows interactive work as well as running of
+Python scripts for troubleshooting.
+
+Gerrit Inspect can be started by adding '-s' option to the
+command used to launch the daemon:
+
+----
+  java -jar buck-out/gen/gerrit.war daemon -d ../gerrit_testsite -s
+----
+
+Gerrit Inspector examines Java libraries first, then loads
+its initialization scripts and then starts a command line
+prompt on the console:
+
+----
+  Welcome to the Gerrit Inspector
+  Enter help() to see the above again, EOF to quit and stop Gerrit
+  Jython 2.5.2 (Release_2_5_2:7206, Mar 2 2011, 23:12:06)
+  [OpenJDK 64-Bit Server VM (Sun Microsystems Inc.)] on java1.6.0 running for Gerrit 2.3-rc0-163-g01967ef
+  >>>
+----
+
+With the Inspector enabled Gerrit can be used normally and all
+interfaces (HTTP, SSH etc.) are available.
+
+Care must be taken not to modify internal state of the system
+when using the Inspector.
+
+=== Querying the Database
 
 The embedded H2 database can be queried and updated from the
 command line.  If the daemon is not currently running:
 
 ----
-  java -jar buck-out/gen/gerrit.war gsql -d ../test_site
+  java -jar buck-out/gen/gerrit.war gsql -d ../gerrit_testsite
 ----
 
 Or, if it is running and the database is in use, connect over SSH
@@ -126,8 +168,7 @@
 
 
 [[debug-javascript]]
-Debugging JavaScript
-~~~~~~~~~~~~~~~~~~~~
+=== Debugging JavaScript
 
 When debugging browser specific issues add `?dbg=1` to the URL so the
 resulting JavaScript more closely matches the Java sources.  The debug
@@ -146,8 +187,7 @@
 ----
 
 
-Release Builds
---------------
+== Release Builds
 
 To create a release build for a production server, or deployment
 through the download site:
@@ -165,8 +205,7 @@
 ----
 
 
-Client-Server RPC
------------------
+== Client-Server RPC
 
 The client-server RPC implementation is gwtjsonrpc, not the stock RPC
 system that comes with GWT.  This buys us automatic XSRF protection.
@@ -177,15 +216,13 @@
 extend RemoteJsonService instead of RemoteService.
 
 
-Why GWT?
---------
+== Why GWT?
 
 We like it.  Plus we can write Java code once and run it both in
 the browser and on the server side.
 
 
-External Links
---------------
+== External Links
 
 Google Web Toolkit:
 
@@ -209,3 +246,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt
index f25aef7..4a4e894 100644
--- a/Documentation/dev-release-deploy-config.txt
+++ b/Documentation/dev-release-deploy-config.txt
@@ -1,5 +1,4 @@
-Deploy Gerrit Artifacts
-=======================
+= Deploy Gerrit Artifacts
 
 Gerrit Artifacts are stored on
 link:https://developers.google.com/storage/[Google Cloud Storage].
@@ -16,13 +15,8 @@
 +
 Bucket to store Gerrit Subproject Artifacts (e.g. `gwtjsonrpc` etc.).
 
-* `gerrit-plugins`:
-+
-Bucket to store Gerrit Core Plugin Artifacts.
-
 [[deploy-configuration-settings-xml]]
-Deploy Configuration in Maven `settings.xml`
---------------------------------------------
+== Deploy Configuration in Maven `settings.xml`
 
 To upload artifacts to a bucket the user must authenticate with a
 username and password. The username and password need to be retrieved
@@ -62,8 +56,7 @@
 ----
 
 [[deploy-configuration-subprojects]]
-Gerrit Subprojects
-~~~~~~~~~~~~~~~~~~
+=== Gerrit Subprojects
 
 * You will need to have the following in the `pom.xml` to make it
 deployable to the `gerrit-maven` storage bucket:
@@ -95,40 +88,9 @@
 ----
 
 
-[[deploy-configuration-core-plugins]]
-Gerrit Core Plugins
-~~~~~~~~~~~~~~~~~~~
-
-* You will need to have the following in the `pom.xml` to make it
-deployable to the `gerrit-plugins` storage bucket:
-
-----
-  <distributionManagement>
-    <repository>
-      <id>gerrit-plugins-repository</id>
-      <name>Gerrit Plugins Repository</name>
-      <url>gs://gerrit-plugins</url>
-      <uniqueVersion>true</uniqueVersion>
-    </repository>
-  </distributionManagement>
-----
-
-
-* Add this to the `pom.xml` to enable the wagon provider:
-
-----
-  <build>
-    <extensions>
-      <extension>
-        <groupId>com.googlesource.gerrit</groupId>
-        <artifactId>gs-maven-wagon</artifactId>
-        <version>3.3</version>
-      </extension>
-    </extensions>
-  </build>
-----
-
-
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-release-subproject.txt b/Documentation/dev-release-subproject.txt
index 956bd29..9571edb 100644
--- a/Documentation/dev-release-subproject.txt
+++ b/Documentation/dev-release-subproject.txt
@@ -1,21 +1,7 @@
-Making a Release of a Gerrit Subproject / Core Plugin
-=====================================================
+= Making a Release of a Gerrit Subproject
 
 [[make-snapshot]]
-Make a Snapshot
----------------
-
-* Only for plugins:
-** In the `pom.xml` update the Gerrit version under `properties` >
-`Gerrit-ApiVersion` to the version of the new Gerrit
-release.
-** Make sure that the URL for the Maven repository with the id
-`gerrit-api-repository` in the `pom.xml` is correct.
-+
-If `Gerrit-ApiVersion` references a released Gerrit version it must be
-`https://gerrit-api.stoarge.googleapis.com/release/`, if
-`Gerrit-ApiVersion` references a snapshot Gerrit version it must be
-`https://gerrit-api.storage.googleapis.com/snapshot/`.
+== Make a Snapshot
 
 * Build the latest snapshot and install it into the local Maven
 repository:
@@ -27,11 +13,10 @@
 * Test Gerrit with this snapshot locally
 
 
-Publish Snapshot
-----------------
+== Publish Snapshot
 
-If a Snapshot for a Subproject was created that should be referenced by
-Gerrit while current Gerrit development is ongoing, this Snapshot needs
+If a snapshot for a subproject was created that should be referenced by
+Gerrit while current Gerrit development is ongoing, this snapshot needs
 to be published.
 
 * Make sure you have done the configuration needed for deployment:
@@ -46,21 +31,20 @@
   mvn deploy
 ====
 
-* Change the version in the Gerrit parent `pom.xml` for the Subproject
-to the `SNAPSHOT` version
+* Change the `id`, `bin_sha1`, and `src_sha1` values in the `maven_jar`
+for the subproject in `/lib/BUCK` to the `SNAPSHOT` version.
 +
-When Gerrit gets released, a release of the Subproject has to be done
-and Gerrit has to reference the released Subproject version.
+When Gerrit gets released, a release of the subproject has to be done
+and Gerrit has to reference the released subproject version.
 
 
 [[prepare-release]]
-Prepare the Release
--------------------
+== Prepare the Release
 
 * link:#make-snapshot[First create (and test) the latest snapshot for
-the subproject/plugin]
+the subproject]
 
-* Update the top level `pom.xml` in the subproject/plugin to reflect
+* 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)
 
@@ -78,15 +62,13 @@
 
 
 [[publish-release]]
-Publish the Release
--------------------
+== Publish the Release
 
 * Make sure you have done the configuration needed for deployment:
 ** link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
 Configuration in Maven `settings.xml`]
 ** Configuration in `pom.xml` for
-link:dev-release-deploy-config.html#deploy-configuration-subprojects[Subprojects] or
-link:dev-release-deploy-config.html#deploy-configuration-core-plugins[Core Plugins]
+link:dev-release-deploy-config.html#deploy-configuration-subprojects[subprojects]
 
 * Deploy the new release:
 +
@@ -107,3 +89,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index f9d0d0e..72247cf 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -1,5 +1,4 @@
-Making a Gerrit Release
-=======================
+= Making a Gerrit Release
 
 [NOTE]
 ========================================================================
@@ -16,16 +15,14 @@
 tasks.
 
 
-Gerrit Release Type
--------------------
+== Gerrit Release Type
 
 Here are some guidelines on release approaches depending on the
 type of release you want to make (`stable-fix`, `stable`, `RC0`,
 `RC1`...).
 
 [[stable]]
-Stable
-~~~~~~
+=== Stable
 
 A `stable` release is generally built from the `master` branch and may
 need to undergo some stabilization before releasing the final release.
@@ -51,8 +48,7 @@
 * Finally create the `stable` release (no `RC`)
 
 
-Stable-Fix
-~~~~~~~~~~
+=== Stable-Fix
 
 `stable-fix` releases should likely only contain bug fixes and doc
 updates.
@@ -64,8 +60,7 @@
 
 
 [[security]]
-Security-Fix
-~~~~~~~~~~~~
+=== Security-Fix
 
 `security-fix` releases should only contain bug fixes for security
 issues.
@@ -82,19 +77,14 @@
 `gerrit` project.
 
 
-Create the Actual Release
--------------------------
+== Create the Actual Release
 
 To create a Gerrit release the following steps have to be done:
 
 . link:#subproject[Release Subprojects]
-. link:#prepare-gerrit[Prepare the Gerrit Release]
-.. link:#prepare-war-and-plugin-api[Prepare the Gerrit WAR and the Plugin API Jar]
-.. link:#prepare-core-plugins[Prepare the Core Plugins]
-.. link:#prepare-war-with-plugins[Prepare Gerrit WAR with Core Plugins]
+. link:#build-gerrit[Build the Gerrit Release]
 . link:#publish-gerrit[Publish the Gerrit Release]
 .. link:#extension-and-plugin-api[Publish the Extension and Plugin API Jars]
-.. link:#publish-core-plugins[Publish the Core Plugins]
 .. link:#publish-gerrit-war[Publish the Gerrit WAR (with Core Plugins)]
 .. link:#push-stable[Push the Stable Branch]
 .. link:#push-tag[Push the Release Tag]
@@ -106,8 +96,7 @@
 
 
 [[subproject]]
-Release Subprojects
-~~~~~~~~~~~~~~~~~~~
+=== Release Subprojects
 
 The subprojects to be released are:
 
@@ -127,67 +116,50 @@
 * link:dev-release-subproject.html#prepare-release[Prepare the Release]
 * link:dev-release-subproject.html#publish-release[Publish the Release]
 
-* Update the version of the Subproject in the Gerrit parent `pom.xml`
-to the released version
+* Update the `id`, `bin_sha1`, and `src_sha1` values in the `maven_jar`
+for the Subproject in `/lib/BUCK` to the released version.
 
 
 [[build-gerrit]]
-Build Gerrit
-~~~~~~~~~~~~
+=== Build Gerrit
 
-* Build the Gerrit WAR
+* Build the Gerrit WAR and API JARs
 +
-====
- ./tools/release.sh
-====
+----
+  buck build release
+  buck build api_install
+----
 
 * Sanity check WAR
 * Test the new Gerrit version
 
 [[publish-gerrit]]
-Publish the Gerrit Release
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Publish the Gerrit Release
 
 
 [[extension-and-plugin-api]]
-Publish the Extension and Plugin API Jars
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Publish the Extension and Plugin API Jars
 
 * Make sure you have done the
 link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
 configuration needed for deployment]
 
-* Push the Jars to `storage.googleapis.com`:
+* Push the Jars to the storage bucket:
 +
 ----
-  ./tools/deploy_api.sh
+  buck build api_deploy
 ----
 
 
-[[publish-core-plugins]]
-Publish the Core Plugins
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-* link:dev-release-subproject.html#publish-release[Publish the Release]
-
-
 [[publish-gerrit-war]]
-Publish the Gerrit WAR (with Core Plugins)
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Publish the Gerrit WAR (with Core Plugins)
 
-* The WAR file to upload is `gerrit-package-plugins\target\gerrit-full-v2.5.war`
-* Upload WAR to `code.google.com/p/gerrit` (manual via web browser)
-** Go to http://code.google.com/p/gerrit/downloads/list
-** Use the `New Download` button
-
-* Update labels:
-** new war: [`release-candidate`], `featured`...
-** old war: `deprecated`
+* The WAR file to upload is `buck-out/gen/release.war`
+* Upload WAR to the storage bucket via `https://cloud.google.com/console` (manual via web browser)
 
 
 [[push-stable]]
-Push the Stable Branch
-^^^^^^^^^^^^^^^^^^^^^^
+==== Push the Stable Branch
 
 * create the stable branch `stable-2.5` in the `gerrit` project
 +
@@ -199,62 +171,43 @@
 
 
 [[push-tag]]
-Push the Release Tag
-^^^^^^^^^^^^^^^^^^^^
+==== Push the Release Tag
 
 * Push the new Release Tag
 +
 For an `RC`:
 +
-====
- git push gerrit-review refs/tags/v2.5-rc0:refs/tags/v2.5-rc0
-====
+----
+  git push gerrit-review refs/tags/v2.5-rc0:refs/tags/v2.5-rc0
+----
 +
 For a final `stable` release:
 +
-====
- git push gerrit-review refs/tags/v2.5:refs/tags/v2.5
-====
+----
+  git push gerrit-review refs/tags/v2.5:refs/tags/v2.5
+----
 
 
 [[upload-documentation]]
-Upload the Documentation
-^^^^^^^^^^^^^^^^^^^^^^^^
+==== Upload the Documentation
 
-====
- make -C Documentation PRIOR=2.4 update
- make -C ReleaseNotes update
-====
+Build the release notes:
 
-(no +PRIOR=+... if updating the same release again during RCs)
+----
+  make -C ReleaseNotes
+----
 
+* Upload html files to the storage bucket via `https://cloud.google.com/console` (manual via web browser)
+** Documentation html files must be extracted from `buck-out/gen/Documentation/html.zip`
 * Update Google Code project links
 ** Go to http://code.google.com/p/gerrit/admin
-** Point the main page to the new docs. The link to the documentation has to be
-updated at two places: in the project description and also in the `Links`
-section.
-** Point the main page to the new release notes
-
-[NOTE]
-========================================================================
-The docs makefile does an `svn cp` of the prior revision of the docs to
-branch the docs so you have less to upload on the new docs.
-
-User and password from here:
-
-    https://code.google.com/hosting/settings
-
-If subversion assumes a different username than your google one and asks for a
-password right away simply hit enter. Subversion will fail and then ask for
-another username and password. This time enter the username and password from
-the page linked above. After that subversion should save the username/password
-somewhere under `~/.subversion/auth` folder.
-========================================================================
-
+** Update the documentation link in the `Resources` section of the
+Description text, and in the `Links` section.
+** Add a link to the new release notes in the `News` section of the
+Description text
 
 [[update-issues]]
-Update the Issues
-^^^^^^^^^^^^^^^^^
+==== Update the Issues
 
 ====
  How do the issues get updated?  Do you run a script to do
@@ -274,8 +227,7 @@
 
 
 [[announce]]
-Announce on Mailing List
-^^^^^^^^^^^^^^^^^^^^^^^^
+==== Announce on Mailing List
 
 * Send an email to the mailing list to announce the release, consider
 including some or all of the following in the email:
@@ -327,35 +279,32 @@
 
 
 [[increase-version]]
-Increase Gerrit Version for Current Development
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Increase Gerrit Version for Current Development
 
 All new development that is done in the `master` branch will be
-included in the next Gerrit release. Update the Gerrit version in each
-`pom.xml` file to the next `SNAPSHOT`version. Push the change for
-review and get it merged.
-
-====
- tools/version.sh --snapshot=2.6
-====
+included in the next Gerrit release. Update the Gerrit version in the
+`VERSION` file, and plugin archetypes' `pom.xml` files. Push the change
+for review and get it merged.
 
 
 [[merge-stable]]
-Merge `stable` into `master`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Merge `stable` into `master`
 
 After every release, stable should be merged to master to ensure that
 none of the changes/fixes ever get lost.
 
-====
- git config merge.summary true
- git checkout master
- git reset --hard origin/master
- git branch -f stable origin/stable
- git merge stable
-====
+----
+  git config merge.summary true
+  git checkout master
+  git reset --hard origin/master
+  git branch -f stable origin/stable
+  git merge stable
+----
 
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-rest-api.txt b/Documentation/dev-rest-api.txt
index 0175a62..d07da62 100644
--- a/Documentation/dev-rest-api.txt
+++ b/Documentation/dev-rest-api.txt
@@ -1,17 +1,14 @@
-Gerrit Code Review - REST API Developers' Notes
-===============================================
+= Gerrit Code Review - REST API Developers' Notes
 
 This document is about developing the REST API.  For details of the
 actual APIs available in Gerrit, please see the
 link:rest-api.html[REST API interface reference].
 
 
-Testing REST API Functionality
-------------------------------
+== Testing REST API Functionality
 
 
-Basic Testing
-~~~~~~~~~~~~~
+=== Basic Testing
 
 Basic testing of REST API functionality can be done with `curl`:
 
@@ -29,8 +26,7 @@
 ----
 
 
-Sending Data in the Request
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Sending Data in the Request
 
 Some REST APIs accept data in the request body of `PUT` and `POST` requests.
 
@@ -49,8 +45,7 @@
 ----
 
 
-Authentication
-~~~~~~~~~~~~~~
+=== Authentication
 
 To test APIs that require authentication, the username and password must be specified on
 the command line:
@@ -71,8 +66,7 @@
 In both cases, the password should be the user's link:user-upload.html#http[HTTP password].
 
 
-Verifying Header Content
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== Verifying Header Content
 
 To verify the headers returned from a REST API call, use `curl` in verbose mode:
 
@@ -87,3 +81,5 @@
 ------
 Part of link:index.html[Gerrit Code Review]
 
+SEARCHBOX
+---------
diff --git a/Documentation/error-branch-not-found.txt b/Documentation/error-branch-not-found.txt
index bd8d090..b866a70 100644
--- a/Documentation/error-branch-not-found.txt
+++ b/Documentation/error-branch-not-found.txt
@@ -1,5 +1,4 @@
-branch ... not found
-====================
+= branch ... not found
 
 With this error message Gerrit rejects to push a commit for code
 review if the specified target branch does not exist.
@@ -32,3 +31,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-change-closed.txt b/Documentation/error-change-closed.txt
index 3244fb3..d8842c5 100644
--- a/Documentation/error-change-closed.txt
+++ b/Documentation/error-change-closed.txt
@@ -1,11 +1,9 @@
-change ... closed
-=================
+= change ... closed
 
 With this error message Gerrit rejects to push a commit or submit a
 review label (approval) to a change that is already closed.
 
-When Pushing a Commit
----------------------
+== When Pushing a Commit
 
 This error occurs if you are trying to push a commit that contains
 the Change-Id of a closed change in its commit message. A change can
@@ -27,8 +25,7 @@
 'Restore Change' button). Afterwards the push should succeed and a
 new patch set for this change will be created.
 
-When Submitting a Review Label
-------------------------------
+== When Submitting a Review Label
 
 This error occurs if you are trying to submit a review label (approval) using
 the link:cmd-review.html[ssh review command] after the change has been closed.
@@ -39,3 +36,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-change-does-not-belong-to-project.txt b/Documentation/error-change-does-not-belong-to-project.txt
index e747881..ce9c23a1e 100644
--- a/Documentation/error-change-does-not-belong-to-project.txt
+++ b/Documentation/error-change-does-not-belong-to-project.txt
@@ -1,5 +1,4 @@
-change ... does not belong to project ...
-=========================================
+= change ... does not belong to project ...
 
 With this error message Gerrit rejects to push a commit to a change
 that belongs to another project.
@@ -14,3 +13,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-change-not-found.txt b/Documentation/error-change-not-found.txt
index b6df13b..98969b0 100644
--- a/Documentation/error-change-not-found.txt
+++ b/Documentation/error-change-not-found.txt
@@ -1,5 +1,4 @@
-change ... not found
-====================
+= change ... not found
 
 With this error message Gerrit rejects to push a commit to a change
 that cannot be found.
@@ -13,3 +12,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-change-upload-blocked.txt b/Documentation/error-change-upload-blocked.txt
index 6bad02e..fd501b7 100644
--- a/Documentation/error-change-upload-blocked.txt
+++ b/Documentation/error-change-upload-blocked.txt
@@ -1,5 +1,4 @@
-One or more refs/for/ names blocks change upload
-================================================
+= One or more refs/for/ names blocks change upload
 
 With this error message Gerrit rejects to push a commit for code
 review if the remote git repository has a branch under the
@@ -39,3 +38,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-commit-already-exists.txt b/Documentation/error-commit-already-exists.txt
index 78ce5f2..d2b7c9d 100644
--- a/Documentation/error-commit-already-exists.txt
+++ b/Documentation/error-commit-already-exists.txt
@@ -1,5 +1,4 @@
-commit already exists
-=====================
+= commit already exists
 
 With "commit already exists (as current patchset)" or
 "commit already exists (in the change)" error message
@@ -21,3 +20,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-contains-banned-commit.txt b/Documentation/error-contains-banned-commit.txt
index 8a30c44..13a0eaa 100644
--- a/Documentation/error-contains-banned-commit.txt
+++ b/Documentation/error-contains-banned-commit.txt
@@ -1,5 +1,4 @@
-contains banned commit ...
-==========================
+= contains banned commit ...
 
 With this error message Gerrit rejects to push a commit that is
 banned or that would merge in an ancestor that is banned.
@@ -19,3 +18,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-has-duplicates.txt b/Documentation/error-has-duplicates.txt
index b5175c0..c34bf52 100644
--- a/Documentation/error-has-duplicates.txt
+++ b/Documentation/error-has-duplicates.txt
@@ -1,5 +1,4 @@
-\... has duplicates
-===================
+= \... has duplicates
 
 With this error message Gerrit rejects to push a commit if its commit
 message contains a Change-ID for which multiple changes can be found
@@ -22,3 +21,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-invalid-author.txt b/Documentation/error-invalid-author.txt
index c484776..5808d4f 100644
--- a/Documentation/error-invalid-author.txt
+++ b/Documentation/error-invalid-author.txt
@@ -1,5 +1,4 @@
-invalid author
-==============
+= invalid author
 
 For every pushed commit Gerrit verifies that the e-mail address of
 the author matches one of the registered e-mail addresses of the
@@ -14,8 +13,7 @@
 . missing privileges to push commits of other authors
 
 
-Incorrect configuration of the e-mail address on client or server side
-----------------------------------------------------------------------
+== Incorrect configuration of the e-mail address on client or server side
 
 If pushing to Gerrit fails with the error message "invalid author"
 and you are the author of the commit for which the push
@@ -23,8 +21,7 @@
 address for your Gerrit account or the author information of the
 pushed commit is incorrect.
 
-Configuration of e-mail address in Gerrit
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Configuration of e-mail address in Gerrit
 
 Check in Gerrit under 'Settings -> Identities' which e-mail addresses
 you've configured for your Gerrit account.  If no e-mail address is
@@ -34,8 +31,7 @@
 If you don't receive the e-mail verification mail it might be that it
 was caught by your spam filter.
 
-Incorrect author information
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Incorrect author information
 
 For every commit Git maintains the author. If not explicitly
 specified Git computes the author on commit out of the Git
@@ -128,8 +124,7 @@
 link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation].
 
 
-Missing privileges to push commits of other users
--------------------------------------------------
+== Missing privileges to push commits of other users
 
 If pushing to Gerrit fails with the error message "invalid author"
 and somebody else is author of the commit for which the
@@ -143,3 +138,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-invalid-changeid-line.txt b/Documentation/error-invalid-changeid-line.txt
index 9235266..1c6e446 100644
--- a/Documentation/error-invalid-changeid-line.txt
+++ b/Documentation/error-invalid-changeid-line.txt
@@ -1,5 +1,4 @@
-invalid Change-Id line format in commit message footer
-======================================================
+= invalid Change-Id line format in commit message footer
 
 With this error message Gerrit rejects to push a commit if its commit
 message footer contains an invalid Change-Id line.
@@ -20,8 +19,7 @@
 Change-Id will be automatically generated and inserted.
 
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:user-changeid.html[Change-Id Lines]
 
@@ -29,3 +27,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-invalid-committer.txt b/Documentation/error-invalid-committer.txt
index 447064e..0e48480 100644
--- a/Documentation/error-invalid-committer.txt
+++ b/Documentation/error-invalid-committer.txt
@@ -1,5 +1,4 @@
-invalid committer
-=================
+= invalid committer
 
 For every pushed commit Gerrit verifies that the e-mail address of
 the committer matches one of the registered e-mail addresses of the
@@ -16,8 +15,7 @@
   users
 
 
-Incorrect configuration of the e-mail address on client or server side
-----------------------------------------------------------------------
+== Incorrect configuration of the e-mail address on client or server side
 
 If pushing to Gerrit fails with the error message "invalid committer"
 and you committed the change for which the push fails,
@@ -25,8 +23,7 @@
 for your Gerrit account or the committer information of the pushed
 commit is incorrect.
 
-Configuration of e-mail address in Gerrit
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Configuration of e-mail address in Gerrit
 
 Check in Gerrit under 'Settings -> Identities' which e-mail addresses
 you've configured for your Gerrit account.  If no e-mail address is
@@ -34,8 +31,7 @@
 e-mail address there. Make sure you confirm your e-mail address by
 clicking on the link in the e-mail verification mail sent by Gerrit.
 
-Incorrect committer information
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Incorrect committer information
 
 For every commit Git maintains the user who did the commit, the so
 called committer. Git computes the committer out of the Git
@@ -93,8 +89,7 @@
 link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation].
 
 
-Missing privileges to push commits that were committed by other users
----------------------------------------------------------------------
+== Missing privileges to push commits that were committed by other users
 
 If pushing to Gerrit fails with the error message "invalid committer"
 and somebody else committed the change for which the
@@ -108,3 +103,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-messages.txt b/Documentation/error-messages.txt
index 58e9e02..16bc37b 100644
--- a/Documentation/error-messages.txt
+++ b/Documentation/error-messages.txt
@@ -1,13 +1,11 @@
-Gerrit Code Review - Error Messages
-===================================
+= Gerrit Code Review - Error Messages
 
 This page provides access to detailed explanations of Gerrit error
 messages. For each error message it is explained why the error is
 occurring and what can be done to solve it.
 
 
-Error Messages
---------------
+== Error Messages
 
 * link:error-branch-not-found.html[branch ... not found]
 * link:error-change-closed.html[change ... closed]
@@ -20,6 +18,7 @@
 * link:error-invalid-changeid-line.html[invalid Change-Id line format in commit message footer]
 * link:error-invalid-committer.html[invalid committer]
 * link:error-missing-changeid.html[missing Change-Id in commit message footer]
+* link:error-missing-subject.html[missing subject; Change-Id must be in commit message footer]
 * link:error-multiple-changeid-lines.html[multiple Change-Id lines in commit message footer]
 * link:error-no-changes-made.html[no changes made]
 * link:error-no-common-ancestry.html[no common ancestry]
@@ -38,8 +37,7 @@
 * link:error-not-allowed-to-upload-merges.html[you are not allowed to upload merges]
 
 
-General Hints
--------------
+== General Hints
 
 * link:error-push-fails-due-to-commit-message.html[push fails due to commit message]
 
@@ -47,3 +45,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-missing-changeid.txt b/Documentation/error-missing-changeid.txt
index edbc63b..714d65e 100644
--- a/Documentation/error-missing-changeid.txt
+++ b/Documentation/error-missing-changeid.txt
@@ -1,5 +1,4 @@
-missing Change-Id in commit message footer
-==========================================
+= missing Change-Id in commit message footer
 
 With this error message Gerrit rejects to push a commit to a project
 which is configured to always require a Change-Id in the commit
@@ -11,7 +10,6 @@
 . missing Change-Id in the commit message
 . Change-Id is contained in the commit message but not in the last
   paragraph
-. Change-Id is the only line in the commit message
 
 You can see the commit messages for existing commits in the history
 by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
@@ -21,8 +19,7 @@
 message on every commit.
 
 
-Missing Change-Id in the commit message
----------------------------------------
+== Missing Change-Id in the commit message
 
 If the commit message of a commit that you want to push does not
 contain a Change-Id you have to update its commit message and insert
@@ -41,8 +38,7 @@
 is explained link:error-push-fails-due-to-commit-message.html[here].
 
 
-Change-Id is contained in the commit message but not in the last paragraph
---------------------------------------------------------------------------
+== Change-Id is contained in the commit message but not in the last paragraph
 
 To be picked up by Gerrit, a Change-Id must be in the last paragraph
 of a commit message, for details, see link:user-changeid.html[Change-Id Lines].
@@ -52,21 +48,10 @@
 Change-ID into the last paragraph. How to update the commit message
 is explained link:error-push-fails-due-to-commit-message.html[here].
 
-Change-Id is the only line in the commit message
-------------------------------------------------
-
-Gerrit does not parse the subject of a commit message for the
-Change-Id even if this is the only and last paragraph of the commit
-message.
-
-If the Change-Id is the only line in the commit message you must update
-the commit message and insert a subject as the first line in the commit
-message. The Change-Id must be in the last paragraph of the commit
-message, i.e. separated from the subject by a blank line. How to update
-the commit message is explained
-link:error-push-fails-due-to-commit-message.html[here].
-
 
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-missing-subject.txt b/Documentation/error-missing-subject.txt
new file mode 100644
index 0000000..3703ade
--- /dev/null
+++ b/Documentation/error-missing-subject.txt
@@ -0,0 +1,33 @@
+= missing subject; Change-Id must be in commit message footer
+
+With this error message Gerrit rejects to push a commit to a project
+which is configured to always require a Change-Id in the commit
+message if the commit message of the pushed commit does not contain
+a subject and a message, but only a Change-Id.
+
+This error happens if the Change-Id is the only line in the commit
+message.
+
+You can see the commit messages for existing commits in the history
+by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
+
+== Change-Id is the only line in the commit message
+
+Gerrit does not parse the subject of a commit message for the
+Change-Id even if this is the only and last paragraph of the commit
+message.
+
+If the Change-Id is the only line in the commit message you must update
+the commit message and insert a subject as the first line in the commit
+message. The Change-Id must be in the last paragraph of the commit
+message, i.e. separated from the subject by a blank line. How to update
+the commit message is explained
+link:error-push-fails-due-to-commit-message.html[here].
+
+
+GERRIT
+------
+Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-multiple-changeid-lines.txt b/Documentation/error-multiple-changeid-lines.txt
index 9fa2b91..ca15270 100644
--- a/Documentation/error-multiple-changeid-lines.txt
+++ b/Documentation/error-multiple-changeid-lines.txt
@@ -1,5 +1,4 @@
-multiple Change-Id lines in commit message footer
-=================================================
+= multiple Change-Id lines in commit message footer
 
 With this error message Gerrit rejects to push a commit if the commit
 message footer of the pushed commit contains several Change-Id lines.
@@ -20,8 +19,7 @@
 will be automatically generated and inserted.
 
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:user-changeid.html[Change-Id Lines]
 
@@ -29,3 +27,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-no-changes-made.txt b/Documentation/error-no-changes-made.txt
index d0e1d4f..6182fcf 100644
--- a/Documentation/error-no-changes-made.txt
+++ b/Documentation/error-no-changes-made.txt
@@ -1,5 +1,4 @@
-no changes made
-===============
+= no changes made
 
 With this error message Gerrit rejects to push a commit as a new
 patch set for a change, if the pushed commit is identical to the
@@ -19,3 +18,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-no-common-ancestry.txt b/Documentation/error-no-common-ancestry.txt
index 615da71..aa3b619 100644
--- a/Documentation/error-no-common-ancestry.txt
+++ b/Documentation/error-no-common-ancestry.txt
@@ -1,5 +1,4 @@
-no common ancestry
-==================
+= no common ancestry
 
 With this error message Gerrit rejects to push a commit for code
 review if the pushed commit and the commit at the tip of the target
@@ -18,3 +17,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-no-new-changes.txt b/Documentation/error-no-new-changes.txt
index a421e1a..2103543 100644
--- a/Documentation/error-no-new-changes.txt
+++ b/Documentation/error-no-new-changes.txt
@@ -1,5 +1,4 @@
-no new changes
-==============
+= no new changes
 
 With this error message Gerrit rejects to push a commit if the pushed
 commit was already successfully pushed to Gerrit in project scope.
@@ -50,3 +49,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-non-fast-forward.txt b/Documentation/error-non-fast-forward.txt
index 6604e10..923132e 100644
--- a/Documentation/error-non-fast-forward.txt
+++ b/Documentation/error-non-fast-forward.txt
@@ -1,5 +1,4 @@
-non-fast forward
-================
+= non-fast forward
 
 With this error message Gerrit rejects a push if the remote branch can't
 be fast forwarded onto the pushed commit. This is the case if the
@@ -20,8 +19,7 @@
 . you are pushing the commit to the wrong project
 
 
-the remote branch has evolved since you started your development
-----------------------------------------------------------------
+== the remote branch has evolved since you started your development
 
 You start your development based on the current tip of the remote
 branch. While you implement your feature / bug-fix, a change in Gerrit
@@ -36,8 +34,7 @@
 Afterwards the push should be successful.
 
 
-you are pushing the commit to the wrong project
------------------------------------------------
+== you are pushing the commit to the wrong project
 
 If you do a commit in one project and then accidentally push this
 commit, with bypassing code review, to another project, this will fail
@@ -57,3 +54,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-not-a-gerrit-administrator.txt b/Documentation/error-not-a-gerrit-administrator.txt
index b771af6..5bf9b49 100644
--- a/Documentation/error-not-a-gerrit-administrator.txt
+++ b/Documentation/error-not-a-gerrit-administrator.txt
@@ -1,5 +1,4 @@
-Not a Gerrit administrator
-==========================
+= Not a Gerrit administrator
 
 With this error message Gerrit rejects to execute an SSH command that
 requires administrator privileges if the user is not a Gerrit
@@ -12,3 +11,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-not-allowed-to-upload-merges.txt b/Documentation/error-not-allowed-to-upload-merges.txt
index 515eef5..d025bd0 100644
--- a/Documentation/error-not-allowed-to-upload-merges.txt
+++ b/Documentation/error-not-allowed-to-upload-merges.txt
@@ -1,5 +1,4 @@
-you are not allowed to upload merges
-====================================
+= you are not allowed to upload merges
 
 With this error message Gerrit rejects to push a merge commit if the
 pushing user has no permission to upload merge commits for the
@@ -19,3 +18,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-not-permitted-to-create.txt b/Documentation/error-not-permitted-to-create.txt
index 9c07fd1..eb15a36 100644
--- a/Documentation/error-not-permitted-to-create.txt
+++ b/Documentation/error-not-permitted-to-create.txt
@@ -1,5 +1,4 @@
-Not permitted to create ...
-===========================
+= Not permitted to create ...
 
 With this error message Gerrit rejects to create a new project in
 Gerrit if the user has no privileges for project creation.
@@ -14,3 +13,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-not-signed-off-by.txt b/Documentation/error-not-signed-off-by.txt
index bd1f40d..85247de5 100644
--- a/Documentation/error-not-signed-off-by.txt
+++ b/Documentation/error-not-signed-off-by.txt
@@ -1,5 +1,4 @@
-not Signed-off-by author/committer/uploader in commit message footer
-====================================================================
+= not Signed-off-by author/committer/uploader in commit message footer
 
 Projects in Gerrit can be configured to require a link:user-signedoffby.html#Signed-off-by[Signed-off-by] in
 the footer of the commit message to enforce that every change is signed by the
@@ -29,3 +28,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-not-valid-ref.txt b/Documentation/error-not-valid-ref.txt
index 128e796..1784fc8 100644
--- a/Documentation/error-not-valid-ref.txt
+++ b/Documentation/error-not-valid-ref.txt
@@ -1,5 +1,4 @@
-not valid ref
-=============
+= not valid ref
 
 With this error message Gerrit rejects to push a commit if the target
 ref in the push specification has an incorrect format (for example:
@@ -10,8 +9,7 @@
 or without code review the ref format is different:
 
 
-ref format for pushing a commit for code review:
-------------------------------------------------
+== ref format for pushing a commit for code review:
 
 If it was the intention to push a commit for code review the target
 ref in the push specification must be the project's magical ref
@@ -26,8 +24,7 @@
 ----
 
 
-ref format for directly pushing a commit (without code review):
----------------------------------------------------------------
+== ref format for directly pushing a commit (without code review):
 
 If it was the intention to bypass code review and to push directly to
 a branch the target ref in the push specification must be the name of
@@ -44,3 +41,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-permission-denied.txt b/Documentation/error-permission-denied.txt
index 2ec0a3f..f60f9b2 100644
--- a/Documentation/error-permission-denied.txt
+++ b/Documentation/error-permission-denied.txt
@@ -1,5 +1,4 @@
-Permission denied (publickey)
-=============================
+= Permission denied (publickey)
 
 With this error message an SSH command to Gerrit is rejected if the
 SSH authentication is not successful.
@@ -23,8 +22,7 @@
   key is used.
 
 
-Test SSH authentication
------------------------
+== Test SSH authentication
 
 To test the SSH authentication you can run the following SSH command.
 This command will print out a detailed trace which is helpful to
@@ -60,3 +58,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-prohibited-by-gerrit.txt b/Documentation/error-prohibited-by-gerrit.txt
index 112e296..4c7bf22 100644
--- a/Documentation/error-prohibited-by-gerrit.txt
+++ b/Documentation/error-prohibited-by-gerrit.txt
@@ -1,5 +1,4 @@
-prohibited by Gerrit
-====================
+= prohibited by Gerrit
 
 This is a general error message that is returned by Gerrit if a push
 is not allowed, e.g. because the pushing user has no sufficient
@@ -42,3 +41,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-project-not-found.txt b/Documentation/error-project-not-found.txt
index 3fc0141..5b58a15 100644
--- a/Documentation/error-project-not-found.txt
+++ b/Documentation/error-project-not-found.txt
@@ -1,5 +1,4 @@
-Project not found: ...
-======================
+= Project not found: ...
 
 With this error message Gerrit rejects to push a commit if the git
 repository to which the push is done does not exist as a project in
@@ -32,3 +31,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-push-fails-due-to-commit-message.txt b/Documentation/error-push-fails-due-to-commit-message.txt
index 172d64f..f6e5c1f 100644
--- a/Documentation/error-push-fails-due-to-commit-message.txt
+++ b/Documentation/error-push-fails-due-to-commit-message.txt
@@ -1,5 +1,4 @@
-Push fails due to commit message
-================================
+= Push fails due to commit message
 
 If Gerrit rejects pushing a commit it is often the case that there is
 an issue with the commit message of the pushed commit. In this case
@@ -39,3 +38,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-squash-commits-first.txt b/Documentation/error-squash-commits-first.txt
index 2181c52..b3b9d56 100644
--- a/Documentation/error-squash-commits-first.txt
+++ b/Documentation/error-squash-commits-first.txt
@@ -1,5 +1,4 @@
-squash commits first
-====================
+= squash commits first
 
 With this error message Gerrit rejects to push a commit if it
 contains the same Change-ID as a predecessor commit.
@@ -19,8 +18,7 @@
 into the different commit messages.
 
 
-Example
--------
+== Example
 
 Here an example about how the push is failing. Please note that the
 two commits 'one commit' and 'another commit' both have the same
@@ -106,3 +104,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/error-upload-denied.txt b/Documentation/error-upload-denied.txt
index 5dec8ab..6de94b4 100644
--- a/Documentation/error-upload-denied.txt
+++ b/Documentation/error-upload-denied.txt
@@ -17,3 +17,6 @@
 GERRIT
 ------
 Part of link:error-messages.html[Gerrit Error Messages]
+
+SEARCHBOX
+---------
diff --git a/Documentation/i18n-readme.txt b/Documentation/i18n-readme.txt
index 2135598..180fc53 100644
--- a/Documentation/i18n-readme.txt
+++ b/Documentation/i18n-readme.txt
@@ -1,19 +1,16 @@
-Gerrit Code Review - i18n
-=========================
+= Gerrit Code Review - i18n
 
 Aside from actually writing translations, there are some issues with
 the way the code produces output.  Most of the UI should support
 right-to-left (RTL) languages.
 
-Labels
-------
+== Labels
 
 Labels and their values are defined in project.config by the Gerrit
 administrator or project owners.  Only a single translation of these
 strings is supported.
 
-/Gerrit Gerrit.html
--------------------
+== /Gerrit Gerrit.html
 
 * The title of the host page is not translated.
 
@@ -22,3 +19,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/images/intro-quick-central-gerrit.png b/Documentation/images/intro-quick-central-gerrit.png
index 61b9638..8717176 100644
--- a/Documentation/images/intro-quick-central-gerrit.png
+++ b/Documentation/images/intro-quick-central-gerrit.png
Binary files differ
diff --git a/Documentation/images/intro-quick-central-repo.png b/Documentation/images/intro-quick-central-repo.png
index 84ffeb0..8400b5e 100644
--- a/Documentation/images/intro-quick-central-repo.png
+++ b/Documentation/images/intro-quick-central-repo.png
Binary files differ
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 6fadc1d..33f1e95 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -1,93 +1,94 @@
-Gerrit Code Review for Git
-==========================
+= Gerrit Code Review for Git
 
-Index
------
+== Tutorial
+. Getting started
+.. link:intro-quick.html[A Quick Introduction to Gerrit]
+.. link:intro-change-screen.html[A Quick Introduction to the New Change Screen]
+.. link:intro-project-owner.html[Project Owner Guide]
+.. link:http://source.android.com/submit-patches/workflow[Default Android Workflow] (external)
+. Web
+.. Registering a new Gerrit account
+.. link:user-search.html[Searching Changes]
+.. link:user-notify.html[Subscribing to Email Notifications]
+. SSH
+.. SSH connection details
+.. link:cmd-index.html[Command Line Tools]
+. Git
+.. Git connection details
+.. Commands, scenarios
+... link:user-upload.html[Uploading Changes]
+... link:error-messages.html[Error Messages]
+.. Changes
+... link:user-changeid.html[Change-Id Lines]
+... link:user-signedoffby.html[Signed-off-by Lines]
+.. Patch sets
 
-. General info
-.. link:licenses.html[Licenses and Notices]
-. Installing
-.. link:intro-quick.html[A Quick Introduction To Gerrit]
-.. link:intro-change-screen.html[A Quick Introduction To The New Change Screen]
-.. link:install.html[Installation Guide]
-. Tutorial
-.. Get started
-... External link: link:http://source.android.com/submit-patches/workflow[Default Android Workflow]
-.. Web
-... Registering a new Gerrit account
-... link:user-search.html[Searching Changes]
-... link:user-notify.html[Subscribing to Email Notifications]
-.. Ssh
-... ssh connection details
-... link:cmd-index.html[Command Line Tools]
-.. Git
-... git connection details
-... Commands, scenarios
-.... link:user-upload.html[Uploading Changes]
-.... link:error-messages.html[Error Messages]
-... Changes
-.... link:user-changeid.html[Change-Id Lines]
-.... link:user-signedoffby.html[Signed-off-by Lines]
-... Patch sets
-. Project management
-.. link:project-setup.html[Project Setup]
-.. link:access-control.html[Access Controls]
-... link:config-labels.html[Review Labels]
-... link:config-project-config.html[Access Controls Configuration Format]
-.. Multi-project management
-... Submodules
-... Repo
-.. Prolog rules
-... link:prolog-cookbook.html[Prolog Cookbook]
-... link:prolog-change-facts.html[Prolog Facts for Gerrit Changes]
-.. link:user-submodules.html[Subscribing to Git Submodules]
-.. Project sunset
-. Customization and integration
-.. link:user-dashboards.html[Dashboards]
-.. link:rest-api.html[REST API]
-.. link:config-gitweb.html[Gitweb Integration]
-.. link:config-themes.html[Themes]
-.. link:config-sso.html[Single Sign-On Systems]
-.. link:config-hooks.html[Hooks]
-.. link:config-mail.html[Mail Templates]
-.. link:config-cla.html[Contributor Agreements]
-. Server administration
-.. link:config-gerrit.html[System Settings]
-.. Backup
-.. Performance tuning
-... link:cmd-index.html[Command Line Tools]
-... Reading show-caches efficiently
-... How to read stats from the JVM
-.. High availability
-.. Replication
-.. link:https://gerrit-review.googlesource.com/#/admin/projects/?filter=plugins%252F[Plugins]
-.. link:dev-design.html[System Design]
-.. link:config-contact.html[User Contact Information]
-.. link:config-reverseproxy.html[Reverse Proxy]
-.. link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
-.. link:pgm-index.html[Server Side Administrative Tools]
-. Developer
-.. link:dev-readme.html[Developer Setup]
-.. link:dev-buck.html[Building with Buck]
-.. link:dev-eclipse.html[Eclipse Setup]
-.. link:dev-contributing.html[Contributing to Gerrit]
-.. Documentation formatting guide for contributions
-.. link:dev-design.html[System Design]
-.. link:i18n-readme.html[i18n Support]
-.. Plugin development
-... link:dev-plugins.html[Developing Plugins]
-... link:js-api.html[JavaScript Plugin API]
-... link:config-validation.html[Commit Validation]
-. Maintainer
-.. link:dev-release.html[Developer Release]
-.. link:dev-release-subproject.html[Developer Subproject Release]
+== Project Management
+. link:project-setup.html[Project Setup]
+. link:access-control.html[Access Controls]
+.. link:config-labels.html[Review Labels]
+.. link:config-project-config.html[Access Controls Configuration Format]
+. Multi-project management
+.. Submodules
+.. Repo
+. Prolog rules
+.. link:prolog-cookbook.html[Prolog Cookbook]
+.. link:prolog-change-facts.html[Prolog Facts for Gerrit Changes]
+. link:user-submodules.html[Subscribing to Git Submodules]
+. Project sunset
 
+== Customization and Integration
+. link:user-dashboards.html[Dashboards]
+. link:rest-api.html[REST API]
+. link:config-gitweb.html[Gitweb Integration]
+. link:config-themes.html[Themes]
+. link:config-sso.html[Single Sign-On Systems]
+. link:config-hooks.html[Hooks]
+. link:config-mail.html[Mail Templates]
+. link:config-cla.html[Contributor Agreements]
 
-Resources
----------
+== Server Administration
+. link:install.html[Installation Guide]
+. link:config-gerrit.html[System Settings]
+. Backup
+. Performance tuning
+.. link:cmd-index.html[Command Line Tools]
+.. Reading show-caches efficiently
+.. How to read stats from the JVM
+. High availability
+. Replication
+. link:https://gerrit-review.googlesource.com/#/admin/projects/?filter=plugins%252F[Plugins]
+. link:dev-design.html[System Design]
+. link:config-contact.html[User Contact Information]
+. link:config-reverseproxy.html[Reverse Proxy]
+. link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
+. link:pgm-index.html[Server Side Administrative Tools]
 
+== Developer
+. link:dev-readme.html[Developer Setup]
+. link:dev-buck.html[Building with Buck]
+. link:dev-build-plugins.html[Building Gerrit plugins]
+. link:dev-eclipse.html[Eclipse Setup]
+. link:dev-contributing.html[Contributing to Gerrit]
+. Documentation formatting guide for contributions
+. link:dev-design.html[System Design]
+. link:i18n-readme.html[i18n Support]
+. Plugin development
+.. link:dev-plugins.html[Developing Plugins]
+.. link:js-api.html[JavaScript Plugin API]
+.. link:config-validation.html[Commit Validation]
+
+== Maintainer
+. link:dev-release.html[Developer Release]
+. link:dev-release-subproject.html[Developer Subproject Release]
+
+== Resources
+* link:licenses.html[Licenses and Notices]
 * link:http://code.google.com/p/gerrit/[Homepage]
 * link:http://gerrit-releases.storage.googleapis.com/index.html[Downloads]
 * link:http://code.google.com/p/gerrit/issues/list[Issue Tracking]
 * link:http://code.google.com/p/gerrit/source/checkout[Source Code]
 * link:http://code.google.com/p/gerrit/wiki/Background[A History of Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/install-j2ee.txt b/Documentation/install-j2ee.txt
index 5ba8cb1..50eccc2 100644
--- a/Documentation/install-j2ee.txt
+++ b/Documentation/install-j2ee.txt
@@ -1,8 +1,6 @@
-Gerrit Code Review - J2EE Installation
-======================================
+= Gerrit Code Review - J2EE Installation
 
-Description
------------
+== Description
 
 Gerrit binary distributions include a standalone Jetty servlet
 container, but are packaged as a standard WAR file to permit easy
@@ -14,8 +12,7 @@
 any commercial server which supports the J2EE servlet specification.
 
 
-Installation
-------------
+== Installation
 
 * Complete the link:install.html#createdb[database setup] and
   link:install.html#init[site initialization] tasks described
@@ -50,8 +47,7 @@
 Configure Automatic Site Initialization on Startup]
 
 
-Jetty 7.x
----------
+== Jetty 7.x
 These directions will configure Gerrit as the default web
 application, allowing URLs like `http://example.com/4543` to jump
 directly to change 4543.
@@ -117,3 +113,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/install-quick.txt b/Documentation/install-quick.txt
index f1bd25c..6138a28 100644
--- a/Documentation/install-quick.txt
+++ b/Documentation/install-quick.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Quick get started guide
-============================================
+= Gerrit Code Review - Quick get started guide
 
 ****
 This guide was made with the impatient in mind, ready to try out Gerrit on their
@@ -21,26 +20,24 @@
 
 
 [[requirements]]
-Requirements
-------------
+== Requirements
 
 Most distributions come with Java today. Do you already have Java installed?
 
 ----
   $ java -version
-  java version "1.6.0_26"
-  Java(TM) SE Runtime Environment (build 1.6.0_26-b03-384-10M3425)
-  Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02-384, mixed mode)
+  java version "1.7.0_21"
+  Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
+  Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)
 ----
 
 If Java isn't installed, get it:
 
-* JDK, minimum version 1.6 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download]
+* JDK, minimum version 1.7 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download]
 
 
 [[user]]
-Create a user to host the Gerrit service
-----------------------------------------
+== Create a user to host the Gerrit service
 
 We will run the service as a non-privileged user on your system.
 First create the user and then become the user:
@@ -55,8 +52,7 @@
 
 
 [[download]]
-Download Gerrit
----------------
+== Download Gerrit
 
 It's time to download the archive that contains the Gerrit web and ssh service.
 
@@ -70,8 +66,7 @@
 
 
 [[initialization]]
-Initialize the Site
--------------------
+== Initialize the Site
 
 It's time to run the initialization, and with the batch switch enabled, we don't have to answer any questions at all:
 
@@ -111,16 +106,14 @@
 
 include::config-login-register.txt[]
 
-Project creation
-----------------
+== Project creation
 
 Your base Gerrit server is now running and you have a user that's ready
 to interact with it.  You now have two options, either you create a new
 test project to work with or you already have a git with history that
 you would like to import into Gerrit and try out code review on.
 
-New project from scratch
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== New project from scratch
 If you choose to create a new repository from scratch, it's easier for
 you to create a project with an initial commit in it. That way first
 time setup between client and server is easier.
@@ -134,8 +127,7 @@
 
 This will create a repository that you can clone to work with.
 
-Already existing project
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== Already existing project
 
 The other alternative is if you already have a git project that you
 want to try out Gerrit on.
@@ -166,8 +158,7 @@
 This will create a repository that you can clone to work with.
 
 
-My first change
----------------
+== My first change
 
 Download a local clone of the repository and move into it
 
@@ -216,8 +207,7 @@
 suggested above, http://localhost:8080/1
 
 
-Quick Installation Complete
----------------------------
+== Quick Installation Complete
 
 This covers the scope of getting Gerrit started and your first change uploaded.
 It doesn't give any clue as to how the review workflow works, please read
@@ -232,3 +222,6 @@
 ------
 
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/install.txt b/Documentation/install.txt
index 1d6d1bd..0157079 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -1,21 +1,18 @@
-Gerrit Code Review - Standalone Daemon Installation Guide
-=========================================================
+= Gerrit Code Review - Standalone Daemon Installation Guide
 
 [[requirements]]
-Requirements
-------------
+== Requirements
 To run the Gerrit service, the following requirements must be met on
 the host:
 
-* JDK, minimum version 1.6 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download]
+* JDK, minimum version 1.7 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download]
 
 You'll also need an SQL database to house the review metadata. You have the
 choice of either using the embedded H2 or to host your own MySQL or PostgreSQL.
 
 
 [[download]]
-Download Gerrit
----------------
+== Download Gerrit
 
 Current and past binary releases of Gerrit can be obtained from
 the link:https://gerrit-releases.storage.googleapis.com/index.html[
@@ -31,8 +28,7 @@
 include::database-setup.txt[]
 
 [[init]]
-Initialize the Site
--------------------
+== Initialize the Site
 
 Gerrit stores configuration files, the server's SSH keys, and the
 managed Git repositories under a local directory, typically referred
@@ -90,8 +86,7 @@
 users will be automatically registered as unprivileged users.
 
 
-Installation Complete
----------------------
+== Installation Complete
 
 Your base Gerrit server is now installed and running.  You're now ready to
 either set up more projects or start working with the projects you've already
@@ -99,8 +94,7 @@
 
 
 [[project_setup]]
-Project Setup
--------------
+== Project Setup
 
 See link:project-setup.html[Project Setup] for further details on
 how to register a new project with Gerrit.  This step is necessary
@@ -108,8 +102,7 @@
 
 
 [[rc_d]]
-Start/Stop Daemon
------------------
+== Start/Stop Daemon
 
 To control the Gerrit Code Review daemon that is running in the
 background, use the rc.d style start script created by 'init':
@@ -149,8 +142,7 @@
 
 
 [[customize]]
-Site Customization
-------------------
+== Site Customization
 
 Gerrit Code Review supports some site-specific customization options.
 For more information, see the related topics in this manual:
@@ -164,8 +156,7 @@
 
 
 [[anonymous_access]]
-Anonymous Access
-----------------
+== Anonymous Access
 
 Exporting the Git repository directory
 (link:config-gerrit.html#gerrit.basePath[gerrit.basePath]) over the
@@ -177,14 +168,12 @@
 
 
 [[plugins]]
-Plugins
--------
+== Plugins
 
 Place Gerrit plugins in the review_site/plugins directory to have them loaded on Gerrit startup.
 
 
-External Documentation Links
-----------------------------
+== External Documentation Links
 
 * http://www.postgresql.org/docs/[PostgreSQL Documentation]
 * http://dev.mysql.com/doc/[MySQL Documentation]
@@ -194,3 +183,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/intro-change-screen.txt b/Documentation/intro-change-screen.txt
index 4c949d2..8bf9e27 100644
--- a/Documentation/intro-change-screen.txt
+++ b/Documentation/intro-change-screen.txt
@@ -1,5 +1,4 @@
-Change Screen - Introduction
-============================
+= Change Screen - Introduction
 
 As of Gerrit 2.8 the change screen was redesigned from the ground up. The old
 change screen is deprecated and will be discontinued in one of the next Gerrit
@@ -15,8 +14,8 @@
 * The prettify syntax highlighting library was replaced by Codemirror
 * Automatic refresh of open changes
 * Support to download a patch direct in browser: no local repo is needed
-* JS API integration: it was never so easy to add change/revision actions to
-the UI from a plugin.
+* JS API integration: it was never so easy to add change/revision actions or
+HTML fragments to the UI from a plugin
 
 This document is intended to help users to switch to the new change screen.
 
@@ -25,43 +24,49 @@
 CodeScreen2 thread on the repo-discuss mailing list].
 
 [[configuration]]
-Configuration
--------------
+== Configuration
 
-The new change screen is deactivated by default.  Administrators can activate
-it system-wide by changing the link:config-gerrit.html[gerrit.changeScreen]
-setting to `CHANGE_SCREEN2`.
+The new change screen is activated by default. It can be deactivated
+system-wide by changing the link:config-gerrit.html[gerrit.changeScreen]
+setting to `OLD_UI`.
 
 Users can switch between the old and new screens by selecting "Old Screen" or
 "New Screen" in the "Change View" setting on their user preferences page.
 
-By setting "Default", users can select to use whichever screen the site
-administrator has configured.
-
 [[switching-between-patch-sets]]
-Switching between patch sets
-----------------------------
+== Switching between patch sets
 
 As already mentioned above, the main difference between the old and the new
 change screen is the fact that only one patch set is presented on the screen.
 
-To switch to other patch sets for the given change, the drop down 'Revisions'
-box is used on the right upper side of the change header.
+To switch to other patch sets for the given change, the drop down 'Patch Sets'
+box is used on the right upper side of the change header. The current selected
+patch set and the number of patch sets are shown on the drop down in the form:
+"currently active patch set/number of patch sets". To indicate that not the
+current patch set is active, change header contains: "Not Current" headline
+instead of the change status.
 
 Patch sets are always sorted in descending order. The option to switch between
 ascending and reverse patch set sorting order is not supported on the new change
 screen.
 
-[[download-commands]]
-Download commands
------------------
+Key bindings: "n" & "p" to navigate between the patch sets.
 
-The download commands are moved to the 'Download' drop down box.  Patch files
-can be downloaded as base64 encoded or zipped versions.
+[[download-commands]]
+== Download commands
+
+The commands to checkout, pull, or cherry-pick a patchset moved to the
+'Download' drop down box.  Patch files can be downloaded as base64 encoded or
+zipped versions. Download-plugin must be installed to see the download actions.
+If download plugin is not installed, only patch files hyperlinks are shown.
+
+Change state is cached in browsers, and the change state includes the download
+URLs. If the change is modified (e.g. add a message or change  a label vote)
+the cached state in the browser will be invalidated and the download commands
+will update. Another option to invalidate the cache is to use "Ctrl+Shift+R".
 
 [[included-in]]
-Included in
------------
+== Included in
 
 To see the branches a specific change was merged into and the list of the tags
 a change was tagged with, use the 'Included In' drop down on the change header,
@@ -70,8 +75,7 @@
 Note that this list is only visible on merged changes.
 
 [[quick-approve]]
-Quick approve
--------------
+== Quick approve
 
 The so called 'Quick approve' button is some times confusing. Normal users (i.e.
 non-maintainers) see this as 'Verified+1' button to the right of the 'Reply'
@@ -92,18 +96,21 @@
 'Reply' button should be used.
 
 [[reply-button]]
-Reply button
-------------
+== Reply button
 
 This button corresponds to the 'Review' button the on patch set panel on the old
-change screen.  The only new feature: the user can optionally send an email
-during the vote.
+change screen. The user can optionally send an email during the vote. Inline
+comments are displayed inside of the ReplyBox. Editing them inline in the
+ReplyBox is currently not possible.
+
+Hint: if "LGTM" (acronym for 'Looks Good To Me') is typed in the ReplyBox,
+then the highest possible score for 'Code-Review' category for logged in user
+is selected.
 
 Key bindings: "a" to open the drop down. "ESC" to close it.
 
 [[edit-commit-message]]
-Edit commit message
--------------------
+== Edit commit message
 
 To edit the commit message use the 'Edit Message' button on the change header,
 which will open a drop-down editor box.
@@ -111,6 +118,7 @@
 Key bindings: "e" to open the drop down. "ESC" to close it.
 
 [[star-change]]
+== Star and unstar changes
 
 Star change icon allows to star the change, so that "starredby:self" query can
 retrieve the starred changes later. If the change is already starred, then
@@ -118,71 +126,93 @@
 
 Key bindings: "s" to star/unstar the change.
 
+[[permalink-change]]
+== Permalink a change
+
+Hyperlink "Change <change link>" is a control that serves for two purposes:
+reload the change and permalink the change. To permalink,
+Right mouse click -> Copy Link Address.
+
+Key bindings: "R" to reload the change.
+
 [[edit-change-topic]]
-Edit change topic
------------------
+== Edit change topic
 
 To edit the topic use the edit icon to the right of the topic field.
 
 Key bindings: "t" to open the drop down. "ESC" to close it.
 
 [[abandon-restore]]
-Abandon or Restore changes
---------------------------
+== Abandon or Restore changes
 
 When a change is abandoned or restored, a panel appears and a comment message
 can be provided.
 
 [[working-with-drafts]]
-Working with draft changes and patch sets
------------------------------------------
+== Working with draft changes and patch sets
 
-When a change or a patch set is a draft, then three additional buttons appear on
-the action panel: 'Publish', 'Delete Revision', and 'Delete Change'. In the
-'Revisions' drop down a "(DRAFT)" suffix is added to the patch set number to
-indicate that the patch set is a draft.
+When a change or a patch set is a draft, then three additional buttons
+appear on the action panel: 'Publish', 'Delete Revision', and 'Delete
+Change'. 'Delete Revision' button is only visible when there are at
+least two (draft) revisions. In the 'Patch Set' drop down a "(DRAFT)"
+suffix is added to the patch set number to indicate that the patch set
+is a draft.
 
 [[draft-comments]]
-Highlight draft comments
-------------------------
+== Highlight draft comments
 
 If a patch set has draft comments that weren't published yet, then that patch
-set is marked on the list in the 'Revisions' drop down list. In addition a red
+set is marked on the list in the 'Patch Sets' drop down list. In addition a red
 "draft" prefix appears on the filenames in the file table.
 
 [[codemirror]]
-Codemirror
-----------
+== Codemirror
 
 On the user preferences page, 'Side By Side' or 'Unified Diff' view can be
-configured.  Use the "/" key to start the CodeMirror search, like in vim.
+configured.  Use the "/" key to start the CodeMirror search, like in 'vim'.
 
 Key bindings are not customizable at the moment. They may be added in the future.
 
 Range comments are supported on Codemirror's 'Side By Side' screen.  Highlight
 lines with the mouse and then click the bottom-most line number to create a
-range comment for the highlighted lines.
+range comment for the highlighted lines. It can also be used to select a
+region on one line to emphasize what the comment is related to.
+
+Key bindings:
+
+* j / k: next line / previous line
+* n / p: next diff chunk / previous diff chunk
+* ] / [: next file / previous file
+* u: up to change
+* c: create a new inline comment (focus has to be in codemirror view)
+* / or <Ctrl> + f: search in the current file
+* <Shift> + o: expand or collapse all comments on current line
+* <Enter> or o: expand or collapse comment
+* <Esc> or Cancel: comment edit
+* i: toggle intraline differences
+* ,: Show diff preferences
+* <Ctrl> + s: Save draft comment
 
 [[reviewers]]
-Reviewers
----------
+== Reviewers
 
-Reviewer are split into two groups: Reviewers who actually voted on the change
-in the 'Reviewers' field, and reviewers, who were added to the change but didn't
-vote yet in the 'CC' field.
+Reviewers who actually voted on the change or were added to the change but didn't
+vote yet are shown as "chip" in the 'Reviewers' field.
 
 The votes per category are listed above the File list.
 
-To add a reviewer, use the '[+]' button to the right of the 'CC' field. Typing
-into the pop-up text field activates auto completion of user or group names.
+To add a reviewer, use the 'Add...' button to the right of the 'Reviewers'
+field. Typing  into the pop-up text field activates auto completion of user or
+group names.
 
 To remove reviewers click on the 'x' icon in the reviewer's "chip".
 
+Votable categories are shown as a tooltip for each reviewer.
+
 Key bindings:  "c" to add a reviewer. "ESC" to close the drop down.
 
 [[auto-refresh]]
-Auto refresh of change data
----------------------------
+== Auto refresh of change data
 
 On the new change screen polling for updates to the currently open change is
 activated per default.  For example, if another user votes or comments on the
@@ -193,28 +223,82 @@
 link:config-gerrit.html[change.updateDelay] setting.
 
 [[related-changes]]
-Related changes
----------------
+== "Related changes"
 
-Dependencies and dependent changes are listed in the 'Related Changes' drop
-down.
+A tab control on the third column shows the related changes. There are 4
+different tabs:
 
-Key bindings:  "J" & "K" to navigate between the related changes. "O" to
-open the currently selected related change.
+* Related Changes
+
+This tab page shows changes on which the current change depends (ancestors) and
+open changes that depend on the current change (descendants). For merge commits
+it shows also the closed changes that are going to be merged into the
+destination branch by accepting the merge commit. It is kind of a dependency
+too, but not in the sense of "these changes have to be submitted in order to
+submit this change".
+
+Unlike the old change screen it doesn't explicitly say "needed by" and "depends
+on" any more, but the relationship can still be inferred from the position of
+the change in the list.  Changes listed above the current change are dependents;
+changes below the current change are dependencies.
+
+Indicators are appended on the commit message headlines of related changes to
+signify dependencies on outdated changes, or commits that are not associated to
+changes under review:
+
+If the selected patch set of a change is older than its latest patch set,
+the change is marked with an orange dot.
+
+If a descendant change depends on a patch set that is older than the selected
+patch set of a change, the descendent change is marked with a tilde (~).
+
+If an ancestor commit is not associated to a Gerrit change, i.e. has been pushed
+directly to the repository bypassing review, it is marked with a black dot.
+
+This tab is only visible if related changes for the current active
+change exist.
+
+* Same Topic Changes
+
+Changes that share the same topic are listed on this tab page. Only changes
+in status opened are currently shown.
+
+This tab is only visible if same topic changes for the current active
+change exist.
+
+* Cherry-Picks Changes
+
+Changes with the same change id for the current project are listed on this
+tab page. Note that also abandoned or merged changes can be shown. Currently
+the status of the listed changes is not shown.
+
+This tab is only visible if cherry-picked changes for the current active
+change exist.
+
+* Conflicts With Changes
+
+Changes that are conflicting with the current change are listed on this
+tab. Only the changes that are mergeable are currently shown. Non mergeable
+changes are filtered out.
+
+This tab is only visible if there are conflicting changes with the
+current change.
+
+Key bindings: "J" & "K" to navigate between the related changes. "O" to
+open the currently selected change on one of the related changes tab page.
 
 [[file-table]]
-File table
-----------
+== File table
 
 The user can now manually toggle the 'reviewed' flag per file using the check
 box to the left of the filename.
 
-Key bindings: "j" & "k" to navigate in the file table, and "r" to toggle the
-'reviewed' flag.
+Key bindings: "j" & "k" to navigate in the file table, "r" to toggle the
+'reviewed' flag, and "o" or "Enter" to open diff in codemirror or unified diff
+view.
 
 [[diff-against]]
-Diff against
-------------
+== Diff against
 
 In the 'Diff against' dropdown, base reference version can be selected. On
 selecting an entry the file table list is reloaded and shows only the files
@@ -223,24 +307,16 @@
 the base reference version choice made on the change screen.
 
 [[history]]
-History
--------
+== History
 
 The history table shows change messages and inline file comments. Expand All and
 Collapse All buttons show/hide the messages.
 
 Key bindings: "x" expands all messages, "z" collapses them again.
 
-[[missing-features]]
-Missing features
-----------------
-
-Several features have not been implemented yet:
-
-* Permalink a change
-* Allow to see if a reviewer can't vote on a label
-* Change diff view preferences
-
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/intro-project-owner.txt b/Documentation/intro-project-owner.txt
new file mode 100644
index 0000000..14912bf
--- /dev/null
+++ b/Documentation/intro-project-owner.txt
@@ -0,0 +1,287 @@
+= Project Owner Guide
+
+This is a Gerrit guide that is dedicated to project owners. It
+explains the many possibilities that Gerrit provides to customize the
+workflows for a project.
+
+[[project-owner]]
+== What is a project owner?
+
+Being project owner means that you own a project in Gerrit.
+Technically this is expressed by having the
+link:access-control.html#category_owner[Owner] access right on
+`refs/*` on that project. As project owner you have the permission to
+edit the access control list and the project settings of the project.
+It also means that you should get familiar with these settings so that
+you can adapt them to the needs of your project.
+
+Being project owner means being responsible for the administration of
+a project. This requires having a deeper knowledge of Gerrit than the
+average user. Normally per team there should be 2 to 3 persons, who
+have a certain level of Git/Gerrit knowledge, assigned as project
+owners. It normally doesn't make sense that everyone in a team is
+project owner. For normal team members it is sufficient to be committer
+or contributor.
+
+[[access-rights]]
+== Access Rights
+
+As a project owner you can edit the access control list of your
+project. This allows you to grant permissions on the project to
+different groups.
+
+Gerrit comes with a rich set of permissions which allow a very
+fine-grained control over who can do what on a project. Access
+control is one of the most powerful Gerrit features but it is also a
+rather complex topic. This guide will only highlight the most
+important aspects of access control, but the link:access-control.html[
+Access Control] chapter explains all the details.
+
+[[edit-access-rights]]
+=== Editing Access Rights
+
+To see the access rights of your project
+
+- go to the Gerrit WebUI
+- click on the `Projects` > `List` menu entry
+- find your project in the project list and click on it
+- click on the `Access` menu entry
+
+By clicking on the `Edit` button the access rights become editable and
+you may save any changes by clicking on the `Save Changes` button.
+Optionally you can provide a `Commit Message` to explain the reasons
+for changing the access rights.
+
+The access rights are stored in the project's Git repository in a
+special branch called `refs/meta/config`. On this branch there is a
+`project.config` file which contains the access rights. More
+information about this storage format can be found in the
+link:config-project-config.html[Project Configuration File Format]
+chapter. What is important to know is that by looking at the history
+of the `project.config` file on the `refs/meta/config` branch you can
+always see how the access rights were changed and by whom. If a good
+commit message is provided you can also see from the history why the
+access rights were modified.
+
+If a Git browser such as GitWeb is configured for the Gerrit server you
+can find a link to the history of the `project.config` file in the
+WebUI. Otherwise you may inspect the history locally. If you have
+cloned the repository you can do this by executing the following
+commands:
+
+====
+  $ git fetch origin refs/meta/config:config
+  $ git checkout config
+  $ git log project.config
+====
+
+Non project owners may still edit the access rights and propose the
+modifications to the project owners by clicking on the `Save for
+Review` button. This creates a new change with the access rights
+modifications that can be approved by a project owner. The project
+owners are automatically added as reviewer on this change so that they
+get informed about it by email.
+
+[[inheritance]]
+=== Inheritance
+
+Normally when a new project is created in Gerrit it already has some
+access rights which are inherited from the parent projects.
+Projects in Gerrit are organized hierarchically as a tree with the
+`All-Projects' project as root from which all projects inherit. Each
+project can have only a single parent project, multi-inheritance is
+not supported.
+
+Looking at the access rights of your project in the Gerrit WebUI, you
+only see the access rights which are defined on that project. To see
+the inherited access rights you must follow the link to the parent
+project under `Rights Inherit From`.
+
+Inherited access rights can be overwritten unless they are defined as
+link:access-control.html#block[BLOCK rule]. BLOCK rules are used to
+limit the possibilities of the project owners on the inheriting
+projects. With this, global policies can be enforced on all projects.
+Please note that Gerrit doesn't prevent you from assigning access
+rights that contradict an inherited BLOCK rule, but these access rights
+will simply have no effect.
+
+If you are responsible for several projects which require the same
+permissions, it makes sense to have a common parent for them and to
+maintain the access rights on that common parent. Changing the parent
+of a project is only allowed for Gerrit administrators. This means you
+need to contact the administrator of your Gerrit server if you want to
+reparent your project. One way to do this is to change the parent
+project in the WebUI, save the modifications for review and get the
+change approved and merged by a Gerrit administrator.
+
+[[refs]]
+=== References
+
+Access rights in Gerrit are assigned on references (aka refs). Refs in
+Git exist in different namespaces, e.g. all branches normally exist
+under `refs/heads/` and all tags under `refs/tags/`. In addition there
+are a number of link:access-control.html#references_special[special refs]
+and link:access-control.html#references_magic[magic refs].
+
+Access rights can be assigned on a concrete ref, e.g.
+`refs/heads/master` but also on ref patterns and regular expressions
+for ref names.
+
+A ref pattern ends with `/*` and describes a complete ref name
+namespace, e.g. access rights assigned on `refs/heads/*` apply to all
+branches.
+
+Regular expressions must start with `^`, e.g. access rights assigned
+on `^refs/heads/rel-.*` would apply to all `rel-*` branches.
+
+[[groups]]
+=== Groups
+
+Access rights are granted to groups. It is useful to know that Gerrit
+maintains its own groups internally but also supports different external
+group backends.
+
+The Gerrit internal groups can be seen in the Gerrit WebUI by clicking
+on the `Groups` > `List` menu entry. By clicking on a group you can
+edit the group members (`Members` tab) and the group options
+(`General` tab).
+
+Gerrit internal groups contain users as members, but can also include
+other groups, even external groups.
+
+Every group is owned by an owner group. Only members of the owner
+group can administrate the owned group (assign members, edit the group
+options). A group can own itself; in this case members of the group
+can, for example, add further members to the group. When you create new
+groups for your project to assign access rights to committer or other
+roles, make sure that they are owned by the project owner group.
+
+An important setting on a group is the option
+`Make group visible to all registered users.`, which defines whether
+non-members can see who is member of the group.
+
+New internal Gerrit groups can be created under `Groups` >
+`Create New Group`. This menu is only available if you have the global
+capability link:access-control.html#capability_createGroup[Create Group]
+assigned.
+
+Gerrit also has a set of special
+link:access-control.html#system_groups[system groups] that you might
+find useful.
+
+External groups need to be prefixed when assigning access rights to
+them, e.g. link:access-control.html#ldap_groups[LDAP group names] need
+to be prefixed with `ldap/`.
+
+If the link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/singleusergroup[
+singleusergroup] plugin is installed you can also directly assign
+access rights to users, by prefixing the username with `user/` or the
+user's account ID by `userid/`.
+
+[[common-access-rights]]
+=== Common Access Rights
+
+Different roles in a project, such as developer (committer) or
+contributor, need different access rights. Examples for which access
+rights are typically assigned for which role are described in the
+link:access-control.html#example_roles[Access Control] chapter.
+
+[[code-review]]
+=== Code Review
+
+Gerrit's main functionality is code review, however using code review
+is optional and you may decide to only use Gerrit as a Git server with
+access control. Whether you allow only pushes for review or also
+direct pushes depends on the project's access rights.
+
+To push a commit for review it must be pushed to
+link:access-control.html#refs_for[refs/for/<branch-name>]. This means
+the link:access-control.html#category_push_review[Push] access right
+must be assigned on `refs/for/<branch-name>`.
+
+To allow direct pushes and bypass code review, the
+link:access-control.html#category_push_direct[Push] access right is
+required on `refs/heads/<branch-name>`.
+
+By pushing for review you are not only enabling the review workflow,
+but you can also get automatic verifications from a build server
+before changes are merged. In addition you can benefit from Gerrit's
+merge strategies that can automatically merge/rebase commits on server
+side if necessary. You can control the merge strategy by configuring
+the link:project-setup.html#submit_type[submit type] on the project.
+If you bypass code review you always need to merge/rebase manually if
+the tip of the destination branch has moved. Please keep this in mind
+if you choose to not work with code review because you think it's
+easier to avoid the additional complexity of the review workflow; it
+might actually not be easier.
+
+You may also enable link:user-upload.html#auto_merge[auto-merge on
+push] to benefit from the automatic merge/rebase on server side while
+pushing directly into the repository.
+
+[[project-options]]
+== Project Options
+
+As project owner you can control several options on your project.
+The different options are described in the
+link:project-setup.html#project_options[Project Options] section.
+
+To see the options of your project
+
+- go to the Gerrit WebUI
+- click on the `Projects` > `List` menu entry
+- find your project in the project list and click on it
+- click on the `General` menu entry
+
+[[submit-type]]
+=== Submit Type
+
+An important decision for a project is the choice of the submit type
+and the content merge setting (aka `Automatically resolve conflicts`).
+The link:project-setup.html#submit_type[submit type] is the method
+Gerrit uses to submit a change to the project. The submit type defines
+what Gerrit should do on submit of a change if the destination branch
+has moved while the change was in review. The
+link:project-setup.html#content_merge[content merge] setting applies
+if the same files have been modified concurrently and tells Gerrit
+whether it should attempt a content merge for these files.
+
+When choosing the submit type and the content merge setting one must
+weigh development comfort against the safety of not breaking the
+destination branch.
+
+The most restrictive submit type is
+link:project-setup.html#fast_forward_only[Fast Forward Only]. Using
+this submit type means that after submitting one change all other open
+changes for the same destination branch must be rebased manually. This
+is quite burdensome and in practice only feasible for branches with
+very few changes. On the other hand, if changes are verified before
+submit, e.g. automatically by a CI integration, with this submit type,
+you can be sure that the destination branch never gets broken.
+
+Choosing link:project-setup.html#merge_if_necessary[Merge If Necessary]
+as submit type makes the life for developers more comfortable,
+especially if content merge is enabled. If this submit strategy is used
+developers only need to rebase manually if the same files have been
+modified concurrently or if the content merge on such a file fails. The
+drawback with this submit type is that there is a risk of breaking
+the destination branch, e.g. if one change moves a class into another
+package and another change imports this class from the old location.
+Experience shows that in practice `Merge If Necessary` with content
+merge enabled works pretty well and breaking the destination branch
+happens rarely. This is why this setting is recommended at least for
+development branches. You likely want to start with
+`Merge If Necessary` with content merge enabled and only switch to a
+more restrictive policy if you are facing issues with the build and
+test stability of the destination branches.
+
+Please note that there are other submit types available; they are
+described in the link:project-setup.html#submit_type[Submit Type]
+section.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/intro-quick.txt b/Documentation/intro-quick.txt
index ae2e7e7..d8734b2 100644
--- a/Documentation/intro-quick.txt
+++ b/Documentation/intro-quick.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - A Quick Introduction
-=========================================
+= Gerrit Code Review - A Quick Introduction
 
 Gerrit is a web-based code review tool built on top of the git version
 control system, but if you've got as far as reading this guide then
@@ -7,8 +6,7 @@
 allow you to answer the question, is Gerrit the right tool for me?
 Will it fit in my work flow and in my organization?
 
-What is Gerrit?
----------------
+== What is Gerrit?
 
 I assume that if you're reading this then you're already convinced of
 the benefits of code review in general but want some technical support
@@ -50,8 +48,7 @@
 useful for answering the inevitable "I know we changed this for a
 reason" questions.
 
-Where does Gerrit fit in?
--------------------------
+== Where does Gerrit fit in?
 
 Any team with more than one member has a central source repository of
 some kind (or they should). Git can theoretically work without such a
@@ -83,8 +80,7 @@
 through the review process even for users who are allowed to directly
 push.
 
-The Life and Times of a Change
-------------------------------
+== The Life and Times of a Change
 
 The easiest way to get a feel for how Gerrit works is to follow a
 change through its entire life cycle. For the purpose of this example
@@ -93,8 +89,7 @@
 interface on port +29418+. The project we'll be working on is called
 +RecipeBook+ and we'll be developing a change for the +master+ branch.
 
-Cloning the Repository
-~~~~~~~~~~~~~~~~~~~~~~
+=== Cloning the Repository
 
 Obviously the first thing we need to do is get the source that we're
 going to be modifying. As with any git project you do this by cloning
@@ -118,8 +113,7 @@
 another version of your change. Because of this it's best to just
 install the hook and forget about it.
 
-Creating the Review
-~~~~~~~~~~~~~~~~~~~
+=== Creating the Review
 
 Once you've made your change and committed it locally it's time to
 push it to Gerrit so that it can be reviewed. This is done with a git
@@ -177,8 +171,7 @@
 roles to continue following the change. Now let's pretend we're the
 reviewer.
 
-Reviewing the Change
-~~~~~~~~~~~~~~~~~~~~
+=== Reviewing the Change
 
 The reviewer's life starts at the code review screen shown above. He
 can get here in a number of ways, but for some reason they've decided
@@ -251,8 +244,7 @@
 rework it. So let's switch roles back to the creator where we
 started.
 
-Reworking the Change
-~~~~~~~~~~~~~~~~~~~~
+=== Reworking the Change
 
 As long as we set up the
 link:user-changeid.html[Change-Id commit-msg hook]
@@ -289,8 +281,7 @@
 Rather than repeating ourselves lets assume that this time around the
 patch is given a +2 score by the code reviewer.
 
-Trying out the Change
-~~~~~~~~~~~~~~~~~~~~~
+=== Trying out the Change
 
 With Gerrit's default work-flow there are two sign-offs, code review
 and verify. Verifying means checking that the change actually works.
@@ -348,8 +339,7 @@
 uploaded some fixes so we want the second patch set rather than the
 initial one which the reviewer rejected.
 
-Manually Verifying the Change
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Manually Verifying the Change
 
 For simplicity we're just going to manually verify the change.
 The Verifier may be the same person as the code reviewer or a
@@ -365,8 +355,7 @@
 it's either a pass or fail so all we need for the change to be
 submitted is a +1 score (and no -1's).
 
-Submitting the Change
-~~~~~~~~~~~~~~~~~~~~~
+=== Submitting the Change
 
 You might have noticed that in the verify screen shot there are two
 buttons for submitting the score _Publish Comments_ and _Publish
@@ -390,3 +379,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/js-api.txt b/Documentation/js-api.txt
index 2e97ac4..396edf6 100644
--- a/Documentation/js-api.txt
+++ b/Documentation/js-api.txt
@@ -1,11 +1,9 @@
-Gerrit Code Review - JavaScript API
-===================================
+= Gerrit Code Review - JavaScript API
 
 Gerrit Code Review supports an API for JavaScript plugins to interact
 with the web UI and the server process.
 
-Entry Point
------------
+== Entry Point
 
 JavaScript is loaded using a standard `<script src='...'>` HTML tag.
 Plugins should protect the global namespace by defining their code
@@ -21,21 +19,20 @@
 
 
 [[self]]
-Plugin Instance
----------------
+== Plugin Instance
 
 The plugin instance is passed to the plugin's initialization function
 and provides a number of utility services to plugin authors.
 
 [[self_delete]]
-self.delete()
-~~~~~~~~~~~~~
+=== self.delete() / self.del()
 Issues a DELETE REST API request to the Gerrit server.
 
 .Signature
 [source,javascript]
 ----
 Gerrit.delete(url, callback)
+Gerrit.del(url, callback)
 ----
 
 * url: URL relative to the plugin's URL space. The JavaScript
@@ -46,8 +43,7 @@
   `204 No Content`, which is passed as null.
 
 [[self_get]]
-self.get()
-~~~~~~~~~~
+=== self.get()
 Issues a GET REST API request to the Gerrit server.
 
 .Signature
@@ -65,15 +61,13 @@
   as described in the relevant REST API documentation.
 
 [[self_getPluginName]]
-self.getPluginName()
-~~~~~~~~~~~~~~~~~~~~
+=== self.getPluginName()
 Returns the name this plugin was installed as by the server
 administrator. The plugin name is required to access REST API
 views installed by the plugin, or to access resources.
 
 [[self_post]]
-self.post()
-~~~~~~~~~~~
+=== self.post()
 Issues a POST REST API request to the Gerrit server.
 
 .Signature
@@ -101,8 +95,7 @@
 ----
 
 [[self_put]]
-self.put()
-~~~~~~~~~~
+=== self.put()
 Issues a PUT REST API request to the Gerrit server.
 
 .Signature
@@ -129,16 +122,50 @@
   function (r) {});
 ----
 
+[[self_on]]
+=== self.on()
+Register a JavaScript callback to be invoked when events occur within
+the web interface.
+
+.Signature
+[source,javascript]
+----
+Gerrit.on(event, callback);
+----
+
+* event: A supported event type. See below for description.
+
+* callback: JavaScript function to be invoked when event happens.
+  Arguments may be passed to this function, depending on the event.
+
+Supported events:
+
+* `history`: Invoked when the view is changed to a new screen within
+  the Gerrit web application.  The token after "#" is passed as the
+  argument to the callback function, for example "/c/42/" while
+  showing change 42.
+
+* `showchange`: Invoked when a change is made visible. A
+  link:rest-api-changes.html#change-info[ChangeInfo] and
+  link:rest-api-changes.html#revision-info[RevisionInfo]
+  are passed as arguments.
+
+* `submitchange`: Invoked when the submit button is clicked
+  on a change. A link:rest-api-changes.html#change-info[ChangeInfo]
+  and link:rest-api-changes.html#revision-info[RevisionInfo] are
+  passed as arguments. Similar to a form submit validation, the
+  function must return true to allow the operation to continue, or
+  false to prevent it.
+
 [[self_onAction]]
-self.onAction()
-~~~~~~~~~~~~~~~
+=== self.onAction()
 Register a JavaScript callback to be invoked when the user clicks
 on a button associated with a server side `UiAction`.
 
 .Signature
 [source,javascript]
 ----
-Gerrit.onAction(type, view_name, callback);
+self.onAction(type, view_name, callback);
 ----
 
 * type: `'change'`, `'revision'` or `'project'`, indicating which type
@@ -151,9 +178,29 @@
 * callback: JavaScript function to invoke when the user clicks. The
   function will be passed a link:#ActionContext[action context].
 
+[[self_screen]]
+=== self.screen()
+Register a JavaScript callback to be invoked when the user navigates
+to an extension screen provided by the plugin. Extension screens are
+usually linked from the link:dev-plugins.html#top-menu-extensions[top menu].
+The callback can populate the DOM with the screen's contents.
+
+.Signature
+[source,javascript]
+----
+self.screen(pattern, callback);
+----
+
+* pattern: URL token pattern to identify the screen. Argument can be
+  either a string (`'index'`) or a RegExp object (`/list\/(.*)/`).
+  If a RegExp is used the matching groups will be available inside of
+  the context as `token_match`.
+
+* callback: JavaScript function to invoke when the user navigates to
+  the screen. The function will be passed a link:#ScreenContext[screen context].
+
 [[self_url]]
-self.url()
-~~~~~~~~~~
+=== self.url()
 Returns a URL within the plugin's URL space. If invoked with no
 parameter the URL of the plugin is returned. If passed a string
 the argument is appended to the plugin URL.
@@ -166,23 +213,20 @@
 
 
 [[ActionContext]]
-Action Context
---------------
+== Action Context
 A new action context is passed to the `onAction` callback function
 each time the associated action button is clicked by the user. A
 context is initialized with sufficient state to issue the associated
 REST API RPC.
 
 [[context_action]]
-context.action
-~~~~~~~~~~~~~~
+=== context.action
 An link:rest-api-changes.html#action-info[ActionInfo] object instance
 supplied by the server describing the UI button the user used to
 invoke the action.
 
 [[context_call]]
-context.call()
-~~~~~~~~~~~~~~
+=== context.call()
 Issues the REST API call associated with the action. The HTTP method
 used comes from `context.action.method`, hiding the JavaScript from
 needing to care.
@@ -211,16 +255,14 @@
 ----
 
 [[context_change]]
-context.change
-~~~~~~~~~~~~~~
+=== context.change
 When the action is invoked on a change a
 link:rest-api-changes.html#change-info[ChangeInfo] object instance
 describing the change. Available fields of the ChangeInfo may vary
 based on the options used by the UI when it loaded the change.
 
 [[context_delete]]
-context.delete()
-~~~~~~~~~~~~~~~~
+=== context.delete()
 Issues a DELETE REST API call to the URL associated with the action.
 
 .Signature
@@ -239,8 +281,7 @@
 ----
 
 [[context_get]]
-context.get()
-~~~~~~~~~~~~~
+=== context.get()
 Issues a GET REST API call to the URL associated with the action.
 
 .Signature
@@ -262,19 +303,16 @@
 ----
 
 [[context_go]]
-context.go()
-~~~~~~~~~~~~
-Go to a page. Shorthand for link:#Gerrit_go[`Gerrit.go()`].
+=== context.go()
+Go to a screen. Shorthand for link:#Gerrit_go[`Gerrit.go()`].
 
 [[context_hide]]
-context.hide()
-~~~~~~~~~~~~~~
+=== context.hide()
 Hide the currently visible popup displayed by
 link:#context_popup[`context.popup()`].
 
 [[context_post]]
-context.post()
-~~~~~~~~~~~~~~
+=== context.post()
 Issues a POST REST API call to the URL associated with the action.
 
 .Signature
@@ -300,8 +338,7 @@
 ----
 
 [[context_popup]]
-context.popup()
-~~~~~~~~~~~~~~~
+=== context.popup()
 
 Displays a small popup near the activation button to gather
 additional input from the user before executing the REST API RPC.
@@ -344,8 +381,7 @@
 ----
 
 [[context_put]]
-context.put()
-~~~~~~~~~~~~~
+=== context.put()
 Issues a PUT REST API call to the URL associated with the action.
 
 .Signature
@@ -371,14 +407,12 @@
 ----
 
 [[context_refresh]]
-context.refresh()
-~~~~~~~~~~~~~~~~~
+=== context.refresh()
 Refresh the current display. Shorthand for
 link:#Gerrit_refresh[`Gerrit.refresh()`].
 
 [[context_revision]]
-context.revision
-~~~~~~~~~~~~~~~~
+=== context.revision
 When the action is invoked on a specific revision of a change,
 a link:rest-api-changes.html#revision-info[RevisionInfo]
 object instance describing the revision. Available fields of the
@@ -386,13 +420,11 @@
 loaded the change.
 
 [[context_project]]
-context.project
-~~~~~~~~~~~~~~~
+=== context.project
 When the action is invoked on a specific project,
 the name of the project.
 
-Action Context HTML Helpers
----------------------------
+=== HTML Helpers
 The link:#ActionContext[action context] includes some HTML helper
 functions to make working with DOM based widgets less painful.
 
@@ -425,21 +457,122 @@
   comes with an onkeypress handler installed to play nicely with
   Gerrit's keyboard binding system.
 
+* `select(a,i)`: a new `<select>` element containing one `<option>`
+  element for each entry in the provided array `a`.  The option with
+  the index `i` will be pre-selected in the drop-down-list.
+
+* `selected(s)`: returns the text of the `<option>` element that is
+  currently selected in the provided `<select>` element `s`.
+
 * `span(...)`: a new `<span>` wrapping the (optional) arguments.
 
 * `msg(label)`: a new label.
 
+
+[[ScreenContext]]
+== Screen Context
+A new screen context is passed to the `screen` callback function
+each time the user navigates to a matching URL.
+
+[[screen_body]]
+=== screen.body
+Empty HTML `<div>` node the plugin should add its content to.  The
+node is already attached to the document, but is invisible.  Plugins
+must call `screen.show()` to display the DOM node.  Deferred display
+allows an implementor to partially populate the DOM, make remote HTTP
+requests, finish populating when the callbacks arrive, and only then
+make the view visible to the user.
+
+[[screen_token]]
+=== screen.token
+URL token fragment that activated this screen.  The value is identical
+to `screen.token_match[0]`.  If the URL is `/#/x/hello/list` the token
+will be `"list"`.
+
+[[screen_token_match]]
+=== screen.token_match
+Array of matching subgroups from the pattern specified to `screen()`.
+This is identical to the result of RegExp.exec. Index 0 contains the
+entire matching expression; index 1 the first matching group, etc.
+
+[[screen_onUnload]]
+=== screen.onUnload()
+Configures an optional callback to be invoked just before the screen
+is deleted from the browser DOM.  Plugins can use this callback to
+remove event listeners from DOM nodes, preventing memory leaks.
+
+.Signature
+[source,javascript]
+----
+screen.onUnload(callback)
+----
+
+* callback: JavaScript function to be invoked just before the
+  `screen.body` DOM element is removed from the browser DOM.
+  This event happens when the user navigates to another screen.
+
+[[screen.setTitle]]
+=== screen.setTitle()
+Sets the heading text to be displayed when the screen is visible.
+This is presented in a large bold font below the menus, but above the
+content in `screen.body`.  Setting the title also sets the window
+title to the same string, if it has not already been set.
+
+.Signature
+[source,javascript]
+----
+screen.setPageTitle(titleText)
+----
+
+[[screen.setWindowTitle]]
+=== screen.setWindowTitle()
+Sets the text to be displayed in the browser's title bar when the
+screen is visible.  Plugins should always prefer this method over
+trying to set `window.title` directly.  The window title defaults to
+the title given to `setTitle`.
+
+.Signature
+[source,javascript]
+----
+screen.setWindowTitle(titleText)
+----
+
+[[screen_show]]
+=== screen.show()
+Destroy the currently visible screen and display the plugin's screen.
+This method must be called after adding content to `screen.body`.
+
 [[Gerrit]]
-Gerrit
-------
+== Gerrit
 
 The `Gerrit` object is the only symbol provided into the global
 namespace by Gerrit Code Review. All top-level functions can be
 accessed through this name.
 
+[[Gerrit_css]]
+Gerrit.css()
+~~~~~~~~~~~~
+Creates a new unique CSS class and injects it into the document.
+The name of the class is returned and can be used by the plugin.
+See link:#Gerrit_html[`Gerrit.html()`] for an easy way to use
+generated class names.
+
+Classes created with this function should be created once at install
+time and reused throughout the plugin.  Repeatedly creating the same
+class will explode the global stylesheet.
+
+.Signature
+[source,javascript]
+----
+Gerrit.install(function(self)) {
+  var style = {
+    name: Gerrit.css('background: #fff; color: #000;'),
+  };
+});
+----
+
 [[Gerrit_delete]]
-Gerrit.delete()
-~~~~~~~~~~~~~~~
+=== Gerrit.delete()
 Issues a DELETE REST API request to the Gerrit server. For plugin
 private REST API URLs see link:#self_delete[self.delete()].
 
@@ -464,8 +597,7 @@
 ----
 
 [[Gerrit_get]]
-Gerrit.get()
-~~~~~~~~~~~~
+=== Gerrit.get()
 Issues a GET REST API request to the Gerrit server. For plugin
 private REST API URLs see link:#self_get[self.get()].
 
@@ -493,8 +625,7 @@
 ----
 
 [[Gerrit_getPluginName]]
-Gerrit.getPluginName()
-~~~~~~~~~~~~~~~~~~~~~~
+=== Gerrit.getPluginName()
 Returns the name this plugin was installed as by the server
 administrator. The plugin name is required to access REST API
 views installed by the plugin, or to access resources.
@@ -504,9 +635,8 @@
 encouraged to use `self.getPluginName()` whenever possible.
 
 [[Gerrit_go]]
-Gerrit.go()
-~~~~~~~~~~~
-Updates the web UI to display the view identified by the supplied
+=== Gerrit.go()
+Updates the web UI to display the screen identified by the supplied
 URL token. The URL token is the text after `#` in the browser URL.
 
 [source,javascript]
@@ -518,9 +648,116 @@
 the current browser window will navigate to the non-Gerrit URL.
 The user can return to Gerrit with the back button.
 
+[[Gerrit_html]]
+Gerrit.html()
+~~~~~~~~~~~~~
+Parses an HTML fragment after performing template replacements.  If
+the HTML has a single root element or node that node is returned,
+otherwise it is wrapped inside a `<div>` and the div is returned.
+
+.Signature
+[source,javascript]
+----
+Gerrit.html(htmlText, options, wantElements);
+----
+
+* htmlText: string of HTML to be parsed.  A new unattached `<div>` is
+  created in the browser's document and the innerHTML property is
+  assigned to the passed string, after performing replacements.  If
+  the div has exactly one child, that child will be returned instead
+  of the div.
+
+* options: optional object reference supplying replacements for any
+  `{name}` references in htmlText.  Navigation through objects is
+  supported permitting `{style.bar}` to be replaced with `"foo"` if
+  options was `{style: {bar: "foo"}}`.  Value replacements are HTML
+  escaped before being inserted into the document fragment.
+
+* wantElements: if options is given and wantElements is also true
+  an object consisting of `{root: parsedElement, elements: {...}}` is
+  returned instead of the parsed element. The elements object contains
+  a property for each element using `id={name}` in htmlText.
+
+.Example
+[source,javascript]
+----
+var style = {bar: Gerrit.css('background: yellow')};
+Gerrit.html(
+  '<span class="{style.bar}">Hello {name}!</span>',
+  {style: style, name: "World"});
+----
+
+Event handlers can be automatically attached to elements referenced
+through an attribute id.  Object navigation is not supported for ids,
+and the parser strips the id attribute before returning the result.
+Handler functions must begin with `on` and be a function to be
+installed on the element.  This approach is useful for onclick and
+other handlers that do not want to create circular references that
+will eventually leak browser memory.
+
+.Example
+[source,javascript]
+----
+var options = {
+  link: {
+    onclick: function(e) { window.close() },
+  },
+};
+Gerrit.html('<a href="javascript:;" id="{link}">Close</a>', options);
+----
+
+When using options to install handlers care must be taken to not
+accidentally include the returned element into the event handler's
+closure.  This is why options is built before calling `Gerrit.html()`
+and not inline as a shown above with "Hello World".
+
+DOM nodes can optionally be returned, allowing handlers to access the
+elements identified by `id={name}` at a later point in time.
+
+.Example
+[source,javascript]
+----
+var w = Gerrit.html(
+    '<div>Name: <input type="text" id="{name}"></div>'
+  + '<div>Age: <input type="text" id="{age}"></div>'
+  + '<button id="{submit}"><div>Save</div></button>',
+  {
+    submit: {
+      onclick: function(s) {
+        var e = w.elements;
+        window.alert(e.name.value + " is " + e.age.value);
+      },
+    },
+  }, true);
+----
+
+To prevent memory leaks `w.root` and `w.elements` should be set to
+null when the elements are no longer necessary.  Screens can use
+link:#screen_onUnload[screen.onUnload()] to define a callback function
+to perform this cleanup:
+
+[source,javascript]
+----
+var w = Gerrit.html(...);
+screen.body.appendElement(w.root);
+screen.onUnload(function() { w.clear() });
+----
+
+[[Gerrit_injectCss]]
+Gerrit.injectCss()
+~~~~~~~~~~~~~~~~~~
+Injects CSS rules into the document by appending onto the end of the
+existing rule list.  CSS rules are global to the entire application
+and must be manually scoped by each plugin.  For an automatic scoping
+alternative see link:#Gerrit_css[`css()`].
+
+[source,javascript]
+----
+Gerrit.injectCss('.myplugin_bg {background: #000}');
+----
+
 [[Gerrit_install]]
-Gerrit.install()
-~~~~~~~~~~~~~~~~
+=== Gerrit.install()
 Registers a new plugin by invoking the supplied initialization
 function. The function is passed the link:#self[plugin instance].
 
@@ -532,8 +769,7 @@
 ----
 
 [[Gerrit_post]]
-Gerrit.post()
-~~~~~~~~~~~~~
+=== Gerrit.post()
 Issues a POST REST API request to the Gerrit server. For plugin
 private REST API URLs see link:#self_post[self.post()].
 
@@ -562,8 +798,7 @@
 ----
 
 [[Gerrit_put]]
-Gerrit.put()
-~~~~~~~~~~~~
+=== Gerrit.put()
 Issues a PUT REST API request to the Gerrit server. For plugin
 private REST API URLs see link:#self_put[self.put()].
 
@@ -592,8 +827,7 @@
 ----
 
 [[Gerrit_onAction]]
-Gerrit.onAction()
-~~~~~~~~~~~~~~~~~
+=== Gerrit.onAction()
 Register a JavaScript callback to be invoked when the user clicks
 on a button associated with a server side `UiAction`.
 
@@ -613,14 +847,37 @@
 * callback: JavaScript function to invoke when the user clicks. The
   function will be passed a link:#ActionContext[ActionContext].
 
+[[Gerrit_screen]]
+=== Gerrit.screen()
+Register a JavaScript callback to be invoked when the user navigates
+to an extension screen provided by the plugin. Extension screens are
+usually linked from the link:dev-plugins.html#top-menu-extensions[top menu].
+The callback can populate the DOM with the screen's contents.
+
+.Signature
+[source,javascript]
+----
+Gerrit.screen(pattern, callback);
+----
+
+* pattern: URL token pattern to identify the screen. Argument can be
+  either a string (`'index'`) or a RegExp object (`/list\/(.*)/`).
+  If a RegExp is used the matching groups will be available inside of
+  the context as `token_match`.
+
+* callback: JavaScript function to invoke when the user navigates to
+  the screen. The function will be passed link:#ScreenContext[screen context].
+
 [[Gerrit_refresh]]
-Gerrit.refresh()
-~~~~~~~~~~~~~~~~
+=== Gerrit.refresh()
 Redisplays the current web UI view, refreshing all information.
 
+[[Gerrit_refreshMenuBar]]
+=== Gerrit.refreshMenuBar()
+Refreshes Gerrit's menu bar.
+
 [[Gerrit_url]]
-Gerrit.url()
-~~~~~~~~~~~~
+=== Gerrit.url()
 Returns the URL of the Gerrit Code Review server. If invoked with
 no parameter the URL of the site is returned. If passed a string
 the argument is appended to the site URL.
@@ -633,6 +890,13 @@
 
 For a plugin specific version see link:#self_url()[`self.url()`].
 
+[[Gerrit_showError]]
+=== Gerrit.showError(message)
+Displays the given message in the Gerrit ErrorDialog.
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/json.txt b/Documentation/json.txt
index 2836e5b..680705c6 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - JSON Data
-==============================
+= Gerrit Code Review - JSON Data
 
 Some commands produce JSON data streams intended for other
 applications to consume.  The structures are documented below.
@@ -7,8 +6,7 @@
 of this JSON stream should deal with that appropriately.
 
 [[change]]
-change
-------
+== change
 The Gerrit change being reviewed, or that was already reviewed.
 
 project:: Project path in Gerrit.
@@ -72,9 +70,11 @@
 submitRecords:: The <<submitRecord,submitRecord attribute>> contains
 information about whether this change has been or can be submitted.
 
+allReviewers:: List of all reviewers in <<account,account attribute>>
+which are added to a change.
+
 [[trackingid]]
-trackingid
-----------
+== trackingid
 A link to an issue tracking system.
 
 system:: Name of the system.  This comes straight from the
@@ -83,8 +83,7 @@
 id:: Id number as scraped out of the commit message.
 
 [[account]]
-account
--------
+== account
 A user account.
 
 name:: User's full name, if configured.
@@ -94,8 +93,7 @@
 username:: User's username, if configured.
 
 [[patchSet]]
-patchSet
---------
+== patchSet
 Refers to a specific patchset within a <<change,change>>.
 
 number:: The patchset number.
@@ -128,8 +126,7 @@
 sizeDeletions:: Size information of deletions of this patchset.
 
 [[approval]]
-approval
---------
+== approval
 Records the code review approval granted to a patch set.
 
 type:: Internal name of the approval given.
@@ -157,8 +154,7 @@
 project:: Project path in Gerrit.
 
 [[submitRecord]]
-submitRecord
-------------
+== submitRecord
 Information about the submit status of a change.
 
 status:: Current submit status.
@@ -173,8 +169,7 @@
 <<label,label attribute>>, unless the status is RULE_ERROR.
 
 [[label]]
-label
------
+== label
 Information about a code review label for a change.
 
 label:: The name of the label.
@@ -198,8 +193,7 @@
 by:: The <<account,account>> that applied the label.
 
 [[dependency]]
-dependency
-----------
+== dependency
 Information about a change or patchset dependency.
 
 id:: Change identifier.
@@ -213,8 +207,7 @@
 isCurrentPatchSet:: If the revision is the current patchset of the change.
 
 [[message]]
-message
--------
+== message
 Comment added on a change by a reviewer.
 
 timestamp:: Time in seconds since the UNIX epoch when this comment
@@ -225,8 +218,7 @@
 message:: The comment text.
 
 [[patchsetcomment]]
-patchsetComment
----------------
+== patchsetComment
 Comment added on a patchset by a reviewer.
 
 file:: The name of the file on which the comment was added.
@@ -238,8 +230,7 @@
 message:: The comment text.
 
 [[file]]
-file
-----
+== file
 Information about a patch on a file.
 
 file:: The name of the file.  If the file is renamed, the new name.
@@ -264,8 +255,7 @@
 
 deletions::  number of deletions of this patch.
 
-SEE ALSO
---------
+== SEE ALSO
 
 * link:cmd-stream-events.html[gerrit stream-events]
 * link:cmd-query.html[gerrit query]
@@ -273,3 +263,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-LocalUsernamesToLowerCase.txt b/Documentation/pgm-LocalUsernamesToLowerCase.txt
index 16aecfd..c4a0f05 100644
--- a/Documentation/pgm-LocalUsernamesToLowerCase.txt
+++ b/Documentation/pgm-LocalUsernamesToLowerCase.txt
@@ -1,18 +1,15 @@
-LocalUsernamesToLowerCase
-=========================
+= LocalUsernamesToLowerCase
 
-NAME
-----
+== NAME
 LocalUsernamesToLowerCase - Convert the local username of every
 account to lower case
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'java' -jar gerrit.war 'LocalUsernamesToLowerCase' -d <SITE_PATH>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Converts the local username for every account to lower case. The
 local username is the username that is used to login into the Gerrit
 WebUI.
@@ -33,8 +30,7 @@
 database is MySQL or PostgreSQL. If the database is H2, this task
 must be run by itself.
 
-OPTIONS
--------
+== OPTIONS
 
 -d::
 --site-path::
@@ -45,24 +41,24 @@
 	Number of threads to perform the scan work with.  Defaults to
 	twice the number of CPUs available.
 
-CONTEXT
--------
+== CONTEXT
 This command can only be run on a server which has direct
 connectivity to the metadata database.
 
-EXAMPLES
---------
+== EXAMPLES
 To convert the local username of every account to lower case:
 
 ====
 	$ java -jar gerrit.war LocalUsernamesToLowerCase -d site_path
 ====
 
-See Also
---------
+== SEE ALSO
 
 * Configuration parameter link:config-gerrit.html#ldap.localUsernameToLowerCase[ldap.localUsernameToLowerCase]
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-ScanTrackingIds.txt b/Documentation/pgm-ScanTrackingIds.txt
deleted file mode 100644
index f9494d4..0000000
--- a/Documentation/pgm-ScanTrackingIds.txt
+++ /dev/null
@@ -1,59 +0,0 @@
-ScanTrackingIds
-===============
-
-NAME
-----
-ScanTrackingIds - Rescan changes to index trackingids
-
-SYNOPSIS
---------
-[verse]
-'java' -jar gerrit.war 'ScanTrackingIds' -d <SITE_PATH>
-
-DESCRIPTION
------------
-Scans every known change and updates the indexed tracking
-ids associated with the change, after editing the trackingid
-configuration in gerrit.config.
-
-This task can take quite some time, but can run in the background
-concurrently to the server if the database is MySQL or PostgreSQL.
-If the database is H2, this task must be run by itself.
-
-STATUS
-------
-This command will be replaced by `reindex`.
-
-If secondary indexing is enabled
-(link:config-gerrit.html#index.type[index.type] set to `LUCENE`
-or `SOLR`) use link:pgm-reindex.html[reindex].
-
-OPTIONS
--------
-
--d::
-\--site-path::
-	Location of the gerrit.config file, and all other per-site
-	configuration data, supporting libraries and log files.
-
-\--threads::
-	Number of threads to perform the scan work with.  Defaults to
-	twice the number of CPUs available.
-
-CONTEXT
--------
-This command can only be run on a server which has direct
-connectivity to the metadata database, and local access to the
-managed Git repositories.
-
-EXAMPLES
---------
-To rescan all known trackingids:
-
-====
-	$ java -jar gerrit.war ScanTrackingIds -d site_path --threads 16
-====
-
-GERRIT
-------
-Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/pgm-daemon.txt b/Documentation/pgm-daemon.txt
index 79ef037..3d7dd45 100644
--- a/Documentation/pgm-daemon.txt
+++ b/Documentation/pgm-daemon.txt
@@ -1,13 +1,10 @@
-daemon
-======
+= daemon
 
-NAME
-----
+== NAME
 daemon - Gerrit network server
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'java' -jar gerrit.war 'daemon'
 	-d <SITE_PATH>
 	[--enable-httpd | --disable-httpd]
@@ -16,9 +13,10 @@
 	[--slave]
 	[--headless]
 	[--init]
+	[-s]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Runs the Gerrit network daemon on the local system, configured as
 per the local copy of link:config-gerrit.html[gerrit.config].
 
@@ -29,8 +27,7 @@
 link:config-gerrit.html#gerrit.basePath[gerrit.basePath] may be set
 to different values.
 
-OPTIONS
--------
+== OPTIONS
 
 -d::
 --site-path::
@@ -68,21 +65,26 @@
 	Run init before starting the daemon. This will create a new site or
 	upgrade an existing site.
 
-CONTEXT
--------
+--s::
+	Start link:dev-inspector.html[Gerrit Inspector] on the console, a
+	built-in interactive inspection environment to assist debugging and
+	troubleshooting of Gerrit code.
++
+This options requires 'jython.jar' from the http://www.jython.org[Jython distribution]
+to be present in '$site_path/lib' directory.
+
+== CONTEXT
 This command can only be run on a server which has direct
 connectivity to the metadata database, and local access to the
 managed Git repositories.
 
-LOGGING
--------
+== LOGGING
 Error and warning messages from the server are automatically written
 to the log file under '$site_path/logs/error_log'.  This log file
 is automatically rotated at 12:00 AM GMT each day, allowing an
 external log cleaning service to clean up the prior logs.
 
-KNOWN ISSUES
-------------
+== KNOWN ISSUES
 Slave daemon caches can quickly become out of date when modifications
 are made on the master.  The following configuration is suggested in
 a slave to reduce the maxAge for each cache entry, so that changes
@@ -117,3 +119,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-gsql.txt b/Documentation/pgm-gsql.txt
index a3d68d4..ba40b26 100644
--- a/Documentation/pgm-gsql.txt
+++ b/Documentation/pgm-gsql.txt
@@ -1,17 +1,14 @@
-gsql
-====
+= gsql
 
-NAME
-----
+== NAME
 gsql - Administrative interface to idle database
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'java' -jar gerrit.war 'gsql' -d <SITE_PATH>
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Interactive query support against the configured SQL database.
 All SQL statements are supported, including SELECT, UPDATE, INSERT,
 DELETE and ALTER.
@@ -20,22 +17,19 @@
 which is not currently open by a Gerrit daemon.  To access an open
 database use link:cmd-gsql.html[gerrit gsql] over SSH.
 
-OPTIONS
--------
+== OPTIONS
 
 -d::
 --site-path::
 	Location of the gerrit.config file, and all other per-site
 	configuration data, supporting libraries and log files.
 
-CONTEXT
--------
+== CONTEXT
 This command can only be run on a server which has direct
 connectivity to the metadata database, and local access to the
 managed Git repositories.
 
-EXAMPLES
---------
+== EXAMPLES
 To manually correct a user's SSH user name:
 
 ====
@@ -54,3 +48,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-index.txt b/Documentation/pgm-index.txt
index 987b4ac..3bb6182 100644
--- a/Documentation/pgm-index.txt
+++ b/Documentation/pgm-index.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Server Side Administrative Tools
-=====================================================
+= Gerrit Code Review - Server Side Administrative Tools
 
 Server side tools can be started by executing the WAR file
 through the Java command line.  For example:
@@ -8,8 +7,7 @@
 
 Tool should be one of the following names:
 
-Tools
------
+== Tools
 
 link:pgm-init.html[init]::
 	Initialize a new Gerrit server installation.
@@ -32,11 +30,7 @@
 version::
 	Display the release version of Gerrit Code Review.
 
-Transition Utilities
-~~~~~~~~~~~~~~~~~~~~
-
-link:pgm-ScanTrackingIds.html[ScanTrackingIds]::
-	Rescan all changes after configuring trackingids.
+=== Transition Utilities
 
 link:pgm-LocalUsernamesToLowerCase.html[LocalUsernamesToLowerCase]::
 	Convert the local username of every account to lower case.
@@ -44,3 +38,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-init.txt b/Documentation/pgm-init.txt
index 86974f2..39cd70d 100644
--- a/Documentation/pgm-init.txt
+++ b/Documentation/pgm-init.txt
@@ -1,22 +1,19 @@
-init
-====
+= init
 
-NAME
-----
+== NAME
 init - Initialize a new Gerrit server installation
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'java' -jar gerrit.war 'init'
 	-d <SITE_PATH>
 	[--batch]
 	[--no-auto-start]
 	[--list-plugins]
 	[--install-plugin=<PLUGIN_NAME>]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Creates a new Gerrit server installation, interactively prompting
 for some basic setup prior to writing default configuration files
 into a newly created `$site_path`.
@@ -24,8 +21,7 @@
 If run in an existing `$site_path`, init will upgrade some resources
 as necessary.
 
-OPTIONS
--------
+== OPTIONS
 --batch::
 	Run in batch mode, skipping interactive prompts.  Reasonable
 	configuration defaults are chosen based on the whims of
@@ -52,9 +48,10 @@
 
 --install-plugin::
 	Automatically install plugin with given name without asking.
+	This option may be supplied more than once to install multiple
+	plugins.
 
-CONTEXT
--------
+== CONTEXT
 This command can only be run on a server which has direct
 connectivity to the metadata database, and local access to the
 managed Git repositories.
@@ -62,3 +59,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-prolog-shell.txt b/Documentation/pgm-prolog-shell.txt
index 3189e90..9861310 100644
--- a/Documentation/pgm-prolog-shell.txt
+++ b/Documentation/pgm-prolog-shell.txt
@@ -1,30 +1,25 @@
-prolog-shell
-============
+= prolog-shell
 
-NAME
-----
+== NAME
 prolog-shell - Simple interactive Prolog interpreter
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'java' -jar gerrit.war 'prolog-shell' [-s FILE.pl ...]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Provides a simple interactive Prolog interpreter for development
 and testing.
 
-OPTIONS
--------
+== OPTIONS
 -s::
 	Dynamically load the Prolog source code at startup,
 	as though the user had entered `['FILE.pl'].` into
 	the interpreter once it was running. This option may
 	be supplied more than once to load multiple files.
 
-EXAMPLES
---------
+== EXAMPLES
 Define a simple predicate and test it:
 
 ====
@@ -55,3 +50,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-reindex.txt b/Documentation/pgm-reindex.txt
index 2b44f6b..b1116d3 100644
--- a/Documentation/pgm-reindex.txt
+++ b/Documentation/pgm-reindex.txt
@@ -1,24 +1,29 @@
-reindex
-=======
+= reindex
 
-NAME
-----
+== NAME
 reindex - Rebuild the secondary index
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'java' -jar gerrit.war 'reindex' [<OPTIONS>]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Rebuilds the secondary index.
 
-OPTIONS
--------
+== OPTIONS
 --threads::
 	Number of threads to use for indexing.
 
+--recheck-mergeable::
+	Recheck the mergeable flag on all open changes. For each open change,
+	look up for which commit the mergeability check was last done and if
+	this commit is different from the HEAD commit of the change's destination
+	branch, recompute the mergeability flag of the change by checking if the
+	commit of the current patch set can be merged into the destination branch.
+	Because this operation is computationally expensive, it is not enabled
+	by default.
+
 --schema-version::
 	Schema version to reindex; default is most recent version.
 
@@ -31,11 +36,13 @@
 --dry-run::
 	Dry run.  Don't write anything to index.
 
-CONTEXT
--------
+== CONTEXT
 The secondary index must be enabled. See
 link:config-gerrit.html#index.type[index.type].
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/pgm-rulec.txt b/Documentation/pgm-rulec.txt
index 6d0a632..3236c38 100644
--- a/Documentation/pgm-rulec.txt
+++ b/Documentation/pgm-rulec.txt
@@ -1,23 +1,19 @@
-rulec
-=====
+= rulec
 
-NAME
-----
+== NAME
 rulec - Compile project-specific Prolog rules to JARs
 
-SYNOPSIS
---------
-[verse]
+== SYNOPSIS
+--
 'java' -jar gerrit.war 'rulec' -d <SITE_PATH> [--all | <PROJECT>...]
+--
 
-DESCRIPTION
------------
+== DESCRIPTION
 Looks for a Prolog rule file named `rules.pl` on the repository's
 `refs/meta/config` branch. If rules.pl exists, creates a JAR file
 named `rules-'SHA1'.jar` in `'$site_path'/cache/rules`.
 
-OPTIONS
--------
+== OPTIONS
 -d::
 --site-path::
 	Location of the gerrit.config file, and all other per-site
@@ -32,8 +28,7 @@
 <PROJECT>:
 	Compile rules for the specified project.
 
-CONTEXT
--------
+== CONTEXT
 This command can only be run on a server which has direct
 connectivity to the metadata database, and local access to the
 managed Git repositories.
@@ -41,8 +36,7 @@
 Caching needs to be enabled. See
 link:config-gerrit.html#cache.directory[cache.directory].
 
-EXAMPLES
---------
+== EXAMPLES
 To compile a rule JAR file for test/project:
 
 ====
@@ -52,3 +46,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/project-setup.txt b/Documentation/project-setup.txt
index 36d3c60..8b99711 100644
--- a/Documentation/project-setup.txt
+++ b/Documentation/project-setup.txt
@@ -1,62 +1,60 @@
-Gerrit Code Review - Project Configuration
-==========================================
+= Gerrit Code Review - Project Configuration
 
-Create Through SSH
-------------------
+== Project Creation
 
-Creating a new repository over SSH is perhaps the easiest way to
-configure a new project:
+There are several ways to create a new project in Gerrit:
 
-====
-  ssh -p 29418 review.example.com gerrit create-project --name new/project
-====
+- in the Web UI under 'Projects' > 'Create Project'
+- via the link:rest-api-projects.html#create-project[Create Project]
+  REST endpoint
+- via the link:cmd-create-project.html[create-project] SSH command
 
-See link:cmd-create-project.html[gerrit create-project] for more
-details.
+To be able to create new projects the global capability
+link:access-control.html#capability_createProject[Create Project] must
+be granted.
 
+In addition, projects can be created link:#manual_project_creation[
+manually].
 
-Manual Creation
----------------
+[[manual_project_creation]]
+=== Manual Project Creation
 
-Projects may also be manually created.
-
-Create Git Repository
-~~~~~~~~~~~~~~~~~~~~~
-
-Create a Git repository under gerrit.basePath:
-
+. Create a Git repository under `gerrit.basePath`:
++
 ====
   git --git-dir=$base_path/new/project.git init
 ====
-
++
 [TIP]
 By tradition the repository directory name should have a `.git`
 suffix.
-
++
 To also make this repository available over the anonymous git://
 protocol, don't forget to create a `git-daemon-export-ok` file:
-
++
 ====
   touch $base_path/new/project.git/git-daemon-export-ok
 ====
 
-Register Project
-~~~~~~~~~~~~~~~~
-
+. Register Project
++
 Either restart the server, or flush the `project_list` cache:
-
++
 ====
   ssh -p 29418 localhost gerrit flush-caches --cache project_list
 ====
 
+[[project_options]]
+== Project Options
+
 [[submit_type]]
-Change Submit Action
---------------------
+=== Submit Type
 
 The method Gerrit uses to submit a change to a project can be
 modified by any project owner through the project console, `Projects` >
 `List` > my/project.  The following methods are supported:
 
+[[fast_forward_only]]
 * Fast Forward Only
 +
 This method produces a strictly linear history.  All merges must
@@ -66,6 +64,7 @@
 destination branch.  That is, the change must already contain the
 tip of the destination branch at submit time.
 
+[[merge_if_necessary]]
 * Merge If Necessary
 +
 This is the default for a new project.
@@ -75,6 +74,7 @@
 then a merge commit is automatically created.  This is identical
 to the classical `git merge` behavior, or `git merge --ff`.
 
+[[always_merge]]
 * Always Merge
 +
 Always produce a merge commit, even if the change is a strict
@@ -82,6 +82,7 @@
 behavior of `git merge --no-ff`, and may be useful if the
 project needs to follow submits with `git log --first-parent`.
 
+[[cherry_pick]]
 * Cherry Pick
 +
 Always cherry pick the patch set, ignoring the parent lineage
@@ -111,12 +112,90 @@
 succeed if there is no path conflict.  A path conflict occurs when
 the same file has also been changed on the other side of the merge.
 
+[[content_merge]]
 If `Automatically resolve conflicts` is enabled, Gerrit will try
 to do a content merge when a path conflict occurs.
 
+=== State
 
-Registering Additional Branches
--------------------------------
+This setting defines the state of the project. A project can have the
+following states:
+
+- `Active`:
++
+The project is active and users can see and modify the project according
+to their access rights on the project.
+
+- `Read Only`:
++
+The project is read only and all modifying operations on it are
+disabled. E.g. this means that pushing to this project fails for all
+users even if they have push permissions assigned on it.
++
+Setting a project to this state is an easy way to temporary close a
+project, as you can keep all write access rights in place and they will
+become active again as soon as the project state is set back to
+`Active`.
++
+This state also makes sense if a project was moved to another location.
+In this case all new development should happen in the new project and
+you want to prevent that somebody accidentally works on the old
+project, while keeping the old project around for old references.
+
+- `Hidden`:
++
+The project is hidden and only visible to project owners. Other users
+are not able to see the project even if they have read permissions
+granted on the project.
+
+=== Require Change-Id
+
+The `Require Change-Id in commit message` option defines whether a
+link:user-changeid.html[Change-Id] in the commit message is required
+for pushing a commit for review. If this option is set, trying to push
+a commit for review that doesn't contain a Change-Id in the commit
+message fails with link:error-missing-changeid.html[missing Change-Id
+in commit message footer].
+
+It is recommended to set this option and use a
+link:user-changeid.html#create[commit-msg hook] (or other client side
+tooling like EGit) to automatically generate Change-Id's for new
+commits. This way the Change-Id is automatically in place when changes
+are reworked or rebased and uploading new patch sets gets easy.
+
+If this option is not set, commits can be uploaded without a Change-Id,
+but then users have to remember to copy the assigned Change-Id from the
+change screen and insert it manually into the commit message when they
+want to upload a second patch set.
+
+=== Maximum Git Object Size Limit
+
+This option defines the maximum allowed Git object size that
+receive-pack will accept. If an object is larger than the given size
+the pack-parsing will abort and the push operation will fail.
+
+With this option users can be prevented from uploading commits that
+contain files which are too large.
+
+Normally the link:config-gerrit.html#receive.maxObjectSizeLimit[maximum
+Git object size limit] is configured globally for a Gerrit server. At
+the project level, the maximum Git object size limit can be further
+reduced, but not extended. The displayed effective limit shows the
+maximum Git object size limit that is actually used on the project.
+
+The defined maximum Git object size limit is inherited by any child
+project.
+
+=== Require Signed-off-by
+
+The `Require Signed-off-by in commit message` option defines whether a
+link:user-signedoffby.html[Signed-off-by] line in the commit message is
+required for pushing a commit. If this option is set, trying to push a
+commit that doesn't contain a Signed-off-by line in the commit message
+fails with link:error-not-signed-off-by.html[not Signed-off-by
+author/committer/uploader in commit message footer].
+
+== Registering Additional Branches
 
 Branches can be created over the SSH port by any `git push` client,
 if the user has been granted the `Create Reference` access right.
@@ -134,3 +213,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/prolog-change-facts.txt b/Documentation/prolog-change-facts.txt
index e21da02..cd97223 100644
--- a/Documentation/prolog-change-facts.txt
+++ b/Documentation/prolog-change-facts.txt
@@ -1,5 +1,4 @@
-Prolog Facts for Gerrit Changes
-===============================
+= Prolog Facts for Gerrit Changes
 
 Prior to invoking the `submit_rule(X)` query for a change, Gerrit initializes
 the Prolog engine with a set of facts (current data) about this change.
@@ -101,3 +100,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index b1710a2..91967b2 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -1,8 +1,6 @@
-Gerrit Code Review - Prolog Submit Rules Cookbook
-=================================================
+= Gerrit Code Review - Prolog Submit Rules Cookbook
 
-Submit Rule
------------
+== Submit Rule
 A 'Submit Rule' in Gerrit is logic that defines when a change is submittable.
 By default, a change is submittable when it gets at least one
 highest vote in each voting category and has no lowest vote (aka veto vote) in
@@ -28,8 +26,7 @@
 link:http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/ReleaseNotes-2.2.2.html[Gerrit
 2.2.2 ReleaseNotes] introduces Prolog support in Gerrit.
 
-Submit Type
------------
+== Submit Type
 A 'Submit Type' is a strategy that is used on submit to integrate the
 change into the destination branch. Supported submit types are:
 
@@ -49,22 +46,19 @@
 Prolog based submit type computes a submit type for each change. The computed
 submit type is shown on the change screen for each change.
 
-Prolog Language
----------------
+== Prolog Language
 This document is not a complete Prolog tutorial.
 link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog] is a
 good starting point for learning the Prolog language. This document will only explain
 some elements of Prolog that are necessary to understand the provided examples.
 
-Prolog in Gerrit
-----------------
+== Prolog in Gerrit
 Gerrit uses its own link:https://code.google.com/p/prolog-cafe/[fork] of the
 original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe]
 project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs at
 runtime.
 
-Interactive Prolog Cafe Shell
------------------------------
+== Interactive Prolog Cafe Shell
 For interactive testing and playing with Prolog, Gerrit provides the
 link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive
 Prolog interpreter shell.
@@ -72,14 +66,12 @@
 NOTE: The interactive shell is just a prolog shell, it does not load
 a gerrit server environment and thus is not intended for xref:TestingSubmitRules[testing submit rules].
 
-SWI-Prolog
-----------
+== SWI-Prolog
 Instead of using the link:pgm-prolog-shell.html[prolog-shell] program one can
 also use the link:http://www.swi-prolog.org/[SWI-Prolog] environment. It
 provides a better shell interface and a graphical source-level debugger.
 
-The rules.pl file
------------------
+== The rules.pl file
 This section explains how to create and edit project specific submit rules. How
 to actually write the submit rules is explained in the next section.
 
@@ -98,8 +90,7 @@
 ====
 
 [[HowToWriteSubmitRules]]
-How to write submit rules
--------------------------
+== How to write submit rules
 Whenever Gerrit needs to evaluate submit rules for a change `C` from project `P` it
 will first initialize the embedded Prolog interpreter by:
 
@@ -201,8 +192,7 @@
 inspecting the facts about the change.
 
 [[SubmitFilter]]
-Submit Filter
--------------
+== Submit Filter
 Another mechanism of changing the default submit rules is to implement the
 `submit_filter/2` predicate. While Gerrit will search for the `submit_rule` only
 in the `rules.pl` file of the current project, the `submit_filter` will be
@@ -270,8 +260,7 @@
 its result will be filtered as described above.
 
 [[HowToWriteSubmitType]]
-How to write submit type
-------------------------
+== How to write submit type
 Writing custom submit type logic in Prolog is the similar top
 xref:HowToWriteSubmitRules[writing submit rules]. The only difference is that
 one has to implement a `submit_type` predicate (instead of the `submit_rule`)
@@ -284,8 +273,7 @@
 * `cherry_pick`
 * `rebase_if_necessary`
 
-Submit Type Filter
-------------------
+== Submit Type Filter
 Submit type filter works the same way as the xref:SubmitFilter[Submit Filter]
 where the name of the filter predicate is `submit_type_filter`.
 
@@ -298,8 +286,7 @@
 the result.
 
 [[TestingSubmitRules]]
-Testing submit rules
---------------------
+== Testing submit rules
 The prolog environment running the `submit_rule` is loaded with state describing the
 change that is being evaluated. The easiest way to load this state is to test your
 `submit_rule` against a real change on a running gerrit instance. The command
@@ -310,8 +297,7 @@
   cat rules.pl | ssh gerrit_srv gerrit test-submit rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
 ====
 
-Prolog vs Gerrit plugin for project specific submit rules
----------------------------------------------------------
+== Prolog vs Gerrit plugin for project specific submit rules
 Since version 2.5 Gerrit supports plugins and extension points. A plugin or an
 extension point could also be used as another means to provide custom submit
 rules. One could ask for a guideline when to use Prolog based submit rules and
@@ -328,8 +314,7 @@
 From version 2.6 Gerrit plugins can contribute Prolog predicates. This way, we
 can make use of the plugin provided predicates when writing Prolog based rules.
 
-Examples - Submit Rule
-----------------------
+== Examples - Submit Rule
 The following examples should serve as a cookbook for developing own submit rules.
 Some of them are too trivial to be used in production and their only purpose is
 to provide step by step introduction and understanding.
@@ -340,8 +325,7 @@
 invoked from all parent projects. This is the most important fact in deciding
 whether to implement `submit_rule` or `submit_filter`.
 
-Example 1: Make every change submittable
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 1: Make every change submittable
 Let's start with a most trivial example where we would make every change submittable
 regardless of the votes it has:
 
@@ -358,8 +342,7 @@
 with these names are not part of the return result. The `'Any-Label-Name'` could really
 be any string.
 
-Example 2: Every change submittable and voting in the standard categories possible
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 2: Every change submittable and voting in the standard categories possible
 This is continuation of the previous example where, in addition, to making
 every change submittable we want to enable voting in the standard
 `'Code-Review'` and `'Verified'` categories.
@@ -376,8 +359,7 @@
 Voting in the standard labels will be shown in the UI as the standard label names are
 included in the return result.
 
-Example 3: Nothing is submittable
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 3: Nothing is submittable
 This example shows how to make all changes non-submittable regardless of the
 votes they have.
 
@@ -392,8 +374,7 @@
 will be submittable. The UI will, however, not indicate what is needed for a
 change to become submittable as we return no labels with status `need`.
 
-Example 4: Nothing is submittable but UI shows several 'Need ...' criteria
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 4: Nothing is submittable but UI shows several 'Need ...' criteria
 In this example no change is submittable but here we show how to present 'Need
 <label>' information to the user in the UI.
 
@@ -429,8 +410,7 @@
   all solutions for a query, the result will be union of all solutions.
   Therefore, we see all 4 `need` labels in the UI.
 
-Example 5: The 'Need ...' labels not shown when change is submittable
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 5: The 'Need ...' labels not shown when change is submittable
 This example shows that, when there is a solution for `submit_rule(X)` where all labels
 have status `ok` then Gerrit will not show any labels with the `need` status from
 any of the previous `submit_rule(X)` solutions.
@@ -462,8 +442,7 @@
 
 The result of the first rule will stop search for any further solutions.
 
-Example 6: Make change submittable if commit author is "John Doe"
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 6: Make change submittable if commit author is "John Doe"
 This is the first example where we will use the Prolog facts about a change that
 are automatically exposed by Gerrit. Our goal is to make any change submittable
 when the commit author is named `'John Doe'`. In the very first
@@ -543,8 +522,7 @@
     Author = label('Author-is-John-Doe', ok(_)).
 ====
 
-Example 7: Make change submittable if commit message starts with "Fix "
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 7: Make change submittable if commit message starts with "Fix "
 Besides showing how to make use of the commit message text the purpose of this
 example is also to show how to match only a part of a string symbol. Similarly
 like commit author the commit message is provided as a string symbol which is
@@ -610,8 +588,7 @@
     Fix = label('Commit-Message-starts-with-Fix', need(_)).
 ====
 
-The default submit policy
--------------------------
+== The default submit policy
 All examples until now concentrate on one particular aspect of change data.
 However, in real-life scenarios we would rather want to reuse Gerrit's default
 submit policy and extend/change it for our specific purpose.  This could be
@@ -622,8 +599,7 @@
 * invoke the default submit rule implementation and then perform further
   actions on its return result.
 
-Default submit rule implementation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Default submit rule implementation
 The default submit rule with the two default categories, `Code-Review` and
 `Verified`, can be implemented as:
 
@@ -643,8 +619,7 @@
 this `submit_rule`.  On the other side, this example is easy to read and
 understand.
 
-Reusing the default submit policy
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Reusing the default submit policy
 To get results of Gerrit's default submit policy we use the
 `gerrit:default_submit` predicate.  The `gerrit:default_submit(X)` includes all
 categories from the database.  This means that if we write a submit rule like:
@@ -669,14 +644,12 @@
 
 In the following examples both styles will be shown.
 
-Example 8: Make change submittable only if `Code-Review+2` is given by a non author
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 8: Make change submittable only if `Code-Review+2` is given by a non author
 In this example we introduce a new label `Non-Author-Code-Review` and make it
 satisfied if there is at least one `Code-Review+2` from a non author. All other
 default policies like the `Verified` category and vetoing changes still apply.
 
-Reusing the `gerrit:default_submit`
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Reusing the `gerrit:default_submit`
 First, we invoke `gerrit:default_submit` to compute the result for the default
 submit policy and then add the `Non-Author-Code-Review` label to it.  The
 `Non-Author-Code-Review` label is added with status `ok` if such an approval
@@ -716,8 +689,7 @@
 if the `cut` in the first rule is not reached and it only happens if a
 predicate before the `cut` fails.
 
-Don't use `gerrit:default_submit`
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Don't use `gerrit:default_submit`
 Let's implement the same submit rule the other way, without reusing the
 `gerrit:default_submit`:
 
@@ -787,8 +759,7 @@
   remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R).
 ====
 
-Example 10: Combine examples 8 and 9
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 10: Combine examples 8 and 9
 In this example we want to both remove the verified and have the four eyes
 principle.  This means we want a combination of examples 7 and 8.
 
@@ -827,8 +798,7 @@
     gerrit:max_with_block(-2, 2, 'Code-Review', CR),
 ====
 
-Example 11: Remove the `Verified` category from all projects
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 11: Remove the `Verified` category from all projects
 Example 9, implements `submit_rule` that removes the `Verified` category from
 one project. In this example we do the same but we want to remove the `Verified`
 category from all projects. This means we have to implement `submit_filter` and
@@ -848,8 +818,7 @@
   remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R).
 ====
 
-Example 12: On release branches require DrNo in addition to project rules
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 12: On release branches require DrNo in addition to project rules
 A new category 'DrNo' is added to the database and is required for release
 branches. To mark a branch as a release branch we use `drno('refs/heads/branch')`.
 
@@ -873,8 +842,7 @@
   submit_filter(In, Out) :- In = Out.
 ====
 
-Example 13: 1+1=2 Code-Review
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 13: 1+1=2 Code-Review
 In this example we introduce accumulative voting to determine if a change is
 submittable or not. We modify the standard Code-Review to be accumulative, and make the
 change submittable if the total score is 2 or higher.
@@ -936,8 +904,7 @@
   sum_list([], S, S).
 ====
 
-Example 14: Master and apprentice
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 14: Master and apprentice
 The master and apprentice example allow you to specify a user (the `master`)
 that must approve all changes done by another user (the `apprentice`).
 
@@ -973,8 +940,7 @@
   add_apprentice_master(S, S).
 ====
 
-Example 15: Only allow Author to submit change
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 15: Only allow Author to submit change
 This example adds a new needed category `Patchset-Author` for any user that is
 not the author of the patch. This effectively blocks all users except the author
 from submitting the change. This could result in an impossible situation if the
@@ -997,12 +963,10 @@
   only_allow_author_to_submit(S1, [label('Patchset-Author', need(_)) | S1]).
 ====
 
-Examples - Submit Type
-----------------------
+== Examples - Submit Type
 The following examples show how to implement own submit type rules.
 
-Example 1: Set a `Cherry Pick` submit type for all changes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 1: Set a `Cherry Pick` submit type for all changes
 This example sets the `Cherry Pick` submit type for all changes. It overrides
 whatever is set as project default submit type.
 
@@ -1013,8 +977,7 @@
 ====
 
 
-Example 2: `Fast Forward Only` for all `refs/heads/stable*` branches
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 2: `Fast Forward Only` for all `refs/heads/stable*` branches
 For all `refs/heads/stable.*` branches we would like to enforce the `Fast
 Forward Only` submit type. A reason for this decision may be a need to never
 break the build in the stable branches.  For all other branches, those not
@@ -1034,8 +997,7 @@
 for `refs/heads/stable.*` branches. The second `submit_type` predicate returns
 the project's default submit type.
 
-Example 3: Don't require `Fast Forward Only` if only documentation was changed
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Example 3: Don't require `Fast Forward Only` if only documentation was changed
 Like in the previous example we want the `Fast Forward Only` submit type for
 the `refs/heads/stable*` branches.  However, if only documentation was changed
 (only `*.txt` files), then we allow project's default submit type for such
@@ -1062,3 +1024,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/replace_macros.py b/Documentation/replace_macros.py
index 1680a47..7623382 100755
--- a/Documentation/replace_macros.py
+++ b/Documentation/replace_macros.py
@@ -22,6 +22,7 @@
 PAT_GET = re.compile(r'^get::([^ \t\n]*)')
 PAT_TITLE = re.compile(r'^\.(.*)')
 PAT_STARS = re.compile(r'^\*\*\*\*')
+PAT_SEARCHBOX = re.compile(r'^SEARCHBOX')
 
 GERRIT_UPLINK = """
 
@@ -56,6 +57,29 @@
 
 """
 
+SEARCH_BOX = """
+
+++++
+<div style="position:absolute; right:20px; top:20px;">
+<input type="text" id="docSearch" size="70" />
+<button type="button" id="searchBox">Search</button>
+<script type="text/javascript">
+var f = function() {
+  window.location = '../#/Documentation/' +
+    encodeURIComponent(document.getElementById("docSearch").value);
+}
+document.getElementById("searchBox").onclick = f;
+document.getElementById("docSearch").onkeypress = function(e) {
+  if (13 == (e.keyCode ? e.keyCode : e.which)) {
+    f();
+  }
+}
+</script>
+</div>
+++++
+
+"""
+
 opts = OptionParser()
 opts.add_option('-o', '--out', help='output file')
 opts.add_option('-s', '--src', help='source file')
@@ -73,6 +97,10 @@
       # Case of "GERRIT\n------" at the footer
       out_file.write(GERRIT_UPLINK)
       last_line = ''
+    elif PAT_SEARCHBOX.match(last_line):
+      # Case of 'SEARCHBOX\n---------'
+      out_file.write(SEARCH_BOX)
+      last_line = ''
     elif PAT_INCLUDE.match(line):
       # Case of 'include::<filename>'
       match = PAT_INCLUDE.match(line)
diff --git a/Documentation/rest-api-access.txt b/Documentation/rest-api-access.txt
index 6c786e4..3ff3595 100644
--- a/Documentation/rest-api-access.txt
+++ b/Documentation/rest-api-access.txt
@@ -1,19 +1,17 @@
-Gerrit Code Review - /access/ REST API
-======================================
+= Gerrit Code Review - /access/ REST API
 
 This page describes the access rights related REST endpoints.
 Please also take note of the general information on the
 link:rest-api.html[REST API].
 
 [[access-endpoints]]
-Access Rights Endpoints
------------------------
+== Access Rights Endpoints
 
 [[list-access]]
-List Access Rights
-~~~~~~~~~~~~~~~~~~
-[verse]
+=== List Access Rights
+--
 'GET /access/?project=link:rest-api-projects.html#project-name[\{project-name\}]'
+--
 
 Lists the access rights for projects. The projects for which the access
 rights should be returned must be specified as `project` options. The
@@ -288,8 +286,7 @@
 ----
 
 [[access-section-info]]
-AccessSectionInfo
-~~~~~~~~~~~~~~~~~
+=== AccessSectionInfo
 The `AccessSectionInfo` describes the access rights that are assigned
 on a ref.
 
@@ -303,8 +300,7 @@
 |==================================
 
 [[permission-info]]
-PermissionInfo
-~~~~~~~~~~~~~~
+=== PermissionInfo
 The `PermissionInfo` entity contains information about an assigned
 permission.
 
@@ -322,8 +318,7 @@
 |==================================
 
 [[permission-rule-info]]
-PermissionRuleInfo
-~~~~~~~~~~~~~~~~~~
+=== PermissionRuleInfo
 The `PermissionRuleInfo` entity contains information about a permission
 rule that is assigned to group.
 
@@ -345,8 +340,7 @@
 |==================================
 
 [[project-access-info]]
-ProjectAccessInfo
-~~~~~~~~~~~~~~~~~
+=== ProjectAccessInfo
 The `ProjectAccessInfo` entity contains information about the access
 rights for a project.
 
@@ -378,3 +372,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 1ac7d51..ac5bd54 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1,19 +1,17 @@
-Gerrit Code Review - /accounts/ REST API
-========================================
+= Gerrit Code Review - /accounts/ REST API
 
 This page describes the account related REST endpoints.
 Please also take note of the general information on the
 link:rest-api.html[REST API].
 
 [[account-endpoints]]
-Account Endpoints
------------------
+== Account Endpoints
 
 [[get-account]]
-Get Account
-~~~~~~~~~~~
-[verse]
+=== Get Account
+--
 'GET /accounts/link:#account-id[\{account-id\}]'
+--
 
 Returns an account as an link:#account-info[AccountInfo] entity.
 
@@ -38,10 +36,10 @@
 ----
 
 [[create-account]]
-Create Account
-~~~~~~~~~~~~~~
-[verse]
+=== Create Account
+--
 'PUT /accounts/link:#username[\{username\}]'
+--
 
 Creates a new account.
 
@@ -82,10 +80,10 @@
 ----
 
 [[get-account-name]]
-Get Account Name
-~~~~~~~~~~~~~~~~
-[verse]
+=== Get Account Name
+--
 'GET /accounts/link:#account-id[\{account-id\}]/name'
+--
 
 Retrieves the full name of an account.
 
@@ -107,10 +105,10 @@
 If the account does not have a name an empty string is returned.
 
 [[set-account-name]]
-Set Account Name
-~~~~~~~~~~~~~~~~
-[verse]
+=== Set Account Name
+--
 'PUT /accounts/link:#account-id[\{account-id\}]/name'
+--
 
 Sets the full name of an account.
 
@@ -145,10 +143,10 @@
 request is rejected with "`405 Method Not Allowed`".
 
 [[delete-account-name]]
-Delete Account Name
-~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Account Name
+--
 'DELETE /accounts/link:#account-id[\{account-id\}]/name'
+--
 
 Deletes the name of an account.
 
@@ -163,10 +161,10 @@
 ----
 
 [[get-username]]
-Get Username
-~~~~~~~~~~~~
-[verse]
+=== Get Username
+--
 'GET /accounts/link:#account-id[\{account-id\}]/username'
+--
 
 Retrieves the username of an account.
 
@@ -188,10 +186,10 @@
 If the account does not have a username the response is `404 Not Found`.
 
 [[get-active]]
-Get Active
-~~~~~~~~~~
-[verse]
+=== Get Active
+--
 'GET /accounts/link:#account-id[\{account-id\}]/active'
+--
 
 Checks if an account is active.
 
@@ -200,19 +198,22 @@
   GET /accounts/john.doe@example.com/active HTTP/1.0
 ----
 
-As response `200 OK` is returned for an active account and
-`404 Not Found` is returned for an inactive account.
+If the account is active the string `ok` is returned.
 
 .Response
 ----
   HTTP/1.1 200 OK
+
+  ok
 ----
 
+If the account is inactive the response is `204 No Content`.
+
 [[set-active]]
-Set Active
-~~~~~~~~~~
-[verse]
+=== Set Active
+--
 'PUT /accounts/link:#account-id[\{account-id\}]/active'
+--
 
 Sets the account state to active.
 
@@ -229,10 +230,10 @@
 If the account was already active the response is `200 OK`.
 
 [[delete-active]]
-Delete Active
-~~~~~~~~~~~~~
-[verse]
+=== Delete Active
+--
 'DELETE /accounts/link:#account-id[\{account-id\}]/active'
+--
 
 Sets the account state to inactive.
 
@@ -249,10 +250,10 @@
 If the account was already inactive the response is `404 Not Found`.
 
 [[get-http-password]]
-Get HTTP Password
-~~~~~~~~~~~~~~~~~
-[verse]
+=== Get HTTP Password
+--
 'GET /accounts/link:#account-id[\{account-id\}]/password.http'
+--
 
 Retrieves the HTTP password of an account.
 
@@ -274,10 +275,10 @@
 If the account does not have an HTTP password the response is `404 Not Found`.
 
 [[set-http-password]]
-Set/Generate HTTP Password
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Set/Generate HTTP Password
+--
 'PUT /accounts/link:#account-id[\{account-id\}]/password.http'
+--
 
 Sets/Generates the HTTP password of an account.
 
@@ -310,10 +311,10 @@
 If the HTTP password was deleted the response is "`204 No Content`".
 
 [[delete-http-password]]
-Delete HTTP Password
-~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete HTTP Password
+--
 'DELETE /accounts/link:#account-id[\{account-id\}]/password.http'
+--
 
 Deletes the HTTP password of an account.
 
@@ -328,10 +329,10 @@
 ----
 
 [[list-account-emails]]
-List Account Emails
-~~~~~~~~~~~~~~~~~~~
-[verse]
+=== List Account Emails
+--
 'GET /accounts/link:#account-id[\{account-id\}]/emails'
+--
 
 Returns the email addresses that are configured for the specified user.
 
@@ -362,10 +363,10 @@
 ----
 
 [[get-account-email]]
-Get Account Email
-~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Account Email
+--
 'GET /accounts/link:#account-id[\{account-id\}]/emails/link:#email-id[\{email-id\}]'
+--
 
 Retrieves an email address of a user.
 
@@ -391,10 +392,10 @@
 ----
 
 [[create-account-email]]
-Create Account Email
-~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Create Account Email
+--
 'PUT /accounts/link:#account-id[\{account-id\}]/emails/link:#email-id[\{email-id\}]'
+--
 
 Registers a new email address for the user. A verification email is
 sent with a link that needs to be visited to confirm the email address,
@@ -429,10 +430,10 @@
 ----
 
 [[delete-account-email]]
-Delete Account Email
-~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Account Email
+--
 'DELETE /accounts/link:#account-id[\{account-id\}]/emails/link:#email-id[\{email-id\}]'
+--
 
 Deletes an email address of an account.
 
@@ -447,10 +448,10 @@
 ----
 
 [[set-preferred-email]]
-Set Preferred Email
-~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Set Preferred Email
+--
 'PUT /accounts/link:#account-id[\{account-id\}]/emails/link:#email-id[\{email-id\}]/preferred'
+--
 
 Sets an email address as preferred email address for an account.
 
@@ -468,10 +469,10 @@
 account the response is "`200 OK`".
 
 [[list-ssh-keys]]
-List SSH Keys
-~~~~~~~~~~~~~
-[verse]
+=== List SSH Keys
+--
 'GET /accounts/link:#account-id[\{account-id\}]/sshkeys'
+--
 
 Returns the SSH keys of an account.
 
@@ -503,10 +504,10 @@
 ----
 
 [[get-ssh-key]]
-Get SSH Key
-~~~~~~~~~~~
-[verse]
+=== Get SSH Key
+--
 'GET /accounts/link:#account-id[\{account-id\}]/sshkeys/link:#ssh-key-id[\{ssh-key-id\}]'
+--
 
 Retrieves an SSH key of a user.
 
@@ -536,10 +537,10 @@
 ----
 
 [[add-ssh-key]]
-Add SSH Key
-~~~~~~~~~~~
-[verse]
+=== Add SSH Key
+--
 'POST /accounts/link:#account-id[\{account-id\}]/sshkeys'
+--
 
 Adds an SSH key for a user.
 
@@ -574,10 +575,10 @@
 ----
 
 [[delete-ssh-key]]
-Delete SSH Key
-~~~~~~~~~~~~~~
-[verse]
+=== Delete SSH Key
+--
 'DELETE /accounts/link:#account-id[\{account-id\}]/sshkeys/link:#ssh-key-id[\{ssh-key-id\}]'
+--
 
 Deletes an SSH key of a user.
 
@@ -592,10 +593,10 @@
 ----
 
 [[list-account-capabilities]]
-List Account Capabilities
-~~~~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== List Account Capabilities
+--
 'GET /accounts/link:#account-id[\{account-id\}]/capabilities'
+--
 
 Returns the global capabilities that are enabled for the specified
 user.
@@ -697,10 +698,10 @@
 ****
 
 [[check-account-capability]]
-Check Account Capability
-~~~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Check Account Capability
+--
 'GET /accounts/link:#account-id[\{account-id\}]/capabilities/link:#capability-id[\{capability-id\}]'
+--
 
 Checks if a user has a certain global capability.
 
@@ -727,10 +728,10 @@
 ****
 
 [[list-groups]]
-List Groups
-~~~~~~~~~~~
-[verse]
+=== List Groups
+--
 'GET /accounts/link:#account-id[\{account-id\}]/groups/'
+--
 
 Lists all groups that contain the specified user as a member.
 
@@ -789,10 +790,10 @@
 ****
 
 [[get-avatar]]
-Get Avatar
-~~~~~~~~~~
-[verse]
+=== Get Avatar
+--
 'GET /accounts/link:#account-id[\{account-id\}]/avatar'
+--
 
 Retrieves the avatar image of the user.
 
@@ -813,10 +814,10 @@
 ----
 
 [[get-avatar-change-url]]
-Get Avatar Change URL
-~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Avatar Change URL
+--
 'GET /accounts/link:#account-id[\{account-id\}]/avatar.change.url'
+--
 
 Retrieves the URL where the user can change the avatar image.
 
@@ -835,10 +836,10 @@
 ----
 
 [[get-diff-preferences]]
-Get Diff Preferences
-~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Diff Preferences
+--
 'GET /accounts/link:#account-id[\{account-id\}]/preferences.diff'
+--
 
 Retrieves the diff preferences of a user.
 
@@ -870,10 +871,10 @@
 ----
 
 [[set-diff-preferences]]
-Set Diff Preferences
-~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Set Diff Preferences
+--
 'PUT /accounts/link:#account-id[\{account-id\}]/preferences.diff'
+--
 
 Sets the diff preferences of a user.
 
@@ -922,10 +923,10 @@
 ----
 
 [[get-starred-changes]]
-Get Starred Changes
-~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Starred Changes
+--
 'GET /accounts/link:#account-id[\{account-id\}]/starred.changes'
+--
 
 Gets the changes starred by the identified user account. This
 URL endpoint is functionally identical to the changes query
@@ -966,10 +967,10 @@
 ----
 
 [[star-change]]
-Star Change
-~~~~~~~~~~~
-[verse]
+=== Star Change
+--
 'PUT /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes.html#change-id[\{change-id\}]'
+--
 
 Star a change. Starred changes are returned for the search query
 `is:starred` or `starredby:USER` and automatically notify the user
@@ -986,10 +987,10 @@
 ----
 
 [[unstar-change]]
-Unstar Change
-~~~~~~~~~~~~~
-[verse]
+=== Unstar Change
+--
 'DELETE /accounts/link:#account-id[\{account-id\}]/starred.changes/link:rest-api-changes#change-id[\{change-id\}]'
+--
 
 Unstar a change. Removes the starred flag, stopping notifications.
 
@@ -1004,12 +1005,10 @@
 ----
 
 [[ids]]
-IDs
----
+== IDs
 
 [[account-id]]
-\{account-id\}
-~~~~~~~~~~~~~~
+=== \{account-id\}
 Identifier that uniquely identifies one account.
 
 This can be:
@@ -1022,35 +1021,29 @@
 * `self` for the calling user
 
 [[capability-id]]
-\{capability-id\}
-~~~~~~~~~~~~~~~~~
+=== \{capability-id\}
 Identifier of a global capability. Valid values are all field names of
 the link:#capability-info[CapabilityInfo] entity.
 
 [[email-id]]
-\{email-id\}
-~~~~~~~~~~~~
+=== \{email-id\}
 An email address, or `preferred` for the preferred email address of the
 user.
 
 [[username]]
-\{username\}
-~~~~~~~~~~~~
+=== \{username\}
 The user name.
 
 [[ssh-key-id]]
-\{ssh-key-id\}
-~~~~~~~~~~~~~~
+=== \{ssh-key-id\}
 The sequence number of the SSH key.
 
 
 [[json-entities]]
-JSON Entities
--------------
+== JSON Entities
 
 [[account-info]]
-AccountInfo
-~~~~~~~~~~~
+=== AccountInfo
 The `AccountInfo` entity contains information about an account.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -1070,8 +1063,7 @@
 |===========================
 
 [[account-input]]
-AccountInput
-~~~~~~~~~~~~
+=== AccountInput
 The `AccountInput` entity contains information for the creation of
 a new account.
 
@@ -1090,8 +1082,7 @@
 |============================
 
 [[account-name-input]]
-AccountNameInput
-~~~~~~~~~~~~~~~~
+=== AccountNameInput
 The `AccountNameInput` entity contains information for setting a name
 for an account.
 
@@ -1103,19 +1094,19 @@
 |=============================
 
 [[capability-info]]
-CapabilityInfo
-~~~~~~~~~~~~~~
+=== CapabilityInfo
 The `CapabilityInfo` entity contains information about the global
 capabilities of a user.
 
 [options="header",width="50%",cols="1,^1,5"]
 |=================================
 |Field Name          ||Description
+|`accessDatabase`    |not set if `false`|Whether the user has the
+link:access-control.html#capability_accessDatabase[Access Database]
+capability.
 |`administrateServer`|not set if `false`|Whether the user has the
 link:access-control.html#capability_administrateServer[Administrate
 Server] capability.
-|`queryLimit`||The link:access-control.html#capability_queryLimit[Query
-Limit] of the user as link:#query-limit-info[QueryLimitInfo].
 |`createAccount`     |not set if `false`|Whether the user has the
 link:access-control.html#capability_createAccount[Create Account]
 capability.
@@ -1128,26 +1119,38 @@
 |`emailReviewers`    |not set if `false`|Whether the user has the
 link:access-control.html#capability_emailReviewers[Email Reviewers]
 capability.
-|`killTask`          |not set if `false`|Whether the user has the
-link:access-control.html#capability_kill[Kill Task] capability.
-|`viewCaches`        |not set if `false`|Whether the user has the
-link:access-control.html#capability_viewCaches[View Caches] capability.
 |`flushCaches`       |not set if `false`|Whether the user has the
 link:access-control.html#capability_flushCaches[Flush Caches]
 capability.
+|`killTask`          |not set if `false`|Whether the user has the
+link:access-control.html#capability_kill[Kill Task] capability.
+|`priority`          |not set if `INTERACTIVE`|The name of the thread
+pool used by the user, see link:access-control.html#capability_priority[
+Priority] capability.
+|`queryLimit`        ||The link:access-control.html#capability_queryLimit[
+Query Limit] of the user as link:#query-limit-info[QueryLimitInfo].
+|`runAs`             |not set if `false`|Whether the user has the
+link:access-control.html#capability_runAs[Run As] capability.
+|`runGC`             |not set if `false`|Whether the user has the
+link:access-control.html#capability_runGC[Run Garbage Collection]
+capability.
+|`streamEvents`      |not set if `false`|Whether the user has the
+link:access-control.html#capability_streamEvents[Stream Events]
+capability.
+|`viewAllAccounts`   |not set if `false`|Whether the user has the
+link:access-control.html#capability_viewAllAccounts[View All Accounts]
+capability.
+|`viewCaches`        |not set if `false`|Whether the user has the
+link:access-control.html#capability_viewCaches[View Caches] capability.
 |`viewConnections`   |not set if `false`|Whether the user has the
 link:access-control.html#capability_viewConnections[View Connections]
 capability.
 |`viewQueue`         |not set if `false`|Whether the user has the
 link:access-control.html#capability_viewQueue[View Queue] capability.
-|`runGC`  |not set if `false`|Whether the user has the
-link:access-control.html#capability_runGC[Run Garbage Collection]
-capability.
 |=================================
 
 [[diff-preferences-info]]
-DiffPreferencesInfo
-~~~~~~~~~~~~~~~~~~~
+=== DiffPreferencesInfo
 The `DiffPreferencesInfo` entity contains information about the diff
 preferences of a user.
 
@@ -1187,13 +1190,16 @@
 Whether uncommented files should be skipped on file switch.
 |`syntax_highlighting`   |not set if `false`|
 Whether syntax highlighting should be enabled.
+|`hide_top_menu`         |not set if `false`|
+If true the top menu header and site header is hidden.
+|`hide_line_numbers`     |not set if `false`|
+If true the line numbers are hidden.
 |`tab_size`              ||
 Number of spaces that should be used to display one tab.
 |=====================================
 
 [[diff-preferences-input]]
-DiffPreferencesInput
-~~~~~~~~~~~~~~~~~~~~
+=== DiffPreferencesInput
 The `DiffPreferencesInput` entity contains information for setting the
 diff preferences of a user. Fields which are not set will not be
 updated.
@@ -1234,13 +1240,16 @@
 Whether uncommented files should be skipped on file switch.
 |`syntax_highlighting`   |optional|
 Whether syntax highlighting should be enabled.
+|`hide_top_menu`         |optional|
+True if the top menu header and site header should be hidden.
+|`hide_line_numbers`     |optional|
+True if the line numbers should be hidden.
 |`tab_size`              |optional|
 Number of spaces that should be used to display one tab.
 |=====================================
 
 [[email-info]]
-EmailInfo
-~~~~~~~~~
+=== EmailInfo
 The `EmailInfo` entity contains information about an email address of a
 user.
 
@@ -1257,8 +1266,7 @@
 |========================
 
 [[email-input]]
-EmailInput
-~~~~~~~~~~
+=== EmailInput
 The `EmailInput` entity contains information for registering a new
 email address.
 
@@ -1280,8 +1288,7 @@
 |==============================
 
 [[http-password-input]]
-HttpPasswordInput
-~~~~~~~~~~~~~~~~~
+=== HttpPasswordInput
 The `HttpPasswordInput` entity contains information for setting/generating
 an HTTP password.
 
@@ -1298,8 +1305,7 @@
 |============================
 
 [[query-limit-info]]
-QueryLimitInfo
-~~~~~~~~~~~~~~
+=== QueryLimitInfo
 The `QueryLimitInfo` entity contains information about the
 link:access-control.html#capability_queryLimit[Query Limit] of a user.
 
@@ -1311,8 +1317,7 @@
 |================================
 
 [[ssh-key-info]]
-SshKeyInfo
-~~~~~~~~~~
+=== SshKeyInfo
 The `SshKeyInfo` entity contains information about an SSH key of a
 user.
 
@@ -1331,3 +1336,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 5ada8d5..c1bb66d 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1,19 +1,17 @@
-Gerrit Code Review - /changes/ REST API
-=======================================
+= Gerrit Code Review - /changes/ REST API
 
 This page describes the change related REST endpoints.
 Please also take note of the general information on the
 link:rest-api.html[REST API].
 
 [[change-endpoints]]
-Change Endpoints
-----------------
+== Change Endpoints
 
 [[list-changes]]
-Query Changes
-~~~~~~~~~~~~~
-[verse]
+=== Query Changes
+--
 'GET /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
@@ -49,6 +47,8 @@
       "created": "2012-07-17 07:18:30.854000000",
       "updated": "2012-07-17 07:19:27.766000000",
       "mergeable": true,
+      "insertions": 26,
+      "deletions": 10,
       "_sortkey": "001e7057000006dc",
       "_number": 1756,
       "owner": {
@@ -66,6 +66,8 @@
       "created": "2012-07-17 07:18:30.884000000",
       "updated": "2012-07-17 07:18:30.885000000",
       "mergeable": true,
+      "insertions": 12,
+      "deletions": 18,
       "_sortkey": "001e7056000006dd",
       "_number": 1757,
       "owner": {
@@ -120,6 +122,8 @@
         "created": "2012-07-17 07:18:30.854000000",
         "updated": "2012-07-17 07:19:27.766000000",
         "mergeable": true,
+        "insertions": 4,
+        "deletions": 7,
         "_sortkey": "001e7057000006dc",
         "_number": 1756,
         "owner": {
@@ -262,6 +266,8 @@
       "created": "2012-04-25 00:52:25.580000000",
       "updated": "2012-04-25 00:52:25.586000000",
       "mergeable": true,
+      "insertions": 16,
+      "deletions": 7,
       "_sortkey": "001c9bf400000061",
       "_number": 97,
       "owner": {
@@ -359,10 +365,10 @@
 ----
 
 [[get-change]]
-Get Change
-~~~~~~~~~~
-[verse]
+=== Get Change
+--
 'GET /changes/link:#change-id[\{change-id\}]'
+--
 
 Retrieves a change.
 
@@ -397,6 +403,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": true,
+    "insertions": 34,
+    "deletions": 101,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
     "owner": {
@@ -406,10 +414,10 @@
 ----
 
 [[get-change-detail]]
-Get Change Detail
-~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Change Detail
+--
 'GET /changes/link:#change-id[\{change-id\}]/detail'
+--
 
 Retrieves a change with link:#labels[labels], link:#detailed-labels[
 detailed labels], link:#detailed-accounts[detailed accounts], and
@@ -446,6 +454,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": true,
+    "insertions": 126,
+    "deletions": 11,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
     "owner": {
@@ -508,8 +518,8 @@
           }
         ]
         "values": {
-          "-2": "Do not submit",
-          "-1": "I would prefer that you didn\u0027t submit this",
+          "-2": "This shall not be merged",
+          "-1": "I would prefer this is not merged as is",
           " 0": "No score",
           "+1": "Looks good to me, but someone else must approve",
           "+2": "Looks good to me, approved"
@@ -574,10 +584,10 @@
 ----
 
 [[get-topic]]
-Get Topic
-~~~~~~~~~
-[verse]
+=== Get Topic
+--
 'GET /changes/link:#change-id[\{change-id\}]/topic'
+--
 
 Retrieves the topic of a change.
 
@@ -599,10 +609,10 @@
 If the change does not have a topic an empty string is returned.
 
 [[set-topic]]
-Set Topic
-~~~~~~~~~
-[verse]
+=== Set Topic
+--
 'PUT /changes/link:#change-id[\{change-id\}]/topic'
+--
 
 Sets the topic of a change.
 
@@ -634,10 +644,10 @@
 If the topic was deleted the response is "`204 No Content`".
 
 [[delete-topic]]
-Delete Topic
-~~~~~~~~~~~~
-[verse]
+=== Delete Topic
+--
 'DELETE /changes/link:#change-id[\{change-id\}]/topic'
+--
 
 Deletes the topic of a change.
 
@@ -659,10 +669,10 @@
 ----
 
 [[abandon-change]]
-Abandon Change
-~~~~~~~~~~~~~~
-[verse]
+=== Abandon Change
+--
 'POST /changes/link:#change-id[\{change-id\}]/abandon'
+--
 
 Abandons a change.
 
@@ -695,6 +705,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": true,
+    "insertions": 3,
+    "deletions": 310,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
     "owner": {
@@ -717,10 +729,10 @@
 ----
 
 [[restore-change]]
-Restore Change
-~~~~~~~~~~~~~~
-[verse]
+=== Restore Change
+--
 'POST /changes/link:#change-id[\{change-id\}]/restore'
+--
 
 Restores a change.
 
@@ -753,6 +765,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": true,
+    "insertions": 2,
+    "deletions": 13,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
     "owner": {
@@ -775,10 +789,10 @@
 ----
 
 [[rebase-change]]
-Rebase Change
-~~~~~~~~~~~~~
-[verse]
+=== Rebase Change
+--
 'POST /changes/link:#change-id[\{change-id\}]/rebase'
+--
 
 Rebases a change.
 
@@ -809,6 +823,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": false,
+    "insertions": 33,
+    "deletions": 9,
     "_sortkey": "0024cf9a000012bf",
     "_number": 4799,
     "owner": {
@@ -864,10 +880,10 @@
 ----
 
 [[revert-change]]
-Revert Change
-~~~~~~~~~~~~~
-[verse]
+=== Revert Change
+--
 'POST /changes/link:#change-id[\{change-id\}]/revert'
+--
 
 Reverts a change.
 
@@ -900,6 +916,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": true,
+    "insertions": 6,
+    "deletions": 4,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
     "owner": {
@@ -922,10 +940,10 @@
 ----
 
 [[submit-change]]
-Submit Change
-~~~~~~~~~~~~~
-[verse]
+=== Submit Change
+--
 'POST /changes/link:#change-id[\{change-id\}]/submit'
+--
 
 Submits a change.
 
@@ -985,10 +1003,10 @@
 ----
 
 [[publish-draft-change]]
-Publish Draft Change
-~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Publish Draft Change
+--
 'POST /changes/link:#change-id[\{change-id\}]/publish'
+--
 
 Publishes a draft change.
 
@@ -1004,10 +1022,10 @@
 ----
 
 [[delete-draft-change]]
-Delete Draft Change
-~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Draft Change
+--
 'DELETE /changes/link:#change-id[\{change-id\}]'
+--
 
 Deletes a draft change.
 
@@ -1023,10 +1041,10 @@
 ----
 
 [[get-included-in]]
-Get Included In
-~~~~~~~~~~~~~~~
-[verse]
+=== Get Included In
+--
 'GET /changes/link:#change-id[\{change-id\}]/in'
+--
 
 Retrieves the branches and tags in which a change is included. As result
 an link:#included-in-info[IncludedInInfo] entity is returned.
@@ -1053,14 +1071,13 @@
 ----
 
 [[reviewer-endpoints]]
-Reviewer Endpoints
-------------------
+== Reviewer Endpoints
 
 [[list-reviewers]]
-List Reviewers
-~~~~~~~~~~~~~~
-[verse]
+=== List Reviewers
+--
 'GET /changes/link:#change-id[\{change-id\}]/reviewers/'
+--
 
 Lists the reviewers of a change.
 
@@ -1103,10 +1120,10 @@
 ----
 
 [[suggest-reviewers]]
-Suggest Reviewers
-~~~~~~~~~~~~~~~~~
-[verse]
+=== Suggest Reviewers
+--
 'GET /changes/link:#change-id[\{change-id\}]/suggest_reviewers?q=J&n=5'
+--
 
 Suggest the reviewers for a given query `q` and result limit `n`. If result
 limit is not passed, then the default 10 is used.
@@ -1145,10 +1162,10 @@
 ----
 
 [[get-reviewer]]
-Get Reviewer
-~~~~~~~~~~~~
-[verse]
+=== Get Reviewer
+--
 'GET /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]'
+--
 
 Retrieves a reviewer of a change.
 
@@ -1180,10 +1197,10 @@
 ----
 
 [[add-reviewer]]
-Add Reviewer
-~~~~~~~~~~~~
-[verse]
+=== Add Reviewer
+--
 'POST /changes/link:#change-id[\{change-id\}]/reviewers'
+--
 
 Adds one user or all members of one group as reviewer to the change.
 
@@ -1271,10 +1288,10 @@
 ----
 
 [[delete-reviewer]]
-Delete Reviewer
-~~~~~~~~~~~~~~~
-[verse]
+=== Delete Reviewer
+--
 'DELETE /changes/link:#change-id[\{change-id\}]/reviewers/link:rest-api-accounts.html#account-id[\{account-id\}]'
+--
 
 Deletes a reviewer from a change.
 
@@ -1289,14 +1306,13 @@
 ----
 
 [[revision-endpoints]]
-Revision Endpoints
-------------------
+== Revision Endpoints
 
 [[get-commit]]
-Get Commit
-~~~~~~~~~~
-[verse]
+=== Get Commit
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/commit'
+--
 
 Retrieves a parsed commit of a revision.
 
@@ -1342,10 +1358,10 @@
 
 
 [[get-review]]
-Get Review
-~~~~~~~~~~
-[verse]
+=== Get Review
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/review'
+--
 
 Retrieves a review of a revision.
 
@@ -1381,6 +1397,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": true,
+    "insertions": 34,
+    "deletions": 45,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
     "owner": {
@@ -1426,8 +1444,8 @@
           }
         ]
         "values": {
-          "-2": "Do not submit",
-          "-1": "I would prefer that you didn\u0027t submit this",
+          "-2": "This shall not be merged",
+          "-1": "I would prefer this is not merged as is",
           " 0": "No score",
           "+1": "Looks good to me, but someone else must approve",
           "+2": "Looks good to me, approved"
@@ -1475,10 +1493,10 @@
 ----
 
 [[set-review]]
-Set Review
-~~~~~~~~~~
-[verse]
+=== Set Review
+--
 'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/review'
+--
 
 Sets a review on a revision.
 
@@ -1528,10 +1546,10 @@
 ----
 
 [[rebase-revision]]
-Rebase Revision
-~~~~~~~~~~~~~~~
-[verse]
+=== Rebase Revision
+--
 'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/rebase'
+--
 
 Rebases a revision.
 
@@ -1562,6 +1580,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": false,
+    "insertions": 21,
+    "deletions": 21,
     "_sortkey": "0024cf9a000012bf",
     "_number": 4799,
     "owner": {
@@ -1617,10 +1637,10 @@
 ----
 
 [[submit-revision]]
-Submit Revision
-~~~~~~~~~~~~~~~
-[verse]
+=== Submit Revision
+--
 'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/submit'
+--
 
 Submits a revision.
 
@@ -1667,10 +1687,10 @@
 ----
 
 [[publish-draft-revision]]
-Publish Draft Revision
-~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Publish Draft Revision
+--
 'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/publish'
+--
 
 Publishes a draft revision.
 
@@ -1686,10 +1706,10 @@
 ----
 
 [[delete-draft-revision]]
-Delete Draft Revision
-~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Draft Revision
+--
 'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]'
+--
 
 Deletes a draft revision.
 
@@ -1705,10 +1725,10 @@
 ----
 
 [[get-patch]]
-Get Patch
-~~~~~~~~~
-[verse]
+=== Get Patch
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/patch'
+--
 
 Gets the formatted patch for one revision.
 
@@ -1740,10 +1760,10 @@
 for later processing by command line tools.
 
 [[get-mergeable]]
-Get Mergeable
-~~~~~~~~~~~~~
-[verse]
+=== Get Mergeable
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/mergeable'
+--
 
 Gets the method the server will use to submit (merge) the change and
 an indicator if the change is currently mergeable.
@@ -1767,10 +1787,10 @@
 ----
 
 [[get-submit-type]]
-Get Submit Type
-~~~~~~~~~~~~~~~
-[verse]
+=== Get Submit Type
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/submit_type'
+--
 
 Gets the method the server will use to submit (merge) the change.
 
@@ -1790,10 +1810,10 @@
 ----
 
 [[test-submit-type]]
-Test Submit Type
-~~~~~~~~~~~~~~~~
-[verse]
+=== Test Submit Type
+--
 'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/test.submit_type'
+--
 
 Tests the submit_type Prolog rule in the project, or the one given.
 
@@ -1821,10 +1841,10 @@
 ----
 
 [[test-submit-rule]]
-Test Submit Rule
-~~~~~~~~~~~~~~~~
-[verse]
+=== Test Submit Rule
+--
 'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/test.submit_rule'
+--
 
 Tests the submit_rule Prolog rule in the project, or the one given.
 
@@ -1863,10 +1883,10 @@
 ----
 
 [[list-drafts]]
-List Drafts
-~~~~~~~~~~~
-[verse]
+=== List Drafts
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/drafts/'
+--
 
 Lists the draft comments of a revision that belong to the calling
 user.
@@ -1909,10 +1929,10 @@
 ----
 
 [[create-draft]]
-Create Draft
-~~~~~~~~~~~~
-[verse]
+=== Create Draft
+--
 'PUT /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/drafts'
+--
 
 Creates a draft comment on a revision.
 
@@ -1952,10 +1972,10 @@
 ----
 
 [[get-draft]]
-Get Draft
-~~~~~~~~~
-[verse]
+=== Get Draft
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/drafts/link:#draft-id[\{draft-id\}]'
+--
 
 Retrieves a draft comment of a revision that belongs to the calling
 user.
@@ -1986,10 +2006,10 @@
 ----
 
 [[update-draft]]
-Update Draft
-~~~~~~~~~~~~
-[verse]
+=== Update Draft
+--
 'PUT /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/drafts/link:#draft-id[\{draft-id\}]'
+--
 
 Updates a draft comment on a revision.
 
@@ -2029,10 +2049,10 @@
 ----
 
 [[delete-draft]]
-Delete Draft
-~~~~~~~~~~~~
-[verse]
+=== Delete Draft
+--
 'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/drafts/link:#draft-id[\{draft-id\}]'
+--
 
 Deletes a draft comment from a revision.
 
@@ -2047,10 +2067,10 @@
 ----
 
 [[list-comments]]
-List Comments
-~~~~~~~~~~~~~
-[verse]
+=== List Comments
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/comments/'
+--
 
 Lists the published comments of a revision.
 
@@ -2102,10 +2122,10 @@
 ----
 
 [[get-comment]]
-Get Comment
-~~~~~~~~~~~
-[verse]
+=== Get Comment
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/comments/link:#comment-id[\{comment-id\}]'
+--
 
 Retrieves a published comment of a revision.
 
@@ -2140,10 +2160,10 @@
 ----
 
 [[list-files]]
-List Files
-~~~~~~~~~~
-[verse]
+=== List Files
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/'
+--
 
 Lists the files that were modified, added or deleted in a revision.
 
@@ -2198,10 +2218,10 @@
 ----
 
 [[get-content]]
-Get Content
-~~~~~~~~~~~
-[verse]
+=== Get Content
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/content'
+--
 
 Gets the content of a file from a certain revision.
 
@@ -2222,10 +2242,10 @@
 ----
 
 [[get-diff]]
-Get Diff
-~~~~~~~~
-[verse]
+=== Get Diff
+--
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/diff'
+--
 
 Gets the diff of a file from a certain revision.
 
@@ -2246,11 +2266,13 @@
   {
     "meta_a": {
       "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
-      "content_type": "text/x-java-source"
+      "content_type": "text/x-java-source",
+      "lines": 372
     },
     "meta_b": {
       "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
-      "content_type": "text/x-java-source"
+      "content_type": "text/x-java-source",
+      "lines": 578
     },
     "change_type": "MODIFIED",
     "diff_header": [
@@ -2314,11 +2336,13 @@
   {
     "meta_a": {
       "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
-      "content_type": "text/x-java-source"
+      "content_type": "text/x-java-source",
+      "lines": 372
     },
     "meta_b": {
       "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
-      "content_type": "text/x-java-source"
+      "content_type": "text/x-java-source",
+      "lines": 578
     },
     "change_type": "MODIFIED",
     "diff_header": [
@@ -2366,11 +2390,13 @@
   {
     "meta_a": {
       "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
-      "content_type": "text/x-java-source"
+      "content_type": "text/x-java-source",
+      "lines": 578
     },
     "meta_b": {
       "name": "gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java",
-      "content_type": "text/x-java-source"
+      "content_type": "text/x-java-source",
+      "lines": 578
     },
     "change_type": "MODIFIED",
     "content": [
@@ -2388,10 +2414,10 @@
 in the diff.  Valid values are `ALL` or number of lines.
 
 [[set-reviewed]]
-Set Reviewed
-~~~~~~~~~~~~
-[verse]
+=== Set Reviewed
+--
 'PUT /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/reviewed'
+--
 
 Marks a file of a revision as reviewed by the calling user.
 
@@ -2409,10 +2435,10 @@
 response is "`200 OK`".
 
 [[delete-reviewed]]
-Delete Reviewed
-~~~~~~~~~~~~~~~
-[verse]
+=== Delete Reviewed
+--
 'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/reviewed'
+--
 
 Deletes the reviewed flag of the calling user from a file of a revision.
 
@@ -2427,10 +2453,10 @@
 ----
 
 [[cherry-pick]]
-Cherry Pick Revision
-~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Cherry Pick Revision
+--
 'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/cherrypick'
+--
 
 Cherry picks a revision to a destination branch.
 
@@ -2469,6 +2495,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": true,
+    "insertions": 12,
+    "deletions": 11,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
     "owner": {
@@ -2478,10 +2506,10 @@
 ----
 
 [[message]]
-Edit Commit Message
-~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Edit Commit Message
+--
 'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/message'
+--
 
 Edit commit message.
 
@@ -2519,6 +2547,8 @@
     "created": "2013-02-01 09:59:32.126000000",
     "updated": "2013-02-21 11:16:36.775000000",
     "mergeable": true,
+    "insertions": 261,
+    "deletions": 101,
     "_sortkey": "0023412400000f7d",
     "_number": 3965,
     "owner": {
@@ -2528,18 +2558,15 @@
 ----
 
 [[ids]]
-IDs
----
+== IDs
 
 [[account-id]]
-link:rest-api-accounts.html#account-id[\{account-id\}]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== link:rest-api-accounts.html#account-id[\{account-id\}]
 --
 --
 
 [[change-id]]
-\{change-id\}
-~~~~~~~~~~~~~
+=== \{change-id\}
 Identifier that uniquely identifies one change.
 
 This can be:
@@ -2552,13 +2579,11 @@
 * a legacy numeric change ID ("4247")
 
 [[comment-id]]
-\{comment-id\}
-~~~~~~~~~~~~~~
+=== \{comment-id\}
 UUID of a published comment.
 
 [[draft-id]]
-\{draft-id\}
-~~~~~~~~~~~~
+=== \{draft-id\}
 UUID of a draft comment.
 
 [[file-id]]
@@ -2567,8 +2592,7 @@
 The path of the file.
 
 [[revision-id]]
-\{revision-id\}
-~~~~~~~~~~~~~~~
+=== \{revision-id\}
 Identifier that uniquely identifies one revision of a change.
 
 This can be:
@@ -2580,12 +2604,10 @@
 * a legacy numeric patch number ("1" for first patch set of the change)
 
 [[json-entities]]
-JSON Entities
--------------
+== JSON Entities
 
 [[abandon-input]]
-AbandonInput
-~~~~~~~~~~~~
+=== AbandonInput
 The `AbandonInput` entity contains information for abandoning a change.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -2597,8 +2619,7 @@
 |===========================
 
 [[action-info]]
-ActionInfo
-~~~~~~~~~~
+=== ActionInfo
 The `ActionInfo` entity describes a REST API call the client can
 make to manipulate a resource. These are frequently implemented by
 plugins and may be discovered at runtime.
@@ -2624,8 +2645,7 @@
 |====================================
 
 [[add-reviewer-result]]
-AddReviewerResult
-~~~~~~~~~~~~~~~~~
+=== AddReviewerResult
 The `AddReviewerResult` entity describes the result of adding a
 reviewer to a change.
 
@@ -2644,8 +2664,7 @@
 |===========================
 
 [[approval-info]]
-ApprovalInfo
-~~~~~~~~~~~~
+=== ApprovalInfo
 The `ApprovalInfo` entity contains information about an approval from a
 user for a label on a change.
 
@@ -2665,8 +2684,7 @@
 |===========================
 
 [[group-base-info]]
-GroupBaseInfo
-~~~~~~~~~~~~~
+=== GroupBaseInfo
 The `GroupBaseInfo` entity contains base information about the group.
 
 [options="header",width="50%",cols="1,6"]
@@ -2677,8 +2695,7 @@
 |==========================
 
 [[change-info]]
-ChangeInfo
-~~~~~~~~~~
+=== ChangeInfo
 The `ChangeInfo` entity contains information about a change.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -2714,6 +2731,10 @@
 |`mergeable`          |optional|
 Whether the change is mergeable. +
 Not set for merged changes.
+|`insertions`         ||
+Number of inserted lines.
+|`deletions`          ||
+Number of deleted lines.
 |`_sortkey`           ||The sortkey of the change.
 |`_number`            ||The legacy numeric ID of the change.
 |`owner`              ||
@@ -2756,8 +2777,7 @@
 |==================================
 
 [[change-message-info]]
-ChangeMessageInfo
-~~~~~~~~~~~~~~~~~
+=== ChangeMessageInfo
 The `ChangeMessageInfo` entity contains information about a message
 attached to a change.
 
@@ -2777,8 +2797,7 @@
 |==================================
 
 [[cherrypick-input]]
-CherryPickInput
-~~~~~~~~~~~~~~~
+=== CherryPickInput
 The `CherryPickInput` entity contains information for cherry-picking a change to a new branch.
 
 [options="header",width="50%",cols="1,6"]
@@ -2789,8 +2808,7 @@
 |===========================
 
 [[comment-info]]
-CommentInfo
-~~~~~~~~~~~
+=== CommentInfo
 The `CommentInfo` entity contains information about an inline comment.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -2825,8 +2843,7 @@
 |===========================
 
 [[comment-input]]
-CommentInput
-~~~~~~~~~~~~
+=== CommentInput
 The `CommitInput` entity contains information for creating an inline
 comment.
 
@@ -2866,8 +2883,7 @@
 |===========================
 
 [[comment-range]]
-CommentRange
-~~~~~~~~~~~~
+=== CommentRange
 The `CommentRange` entity describes the range of an inline comment.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -2880,8 +2896,7 @@
 |===========================
 
 [[commit-info]]
-CommitInfo
-~~~~~~~~~~
+=== CommitInfo
 The `CommitInfo` entity contains information about a commit.
 
 [options="header",width="50%",cols="1,6"]
@@ -2902,8 +2917,7 @@
 |==========================
 
 [[diff-content]]
-DiffContent
-~~~~~~~~~~~
+=== DiffContent
 The `DiffContent` entity contains information about the content differences
 in a file.
 
@@ -2921,11 +2935,14 @@
 link:#diff-intraline-info[DiffIntralineInfo] entity.
 |`skip`     |optional|count of lines skipped on both sides when the file is
 too large to include all common lines.
+|`common`   |optional|Set to `true` if the region is common according
+to the requested ignore-whitespace parameter, but a and b contain
+differing amounts of whitespace. When present and true a and b are
+used instead of ab.
 |==========================
 
 [[diff-file-meta-info]]
-DiffFileMetaInfo
-~~~~~~~~~~~~~~~~
+=== DiffFileMetaInfo
 The `DiffFileMetaInfo` entity contains meta information about a file diff.
 
 [options="header",width="50%",cols="1,6"]
@@ -2933,11 +2950,11 @@
 |Field Name    |Description
 |`name`        |The name of the file.
 |`content_type`|The content type of the file.
+|`lines`       |The total number of lines in the file.
 |==========================
 
 [[diff-info]]
-DiffInfo
-~~~~~~~~
+=== DiffInfo
 The `DiffInfo` entity contains information about the diff of a file
 in a revision.
 
@@ -2960,8 +2977,7 @@
 |==========================
 
 [[diff-intraline-info]]
-DiffIntralineInfo
-~~~~~~~~~~~~~~~~~
+=== DiffIntralineInfo
 The `DiffIntralineInfo` entity contains information about intraline edits in a
 file.
 
@@ -2975,8 +2991,7 @@
 the length calculation, and thus it is possible for the edits to span newlines.
 
 [[fetch-info]]
-FetchInfo
-~~~~~~~~~
+=== FetchInfo
 The `FetchInfo` entity contains information about how to fetch a patch
 set via a certain protocol.
 
@@ -2992,8 +3007,7 @@
 |==========================
 
 [[file-info]]
-FileInfo
-~~~~~~~~
+=== FileInfo
 The `FileInfo` entity contains information about a file in a patch set.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -3016,8 +3030,7 @@
 |=============================
 
 [[git-person-info]]
-GitPersonInfo
-~~~~~~~~~~~~~
+=== GitPersonInfo
 The `GitPersonInfo` entity contains information about the
 author/committer of a commit.
 
@@ -3033,8 +3046,7 @@
 |==========================
 
 [[label-info]]
-LabelInfo
-~~~~~~~~~
+=== LabelInfo
 The `LabelInfo` entity contains information about a label on a change, always
 corresponding to the current patch set.
 
@@ -3045,8 +3057,7 @@
 * For detailed information about labels, including exact numeric votes for all
   users and the allowed range of votes for the current user, use `DETAILED_LABELS`.
 
-Common fields
-^^^^^^^^^^^^^
+==== Common fields
 [options="header",width="50%",cols="1,^1,5"]
 |===========================
 |Field Name    ||Description
@@ -3056,8 +3067,7 @@
 set.
 |===========================
 
-Fields set by `LABELS`
-^^^^^^^^^^^^^^^^^^^^^^
+==== Fields set by `LABELS`
 [options="header",width="50%",cols="1,^1,5"]
 |===========================
 |Field Name    ||Description
@@ -3080,8 +3090,7 @@
 "`+1`"/"`-1`".
 |===========================
 
-Fields set by `DETAILED_LABELS`
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Fields set by `DETAILED_LABELS`
 [options="header",width="50%",cols="1,^1,5"]
 |===========================
 |Field Name    ||Description
@@ -3094,8 +3103,7 @@
 
 
 [[restore-input]]
-RestoreInput
-~~~~~~~~~~~~
+=== RestoreInput
 The `RestoreInput` entity contains information for restoring a change.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -3107,8 +3115,7 @@
 |===========================
 
 [[revert-input]]
-RevertInput
-~~~~~~~~~~~
+=== RevertInput
 The `RevertInput` entity contains information for reverting a change.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -3120,8 +3127,7 @@
 |===========================
 
 [[review-info]]
-ReviewInfo
-~~~~~~~~~~
+=== ReviewInfo
 The `ReviewInfo` entity contains information about a review.
 
 [options="header",width="50%",cols="1,6"]
@@ -3133,8 +3139,7 @@
 |===========================
 
 [[review-input]]
-ReviewInput
-~~~~~~~~~~~
+=== ReviewInput
 The `ReviewInput` entity contains information for adding a review to a
 revision.
 
@@ -3174,8 +3179,7 @@
 |============================
 
 [[reviewer-info]]
-ReviewerInfo
-~~~~~~~~~~~~
+=== ReviewerInfo
 The `ReviewerInfo` entity contains information about a reviewer and its
 votes on a change.
 
@@ -3194,8 +3198,7 @@
 |==========================
 
 [[reviewer-input]]
-ReviewerInput
-~~~~~~~~~~~~~
+=== ReviewerInput
 The `ReviewerInput` entity contains information for adding a reviewer
 to a change.
 
@@ -3216,8 +3219,7 @@
 |===========================
 
 [[revision-info]]
-RevisionInfo
-~~~~~~~~~~~~
+=== RevisionInfo
 The `RevisionInfo` entity contains information about a patch set.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -3244,8 +3246,7 @@
 |===========================
 
 [[rule-input]]
-RuleInput
-~~~~~~~~~
+=== RuleInput
 The `RuleInput` entity contains information to test a Prolog rule.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -3262,8 +3263,7 @@
 |===========================
 
 [[suggested-reviewer-info]]
-SuggestedReviewerInfo
-~~~~~~~~~~~~~~~~~~~~~
+=== SuggestedReviewerInfo
 The `SuggestedReviewerInfo` entity contains information about a reviewer
 that can be added to a change (an account or a group).
 
@@ -3273,8 +3273,7 @@
 link:rest-api-changes.html#group-base-info[GroupBaseInfo] entity.
 
 [[submit-info]]
-SubmitInfo
-~~~~~~~~~~
+=== SubmitInfo
 The `SubmitInfo` entity contains information about the change status
 after submitting.
 
@@ -3287,11 +3286,19 @@
 If `wait_for_merge` in the link:#submit-input[SubmitInput] was set to
 `false` the returned status is `SUBMITTED` and the caller can't know
 whether the change could be merged successfully.
+|`on_behalf_of`|optional|
+The link:rest-api-accounts.html#account-id[\{account-id\}] of the user on
+whose behalf the action should be done. To use this option the caller must
+have been granted both `Submit` and `Submit (On Behalf Of)` permissions.
+The user named by `on_behalf_of` does not need to be granted the `Submit`
+permission. This feature is aimed for CI solutions: the CI account can be
+granted both permssions, so individual users don't need `Submit` permission
+themselves. Still the changes can be submited on behalf of real users and
+not with the identity of the CI account.
 |==========================
 
 [[submit-input]]
-SubmitInput
-~~~~~~~~~~~
+=== SubmitInput
 The `SubmitInput` entity contains information for submitting a change.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -3305,8 +3312,7 @@
 |===========================
 
 [[submit-record]]
-SubmitRecord
-~~~~~~~~~~~~
+=== SubmitRecord
 The `SubmitRecord` entity describes results from a submit_rule.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -3341,8 +3347,7 @@
 |===========================
 
 [[topic-input]]
-TopicInput
-~~~~~~~~~~
+=== TopicInput
 The `TopicInput` entity contains information for setting a topic.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -3350,14 +3355,10 @@
 |Field Name    ||Description
 |`topic`       |optional|The topic. +
 The topic will be deleted if not set.
-|`message`     |optional|
-Message to be added as review comment to the change when setting the
-topic.
 |===========================
 
 [[included-in-info]]
-IncludedInInfo
-~~~~~~~~~~~~~~
+=== IncludedInInfo
 The `IncludedInInfo` entity contains information about the branches a
 change was merged into and tags it was tagged with.
 
@@ -3375,3 +3376,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 5bd5e0c..6c02bb6 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - /config/ REST API
-======================================
+= Gerrit Code Review - /config/ REST API
 
 This page describes the config related REST endpoints.
 Please also take note of the general information on the
@@ -10,10 +9,10 @@
 ---------------
 
 [[get-version]]
-Get Version
-~~~~~~~~~~~
-[verse]
+=== Get Version
+--
 'GET /config/server/version'
+--
 
 Returns the version of the Gerrit server.
 
@@ -32,10 +31,10 @@
 ----
 
 [[list-capabilities]]
-List Capabilities
-~~~~~~~~~~~~~~~~~
-[verse]
+=== List Capabilities
+--
 'GET /config/server/capabilities'
+--
 
 Lists the capabilities that are available in the system. There are two
 kinds of capabilities: core and plugin-owned capabilities.
@@ -136,10 +135,10 @@
 ----
 
 [[get-top-menus]]
-Get Top Menus
-~~~~~~~~~~~~~
-[verse]
+=== Get Top Menus
+--
 'GET /config/server/top-menus'
+--
 
 Returns the list of additional top menu entries.
 
@@ -173,12 +172,10 @@
 
 
 [[json-entities]]
-JSON Entities
--------------
+== JSON Entities
 
 [[capability-info]]
-CapabilityInfo
-~~~~~~~~~~~~~~
+=== CapabilityInfo
 The `CapabilityInfo` entity contains information about a capability.
 
 [options="header",width="50%",cols="1,6"]
@@ -190,8 +187,7 @@
 |=================================
 
 [[top-menu-entry-info]]
-TopMenuEntryInfo
-~~~~~~~~~~~~~~~~
+=== TopMenuEntryInfo
 The `TopMenuEntryInfo` entity contains information about a top menu
 entry.
 
@@ -203,8 +199,7 @@
 |=================================
 
 [[top-menu-item-info]]
-TopMenuItemInfo
-~~~~~~~~~~~~~~~
+=== TopMenuItemInfo
 The `TopMenuItemInfo` entity contains information about a menu item in
 a top menu entry.
 
@@ -220,3 +215,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/rest-api-documentation.txt b/Documentation/rest-api-documentation.txt
new file mode 100644
index 0000000..de07024
--- /dev/null
+++ b/Documentation/rest-api-documentation.txt
@@ -0,0 +1,150 @@
+= Gerrit Code Review - /Documentation/ REST API
+
+This page describes the documentation search related REST endpoints.
+Please also take note of the general information on the
+link:rest-api.html[REST API].
+
+Please note that this feature is only usable with documentation built-in.
+You'll need to
+`buck build withdocs`
+or
+`buck build release`
+to test this feature.
+
+[[documentation-endpoints]]
+== Documentation Search Endpoints
+
+[[search-documentation]]
+=== Search Documentation
+--
+'GET /Documentation/'
+--
+
+With `q` parameter, search our documentation index for the terms.
+
+A list of link:#doc-result[DocResult] entities is returned describing the
+results.
+
+.Request
+----
+  GET /Documentation/?q=test HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  [
+    {
+      "title": "Gerrit Code Review - REST API Developers\u0027 Notes",
+      "url": "Documentation/dev-rest-api.html"
+    },
+    {
+      "title": "Gerrit Code Review - REST API",
+      "url": "Documentation/rest-api.html"
+    },
+    {
+      "title": "Gerrit Code Review - JavaScript API",
+      "url": "Documentation/js-api.html"
+    },
+    {
+      "title": "Gerrit Code Review - /plugins/ REST API",
+      "url": "Documentation/rest-api-plugins.html"
+    },
+    {
+      "title": "Gerrit Code Review - /config/ REST API",
+      "url": "Documentation/rest-api-config.html"
+    },
+    {
+      "title": "Gerrit Code Review for Git",
+      "url": "Documentation/index.html"
+    },
+    {
+      "title": "Gerrit Code Review - /access/ REST API",
+      "url": "Documentation/rest-api-access.html"
+    },
+    {
+      "title": "Gerrit Code Review - Plugin Development",
+      "url": "Documentation/dev-plugins.html"
+    },
+    {
+      "title": "Gerrit Code Review - Developer Setup",
+      "url": "Documentation/dev-readme.html"
+    },
+    {
+      "title": "Gerrit Code Review - Hooks",
+      "url": "Documentation/config-hooks.html"
+    },
+    {
+      "title": "Change Screen - Introduction",
+      "url": "Documentation/intro-change-screen.html"
+    },
+    {
+      "title": "Gerrit Code Review - /groups/ REST API",
+      "url": "Documentation/rest-api-groups.html"
+    },
+    {
+      "title": "Gerrit Code Review - /accounts/ REST API",
+      "url": "Documentation/rest-api-accounts.html"
+    },
+    {
+      "title": "Gerrit Code Review - /projects/ REST API",
+      "url": "Documentation/rest-api-documentation.html"
+    },
+    {
+      "title": "Gerrit Code Review - /projects/ REST API",
+      "url": "Documentation/rest-api-projects.html"
+    },
+    {
+      "title": "Gerrit Code Review - Prolog Submit Rules Cookbook",
+      "url": "Documentation/prolog-cookbook.html"
+    },
+    {
+      "title": "Gerrit Code Review - /changes/ REST API",
+      "url": "Documentation/rest-api-changes.html"
+    },
+    {
+      "title": "Gerrit Code Review - Configuration",
+      "url": "Documentation/config-gerrit.html"
+    },
+    {
+      "title": "Gerrit Code Review - Access Controls",
+      "url": "Documentation/access-control.html"
+    },
+    {
+      "title": "Gerrit Code Review - Licenses",
+      "url": "Documentation/licenses.html"
+    }
+  ]
+----
+
+.Query documentation
+****
+get::/Documentation/?q=keyword
+****
+
+
+[[json-entities]]
+== JSON Entities
+
+[[doc-result]]
+=== DocResult
+The `DocResult` entity contains information about a document.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=========================
+|Field Name  ||Description
+|`title`     ||The title of the document.
+|`url`       ||The URL of the document.
+|=========================
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index e3559a3..5ca3039 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -1,19 +1,17 @@
-Gerrit Code Review - /groups/ REST API
-======================================
+= Gerrit Code Review - /groups/ REST API
 
 This page describes the group related REST endpoints.
 Please also take note of the general information on the
 link:rest-api.html[REST API].
 
 [[group-endpoints]]
-Group Endpoints
----------------
+== Group Endpoints
 
 [[list-groups]]
-List Groups
-~~~~~~~~~~~
-[verse]
+=== List Groups
+--
 'GET /groups/'
+--
 
 Lists the groups accessible by the caller. This is the same as
 using the link:cmd-ls-groups.html[ls-groups] command over SSH,
@@ -111,8 +109,7 @@
 ****
 
 [[group-options]]
-Group Options
-^^^^^^^^^^^^^
+==== Group Options
 Additional fields can be obtained by adding `o` parameters, each option
 requires more lookups and slows down the query response time to the
 client so they are generally disabled by default. Optional fields are:
@@ -127,8 +124,7 @@
 * `MEMBERS`: include list of direct group members.
 --
 
-Check if a group is owned by the calling user
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Check if a group is owned by the calling user
 By setting the option `owned` and specifying a group to inspect with
 the option `q`, it is possible to find out, if this group is owned by
 the calling user.
@@ -184,10 +180,10 @@
 ----
 
 [[get-group]]
-Get Group
-~~~~~~~~~
-[verse]
+=== Get Group
+--
 'GET /groups/link:#group-id[\{group-id\}]'
+--
 
 Retrieves a group.
 
@@ -221,10 +217,10 @@
 ----
 
 [[create-group]]
-Create Group
-~~~~~~~~~~~~
-[verse]
+=== Create Group
+--
 'PUT /groups/link:#group-name[\{group-name\}]'
+--
 
 Creates a new Gerrit internal group.
 
@@ -273,10 +269,10 @@
 response is "`409 Conflict`".
 
 [[get-group-detail]]
-Get Group Detail
-~~~~~~~~~~~~~~~~
-[verse]
+=== Get Group Detail
+--
 'GET /groups/link:#group-id[\{group-id\}]/detail'
+--
 
 Retrieves a group with the direct link:#members[members] and the
 directly link:#includes[included groups].
@@ -326,10 +322,10 @@
 ----
 
 [[get-group-name]]
-Get Group Name
-~~~~~~~~~~~~~~
-[verse]
+=== Get Group Name
+--
 'GET /groups/link:#group-id[\{group-id\}]/name'
+--
 
 Retrieves the name of a group.
 
@@ -349,10 +345,10 @@
 ----
 
 [[rename-group]]
-Rename Group
-~~~~~~~~~~~~
-[verse]
+=== Rename Group
+--
 'PUT /groups/link:#group-id[\{group-id\}]/name'
+--
 
 Renames a Gerrit internal group.
 
@@ -384,10 +380,10 @@
 response is "`409 Conflict`".
 
 [[get-group-description]]
-Get Group Description
-~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Group Description
+--
 'GET /groups/link:#group-id[\{group-id\}]/description'
+--
 
 Retrieves the description of a group.
 
@@ -409,10 +405,10 @@
 If the group does not have a description an empty string is returned.
 
 [[set-group-description]]
-Set Group Description
-~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Set Group Description
+--
 'PUT /groups/link:#group-id[\{group-id\}]/description'
+--
 
 Sets the description of a Gerrit internal group.
 
@@ -443,10 +439,10 @@
 If the description was deleted the response is "`204 No Content`".
 
 [[delete-group-description]]
-Delete Group Description
-~~~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Group Description
+--
 'DELETE /groups/link:#group-id[\{group-id\}]/description'
+--
 
 Deletes the description of a Gerrit internal group.
 
@@ -461,10 +457,10 @@
 ----
 
 [[get-group-options]]
-Get Group Options
-~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Group Options
+--
 'GET /groups/link:#group-id[\{group-id\}]/options'
+--
 
 Retrieves the options of a group.
 
@@ -489,10 +485,10 @@
 ----
 
 [[set-group-options]]
-Set Group Options
-~~~~~~~~~~~~~~~~~
-[verse]
+=== Set Group Options
+--
 'PUT /groups/link:#group-id[\{group-id\}]/options'
+--
 
 Sets the options of a Gerrit internal group.
 
@@ -525,10 +521,10 @@
 ----
 
 [[get-group-owner]]
-Get Group Owner
-~~~~~~~~~~~~~~~
-[verse]
+=== Get Group Owner
+--
 'GET /groups/link:#group-id[\{group-id\}]/owner'
+--
 
 Retrieves the owner group of a Gerrit internal group.
 
@@ -562,10 +558,10 @@
 ----
 
 [[set-group-owner]]
-Set Group Owner
-~~~~~~~~~~~~~~~
-[verse]
+=== Set Group Owner
+--
 'PUT /groups/link:#group-id[\{group-id\}]/owner'
+--
 
 Sets the owner group of a Gerrit internal group.
 
@@ -609,14 +605,13 @@
 ----
 
 [[group-member-endpoints]]
-Group Member Endpoints
-----------------------
+== Group Member Endpoints
 
 [[group-members]]
-List Group Members
-~~~~~~~~~~~~~~~~~~
-[verse]
+=== List Group Members
+--
 'GET /groups/link:#group-id[\{group-id\}]/members/'
+--
 
 Lists the direct members of a Gerrit internal group.
 
@@ -698,10 +693,10 @@
 ----
 
 [[get-group-member]]
-Get Group Member
-~~~~~~~~~~~~~~~~
-[verse]
+=== Get Group Member
+--
 'GET /groups/link:#group-id[\{group-id\}]/members/link:rest-api-accounts.html#account-id[\{account-id\}]'
+--
 
 Retrieves a group member.
 
@@ -729,10 +724,10 @@
 ----
 
 [[add-group-member]]
-Add Group Member
-~~~~~~~~~~~~~~~~
-[verse]
+=== Add Group Member
+--
 'PUT /groups/link:#group-id[\{group-id\}]/members/link:rest-api-accounts.html#account-id[\{account-id\}]'
+--
 
 Adds a user as member to a Gerrit internal group.
 
@@ -762,15 +757,16 @@
 The request also succeeds if the user is already a member of this
 group, but then the HTTP response code is `200 OK`.
 
-Add Group Members
-~~~~~~~~~~~~~~~~~
-[verse]
+=== Add Group Members
+--
 'POST /groups/link:#group-id[\{group-id\}]/members'
+--
 
 OR
 
-[verse]
+--
 'POST /groups/link:#group-id[\{group-id\}]/members.add'
+--
 
 Adds one or several users to a Gerrit internal group.
 
@@ -822,10 +818,10 @@
 ----
 
 [[delete-group-member]]
-Delete Group Member
-~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Group Member
+--
 'DELETE /groups/link:#group-id[\{group-id\}]/members/link:rest-api-accounts.html#account-id[\{account-id\}]'
+--
 
 Deletes a user from a Gerrit internal group.
 
@@ -840,10 +836,10 @@
 ----
 
 [[delete-group-members]]
-Delete Group Members
-~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Group Members
+--
 'POST /groups/link:#group-id[\{group-id\}]/members.delete'
+--
 
 Delete one or several users from a Gerrit internal group.
 
@@ -869,14 +865,13 @@
 ----
 
 [[group-include-endpoints]]
-Group Include Endpoints
------------------------
+== Group Include Endpoints
 
 [[included-groups]]
-List Included Groups
-~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== List Included Groups
+--
 'GET /groups/link:#group-id[\{group-id\}]/groups/'
+--
 
 Lists the directly included groups of a group.
 
@@ -911,10 +906,10 @@
 ----
 
 [[get-included-group]]
-Get Included Group
-~~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Included Group
+--
 'GET /groups/link:#group-id[\{group-id\}]/groups/link:#group-id[\{group-id\}]'
+--
 
 Retrieves an included group.
 
@@ -947,10 +942,10 @@
 ----
 
 [[include-group]]
-Include Group
-~~~~~~~~~~~~~
-[verse]
+=== Include Group
+--
 'PUT /groups/link:#group-id[\{group-id\}]/groups/link:#group-id[\{group-id\}]'
+--
 
 Includes an internal or external group into a Gerrit internal group.
 External groups must be specified using the UUID.
@@ -987,15 +982,16 @@
 group, but then the HTTP response code is `200 OK`.
 
 [[include-groups]]
-Include Groups
-~~~~~~~~~~~~~~
-[verse]
+=== Include Groups
+--
 'POST /groups/link:#group-id[\{group-id\}]/groups'
+--
 
 OR
 
-[verse]
+--
 'POST /groups/link:#group-id[\{group-id\}]/groups.add'
+--
 
 Includes one or several groups into a Gerrit internal group.
 
@@ -1056,10 +1052,10 @@
 ----
 
 [[delete-included-group]]
-Delete Included Group
-~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Included Group
+--
 'DELETE /groups/link:#group-id[\{group-id\}]/groups/link:#group-id[\{group-id\}]'
+--
 
 Deletes an included group from a Gerrit internal group.
 
@@ -1074,10 +1070,10 @@
 ----
 
 [[delete-included-groups]]
-Delete Included Groups
-~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Included Groups
+--
 'POST /groups/link:#group-id[\{group-id\}]/groups.delete'
+--
 
 Delete one or several included groups from a Gerrit internal group.
 
@@ -1104,18 +1100,15 @@
 
 
 [[ids]]
-IDs
----
+== IDs
 
 [[account-id]]
-link:rest-api-accounts.html#account-id[\{account-id\}]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== link:rest-api-accounts.html#account-id[\{account-id\}]
 --
 --
 
 [[group-id]]
-\{group-id\}
-~~~~~~~~~~~~
+=== \{group-id\}
 Identifier for a group.
 
 This can be:
@@ -1125,18 +1118,15 @@
 * the name of the group if it is unique
 
 [[group-name]]
-\{group-name\}
-~~~~~~~~~~~~~~
+=== \{group-name\}
 Group name that uniquely identifies one group.
 
 
 [[json-entities]]
-JSON Entities
--------------
+== JSON Entities
 
 [[group-info]]
-GroupInfo
-~~~~~~~~~
+=== GroupInfo
 The `GroupInfo` entity contains information about a group. This can be
 a Gerrit internal group, or an external group that is known to Gerrit.
 
@@ -1176,8 +1166,7 @@
 |============
 
 [[group-input]]
-GroupInput
-~~~~~~~~~~
+=== GroupInput
 The 'GroupInput' entity contains information for the creation of
 a new internal group.
 
@@ -1197,8 +1186,7 @@
 |===========================
 
 [[groups-input]]
-GroupsInput
-~~~~~~~~~~~
+=== GroupsInput
 The `GroupsInput` entity contains information about groups that should
 be included into a group or that should be deleted from a group.
 
@@ -1213,8 +1201,7 @@
 |==========================
 
 [[group-options-info]]
-GroupOptionsInfo
-~~~~~~~~~~~~~~~~
+=== GroupOptionsInfo
 Options of the group.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -1225,8 +1212,7 @@
 |=============================
 
 [[group-options-input]]
-GroupOptionsInput
-~~~~~~~~~~~~~~~~~
+=== GroupOptionsInput
 New options for a group.
 
 [options="header",width="50%",cols="1,^1,5"]
@@ -1257,3 +1243,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/rest-api-plugins.txt b/Documentation/rest-api-plugins.txt
index 2cc80f0..89a22f0 100644
--- a/Documentation/rest-api-plugins.txt
+++ b/Documentation/rest-api-plugins.txt
@@ -1,13 +1,11 @@
-Gerrit Code Review - /plugins/ REST API
-=======================================
+= Gerrit Code Review - /plugins/ REST API
 
 This page describes the plugin related REST endpoints.
 Please also take note of the general information on the
 link:rest-api.html[REST API].
 
 [[plugin-endpoints]]
-Plugin Endpoints
-----------------
+== Plugin Endpoints
 
 Gerrit REST endpoints for installed plugins are available under
 '/plugins/link:#plugin-id[\{plugin-id\}]/gerrit~<endpoint-id>'.
@@ -17,10 +15,10 @@
 
 
 [[list-plugins]]
-List Plugins
-~~~~~~~~~~~~
-[verse]
+=== List Plugins
+--
 'GET /plugins/'
+--
 
 Lists the plugins installed on the Gerrit server. Only the enabled
 plugins are returned unless the `all` option is specified.
@@ -45,22 +43,24 @@
     "delete-project": {
       "kind": "gerritcodereview#plugin",
       "id": "delete-project",
-      "version": "2.8-SNAPSHOT"
+      "index_url": "plugins/delete-project/",
+      "version": "2.9-SNAPSHOT"
     },
     "reviewers-by-blame": {
       "kind": "gerritcodereview#plugin",
       "id": "reviewers-by-blame",
-      "version": "2.8-SNAPSHOT",
+      "index_url": "plugins/reviewers-by-blame/",
+      "version": "2.9-SNAPSHOT",
       "disabled": true
     }
   }
 ----
 
 [[install-plugin]]
-Install Plugin
-~~~~~~~~~~~~~~
-[verse]
+=== Install Plugin
+--
 'PUT /plugins/link:#plugin-id[\{plugin-id\}]'
+--
 
 Installs a new plugin on the Gerrit server. If a plugin with the
 specified name already exists it is overwritten. Note: if the plugin
@@ -108,10 +108,10 @@
 If an existing plugin was overwritten the response is "`200 OK`".
 
 [[get-plugin-status]]
-Get Plugin Status
-~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Plugin Status
+--
 'GET /plugins/link:#plugin-id[\{plugin-id\}]/gerrit~status'
+--
 
 Retrieves the status of a plugin on the Gerrit server.
 
@@ -138,10 +138,10 @@
 ----
 
 [[enable-plugin]]
-Enable Plugin
-~~~~~~~~~~~~~
-[verse]
+=== Enable Plugin
+--
 'POST /plugins/link:#plugin-id[\{plugin-id\}]/gerrit~enable'
+--
 
 Enables a plugin on the Gerrit server.
 
@@ -168,15 +168,16 @@
 ----
 
 [[disable-plugin]]
-Disable Plugin
-~~~~~~~~~~~~~~
-[verse]
+=== Disable Plugin
+--
 'POST /plugins/link:#plugin-id[\{plugin-id\}]/gerrit~disable'
+--
 
 OR
 
-[verse]
+--
 'DELETE /plugins/link:#plugin-id[\{plugin-id\}]'
+--
 
 Disables a plugin on the Gerrit server.
 
@@ -204,10 +205,10 @@
 ----
 
 [[reload-plugin]]
-Reload Plugin
-~~~~~~~~~~~~~
-[verse]
+=== Reload Plugin
+--
 'POST /plugins/link:#plugin-id[\{plugin-id\}]/gerrit~reload'
+--
 
 Reloads a plugin on the Gerrit server.
 
@@ -236,35 +237,31 @@
 
 
 [[ids]]
-IDs
----
+== IDs
 
 [[plugin-id]]
-\{plugin-id\}
-~~~~~~~~~~~~~
+=== \{plugin-id\}
 The ID of the plugin.
 
 [[json-entities]]
-JSON Entities
--------------
+== JSON Entities
 
 [[plugin-info]]
-PluginInfo
-~~~~~~~~~~
+=== PluginInfo
 The `PluginInfo` entity describes a plugin.
 
 [options="header",width="50%",cols="1,^2,4"]
 |=======================
-|Field Name||Description
-|`kind`    ||`gerritcodereview#plugin`
-|`id`      ||The ID of the plugin.
-|`version` ||The version of the plugin.
-|`disabled`|not set if `false`|Whether the plugin is disabled.
+|Field Name ||Description
+|`kind`     ||`gerritcodereview#plugin`
+|`id`       ||The ID of the plugin.
+|`version`  ||The version of the plugin.
+|`index_url`|optional|URL of the plugin's default page.
+|`disabled` |not set if `false`|Whether the plugin is disabled.
 |=======================
 
 [[plugin-input]]
-PluginInput
-~~~~~~~~~~~
+=== PluginInput
 The `PluginInput` entity describes a plugin that should be installed.
 
 [options="header",width="50%",cols="1,6"]
@@ -277,3 +274,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 44eecdc..418b93c 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1,19 +1,17 @@
-Gerrit Code Review - /projects/ REST API
-========================================
+= Gerrit Code Review - /projects/ REST API
 
 This page describes the project related REST endpoints.
 Please also take note of the general information on the
 link:rest-api.html[REST API].
 
 [[project-endpoints]]
-Project Endpoints
------------------
+== Project Endpoints
 
 [[list-projects]]
-List Projects
-~~~~~~~~~~~~~
-[verse]
+=== List Projects
+--
 'GET /projects/'
+--
 
 Lists the projects accessible by the caller. This is the same as
 using the link:cmd-ls-projects.html[ls-projects] command over SSH,
@@ -111,10 +109,10 @@
 ----
 
 [[get-project]]
-Get Project
-~~~~~~~~~~~
-[verse]
+=== Get Project
+--
 'GET /projects/link:#project-name[\{project-name\}]'
+--
 
 Retrieves a project.
 
@@ -143,10 +141,10 @@
 ----
 
 [[create-project]]
-Create Project
-~~~~~~~~~~~~~~
-[verse]
+=== Create Project
+--
 'PUT /projects/link:#project-name[\{project-name\}]'
+--
 
 Creates a new project.
 
@@ -187,10 +185,10 @@
 ----
 
 [[get-project-description]]
-Get Project Description
-~~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Project Description
+--
 'GET /projects/link:#project-name[\{project-name\}]/description'
+--
 
 Retrieves the description of a project.
 
@@ -212,10 +210,10 @@
 If the project does not have a description an empty string is returned.
 
 [[set-project-description]]
-Set Project Description
-~~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Set Project Description
+--
 'PUT /projects/link:#project-name[\{project-name\}]/description'
+--
 
 Sets the description of a project.
 
@@ -248,10 +246,10 @@
 If the description was deleted the response is "`204 No Content`".
 
 [[delete-project-description]]
-Delete Project Description
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Project Description
+--
 'DELETE /projects/link:#project-name[\{project-name\}]/description'
+--
 
 Deletes the description of a project.
 
@@ -274,10 +272,10 @@
 ----
 
 [[get-project-parent]]
-Get Project Parent
-~~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Project Parent
+--
 'GET /projects/link:#project-name[\{project-name\}]/parent'
+--
 
 Retrieves the name of a project's parent project. For the
 `All-Projects` root project an empty string is returned.
@@ -298,10 +296,10 @@
 ----
 
 [[set-project-parent]]
-Set Project Parent
-~~~~~~~~~~~~~~~~~~
-[verse]
+=== Set Project Parent
+--
 'PUT /projects/link:#project-name[\{project-name\}]/parent'
+--
 
 Sets the parent project for a project.
 
@@ -332,10 +330,10 @@
 ----
 
 [[get-head]]
-Get HEAD
-~~~~~~~~
-[verse]
+=== Get HEAD
+--
 'GET /projects/link:#project-name[\{project-name\}]/HEAD'
+--
 
 Retrieves for a project the name of the branch to which `HEAD` points.
 
@@ -355,10 +353,10 @@
 ----
 
 [[set-head]]
-Set HEAD
-~~~~~~~~
-[verse]
+=== Set HEAD
+--
 'PUT /projects/link:#project-name[\{project-name\}]/HEAD'
+--
 
 Sets `HEAD` for a project.
 
@@ -388,10 +386,10 @@
 ----
 
 [[get-repository-statistics]]
-Get Repository Statistics
-~~~~~~~~~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Repository Statistics
+--
 'GET /projects/link:#project-name[\{project-name\}]/statistics.git'
+--
 
 Return statistics for the repository of a project.
 
@@ -422,10 +420,10 @@
 ----
 
 [[get-config]]
-Get Config
-~~~~~~~~~~
-[verse]
+=== Get Config
+--
 'GET /projects/link:#project-name[\{project-name\}]/config'
+--
 
 Gets some configuration information about a project. Note that this
 config info is not simply the contents of `project.config`; it generally
@@ -478,6 +476,15 @@
     "submit_type": "MERGE_IF_NECESSARY",
     "state": "ACTIVE",
     "commentlinks": {},
+    "plugin_config": {
+      "helloworld": {
+        "language": {
+          "display_name": "Preferred Language",
+          "type": "STRING",
+          "value": "en"
+        }
+      }
+    },
     "actions": {
       "cookbook~hello-project": {
         "method": "POST",
@@ -490,10 +497,10 @@
 ----
 
 [[set-config]]
-Set Config
-~~~~~~~~~~
-[verse]
+=== Set Config
+--
 'PUT /projects/link:#project-name[\{project-name\}]/config'
+--
 
 Sets the configuration of a project.
 
@@ -561,16 +568,24 @@
 ----
 
 [[run-gc]]
-Run GC
-~~~~~~
-[verse]
+=== Run GC
+--
 'POST /projects/link:#project-name[\{project-name\}]/gc'
+--
 
 Run the Git garbage collection for the repository of a project.
 
+Options for the Git garbage collection can be specified in the
+request body as a link:#gc-input[GCInput] entity.
+
 .Request
 ----
   POST /projects/plugins%2Freplication/gc HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "show_progress": true
+  }
 ----
 
 The response is the streamed output of the garbage collection.
@@ -600,14 +615,13 @@
 ----
 
 [[branch-endpoints]]
-Branch Endpoints
-----------------
+== Branch Endpoints
 
 [[list-branches]]
-List Branches
-~~~~~~~~~~~~~
-[verse]
+=== List Branches
+--
 'GET /projects/link:#project-name[\{project-name\}]/branches/'
+--
 
 List the branches of a project.
 
@@ -648,10 +662,10 @@
 ----
 
 [[get-branch]]
-Get Branch
-~~~~~~~~~~
-[verse]
+=== Get Branch
+--
 'GET /projects/link:#project-name[\{project-name\}]/branches/link:#branch-id[\{branch-id\}]'
+--
 
 Retrieves a branch of a project.
 
@@ -677,10 +691,10 @@
 ----
 
 [[create-branch]]
-Create Branch
-~~~~~~~~~~~~~
-[verse]
+=== Create Branch
+--
 'PUT /projects/link:#project-name[\{project-name\}]/branches/link:#branch-id[\{branch-id\}]'
+--
 
 Creates a new branch.
 
@@ -715,10 +729,10 @@
 ----
 
 [[delete-branch]]
-Delete Branch
-~~~~~~~~~~~~~
-[verse]
+=== Delete Branch
+--
 'DELETE /projects/link:#project-name[\{project-name\}]/branches/link:#branch-id[\{branch-id\}]'
+--
 
 Deletes a branch.
 
@@ -732,15 +746,38 @@
   HTTP/1.1 204 No Content
 ----
 
+[[get-content]]
+=== Get Content
+--
+'GET /projects/link:#project-name[\{project-name\}]/branches/link:#branch-id[\{branch-id\}]/files/link:rest-api-changes.html#file-id[\{file-id\}]/content'
+--
+
+Gets the content of a file from the HEAD revision of a certain branch.
+
+.Request
+----
+  GET /projects/gerrit/branches/master/files/gerrit-server%2Fsrc%2Fmain%2Fjava%2Fcom%2Fgoogle%2Fgerrit%2Fserver%2Fproject%2FRefControl.java/content HTTP/1.0
+----
+
+The content is returned as base64 encoded string.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: text/plain;charset=UTF-8
+
+  Ly8gQ29weXJpZ2h0IChDKSAyMDEwIFRoZSBBbmRyb2lkIE9wZW4gU291cmNlIFByb2plY...
+----
+
 [[child-project-endpoints]]
-Child Project Endpoints
------------------------
+== Child Project Endpoints
 
 [[list-child-projects]]
-List Child Projects
-~~~~~~~~~~~~~~~~~~~
-[verse]
+=== List Child Projects
+--
 'GET /projects/link:#project-name[\{project-name\}]/children/'
+--
 
 List the direct child projects of a project.
 
@@ -842,10 +879,10 @@
 ----
 
 [[get-child-project]]
-Get Child Project
-~~~~~~~~~~~~~~~~~
-[verse]
+=== Get Child Project
+--
 'GET /projects/link:#project-name[\{project-name\}]/children/link:#project-name[\{project-name\}]'
+--
 
 Retrieves a child project. If a non-direct child project should be
 retrieved the parameter `recursive` must be set.
@@ -875,14 +912,13 @@
 ----
 
 [[dashboard-endpoints]]
-Dashboard Endpoints
--------------------
+== Dashboard Endpoints
 
 [[list-dashboards]]
-List Dashboards
-~~~~~~~~~~~~~~~
-[verse]
+=== List Dashboards
+--
 'GET /projects/link:#project-name[\{project-name\}]/dashboards/'
+--
 
 List custom dashboards for a project.
 
@@ -933,10 +969,10 @@
 ****
 
 [[get-dashboard]]
-Get Dashboard
-~~~~~~~~~~~~~
-[verse]
+=== Get Dashboard
+--
 'GET /projects/link:#project-name[\{project-name\}]/dashboards/link:#dashboard-id[\{dashboard-id\}]'
+--
 
 Retrieves a project dashboard. The dashboard can be defined on that
 project or be inherited from a parent project.
@@ -1016,10 +1052,10 @@
 ----
 
 [[set-dashboard]]
-Set Dashboard
-~~~~~~~~~~~~~
-[verse]
+=== Set Dashboard
+--
 'PUT /projects/link:#project-name[\{project-name\}]/dashboards/link:#dashboard-id[\{dashboard-id\}]'
+--
 
 Updates/Creates a project dashboard.
 
@@ -1072,10 +1108,10 @@
 ----
 
 [[delete-dashboard]]
-Delete Dashboard
-~~~~~~~~~~~~~~~~
-[verse]
+=== Delete Dashboard
+--
 'DELETE /projects/link:#project-name[\{project-name\}]/dashboards/link:#dashboard-id[\{dashboard-id\}]'
+--
 
 Deletes a project dashboard.
 
@@ -1099,36 +1135,30 @@
 
 
 [[ids]]
-IDs
----
+== IDs
 
 [[branch-id]]
-\{branch-id\}
-~~~~~~~~~~~~~
+=== \{branch-id\}
 The name of a branch or `HEAD`. The prefix `refs/heads/` can be
 omitted.
 
 [[dashboard-id]]
-\{dashboard-id\}
-~~~~~~~~~~~~~~~~
+=== \{dashboard-id\}
 The ID of a dashboard in the format '<ref>:<path>'.
 
 A special dashboard ID is `default` which represents the default
 dashboard of a project.
 
 [[project-name]]
-\{project-name\}
-~~~~~~~~~~~~~~~~
+=== \{project-name\}
 The name of the project.
 
 
 [[json-entities]]
-JSON Entities
--------------
+== JSON Entities
 
 [[branch-info]]
-BranchInfo
-~~~~~~~~~~
+=== BranchInfo
 The `BranchInfo` entity contains information about a branch.
 
 [options="header",width="50%",cols="1,^2,4"]
@@ -1141,8 +1171,7 @@
 |=========================
 
 [[branch-input]]
-BranchInput
-~~~~~~~~~~~
+=== BranchInput
 The `BranchInput` entity contains information for the creation of
 a new branch.
 
@@ -1159,8 +1188,7 @@
 |=======================
 
 [[config-info]]
-ConfigInfo
-~~~~~~~~~~
+=== ConfigInfo
 The `ConfigInfo` entity contains information about the effective project
 configuration.
 
@@ -1208,6 +1236,10 @@
 |`theme`                     |optional|
 The theme that is configured for the project as a link:#theme-info[
 ThemeInfo] entity.
+|`plugin_config`             |optional|
+Plugin configuration as map which maps the plugin name to a map of
+parameter names to link:#config-parameter-info[ConfigParameterInfo]
+entities.
 |`actions`                   |optional|
 Actions the caller might be able to perform on this project. The
 information is a map of view names to
@@ -1215,8 +1247,7 @@
 |=========================================
 
 [[config-input]]
-ConfigInput
-~~~~~~~~~~~
+=== ConfigInput
 The `ConfigInput` entity describes a new project configuration.
 
 [options="header",width="50%",cols="1,^2,4"]
@@ -1263,11 +1294,53 @@
 The state of the project, can be `ACTIVE`, `READ_ONLY` or `HIDDEN`. +
 Not set if the project state is `ACTIVE`. +
 If not set, the project state is not updated.
+|`plugin_config_values`      |optional|
+Plugin configuration values as map which maps the plugin name to a map
+of parameter names to values.
 |=========================================
 
+[[config-parameter-info]]
+ConfigParameterInfo
+~~~~~~~~~~~~~~~~~~~
+The `ConfigParameterInfo` entity describes a project configuration
+parameter.
+
+[options="header",width="50%",cols="1,^2,4"]
+|===============================
+|Field Name        ||Description
+|`display_name`    |optional|
+The display name of the configuration parameter.
+|`description`     |optional|
+The description of the configuration parameter.
+|`warning`         |optional|
+Warning message for the configuration parameter.
+|`type`            ||
+The type of the configuration parameter. Can be `STRING`, `INT`,
+`LONG`, `BOOLEAN`, `LIST` or `ARRAY`.
+|`value`           |optional|
+The value of the configuration parameter as string. If the parameter
+is inheritable this is the effective value which is deduced from
+`configured_value` and `inherited_value`.
+|`values`          |optional|
+The list of values. Only set if the `type` is `ARRAY`.
+`editable`         |`false` if not set|
+Whether the value is editable.
+|`permitted_values`|optional|
+The list of permitted values. Only set if the `type` is `LIST`.
+|`inheritable`     |`false` if not set|
+Whether the configuration parameter can be inherited.
+|`configured_value`|optional|
+The value of the configuration parameter that is configured on this
+project, only set if `inheritable` is true.
+|`inherited_value` |optional|
+The inherited value of the configuration parameter, only set if
+`inheritable` is true.
+|`permitted_values` |optional|
+The list of permitted values, only set if the `type` is `LIST`.
+|===============================
+
 [[dashboard-info]]
-DashboardInfo
-~~~~~~~~~~~~~
+=== DashboardInfo
 The `DashboardInfo` entity contains information about a project
 dashboard.
 
@@ -1305,8 +1378,7 @@
 |===============================
 
 [[dashboard-input]]
-DashboardInput
-~~~~~~~~~~~~~~
+=== DashboardInput
 The `DashboardInput` entity contains information to create/update a
 project dashboard.
 
@@ -1320,8 +1392,7 @@
 |=============================
 
 [[dashboard-section-info]]
-DashboardSectionInfo
-~~~~~~~~~~~~~~~~~~~~
+=== DashboardSectionInfo
 The `DashboardSectionInfo` entity contains information about a section
 in a dashboard.
 
@@ -1333,9 +1404,20 @@
 Tokens such as `${project}` are not resolved.
 |===========================
 
+[[gc-input]]
+=== GCInput
+The `GCInput` entity contains information to run the Git garbage
+collection.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=============================
+|Field Name      ||Description
+|`show_progress` |`false` if not set|
+Whether progress information should be shown.
+|=============================
+
 [[head-input]]
-HeadInput
-~~~~~~~~~
+=== HeadInput
 The `HeadInput` entity contains information for setting `HEAD` for a
 project.
 
@@ -1348,8 +1430,7 @@
 |============================
 
 [[inherited-boolean-info]]
-InheritedBooleanInfo
-~~~~~~~~~~~~~~~~~~~~
+=== InheritedBooleanInfo
 A boolean value that can also be inherited.
 
 [options="header",width="50%",cols="1,^2,4"]
@@ -1365,8 +1446,7 @@
 |================================
 
 [[max-object-size-limit-info]]
-MaxObjectSizeLimitInfo
-~~~~~~~~~~~~~~~~~~~~~~
+=== MaxObjectSizeLimitInfo
 The `MaxObjectSizeLimitInfo` entity contains information about the
 link:config-gerrit.html#receive.maxObjectSizeLimit[max object size
 limit] of a project.
@@ -1388,8 +1468,7 @@
 |===============================
 
 [[project-description-input]]
-ProjectDescriptionInput
-~~~~~~~~~~~~~~~~~~~~~~~
+=== ProjectDescriptionInput
 The `ProjectDescriptionInput` entity contains information for setting a
 project description.
 
@@ -1405,8 +1484,7 @@
 |=============================
 
 [[project-info]]
-ProjectInfo
-~~~~~~~~~~~
+=== ProjectInfo
 The `ProjectInfo` entity contains information about a project.
 
 [options="header",width="50%",cols="1,^2,4"]
@@ -1426,8 +1504,7 @@
 |===========================
 
 [[project-input]]
-ProjectInput
-~~~~~~~~~~~~
+=== ProjectInput
 The `ProjectInput` entity contains information for the creation of
 a new project.
 
@@ -1449,7 +1526,9 @@
 The submit type that should be set for the project
 (`MERGE_IF_NECESSARY`, `REBASE_IF_NECESSARY`, `FAST_FORWARD_ONLY`,
 `MERGE_ALWAYS`, `CHERRY_PICK`). +
-If not set, `MERGE_IF_NECESSARY` is set as submit type.
+If not set, `MERGE_IF_NECESSARY` is set as submit type unless
+link:config-gerrit.html#repository.name.defaultSubmitType[
+repository.<name>.defaultSubmitType] is set to a different value.
 |`branches`                  |optional|
 A list of branches that should be initially created. +
 For the branch names the `refs/heads/` prefix can be omitted.
@@ -1476,11 +1555,13 @@
 |`max_object_size_limit`     |optional|
 Max allowed Git object size for this project.
 Common unit suffixes of 'k', 'm', or 'g' are supported.
+|`plugin_config_values`      |optional|
+Plugin configuration values as map which maps the plugin name to a map
+of parameter names to values.
 |=========================================
 
 [[project-parent-input]]
-ProjectParentInput
-~~~~~~~~~~~~~~~~~~
+=== ProjectParentInput
 The `ProjectParentInput` entity contains information for setting a
 project parent.
 
@@ -1494,8 +1575,7 @@
 |=============================
 
 [[repository-statistics-info]]
-RepositoryStatisticsInfo
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== RepositoryStatisticsInfo
 The `RepositoryStatisticsInfo` entity contains information about
 statistics of a Git repository.
 
@@ -1512,8 +1592,7 @@
 |======================================
 
 [[theme-info]]
-ThemeInfo
-~~~~~~~~~
+=== ThemeInfo
 The `ThemeInfo` entity describes a theme.
 
 [options="header",width="50%",cols="1,^2,4"]
@@ -1531,3 +1610,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index 7eed6ef..6c681e5 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - REST API
-=============================
+= Gerrit Code Review - REST API
 
 Gerrit Code Review comes with a REST like API available over HTTP.
 The API is suitable for automated tools to build upon, as well as
@@ -7,8 +6,7 @@
 
 See also: link:dev-rest-api.html[REST API Developers' Notes].
 
-Endpoints
----------
+== Endpoints
 link:rest-api-access.html[/access/]::
   Access Right related REST endpoints
 link:rest-api-accounts.html[/accounts/]::
@@ -23,13 +21,13 @@
   Plugin related REST endpoints
 link:rest-api-projects.html[/projects/]::
   Project related REST endpoints
+link:rest-api-documentation.html[/Documentation/]::
+  Documentation related REST endpoints
 
-Protocol Details
-----------------
+== Protocol Details
 
 [[authentication]]
-Authentication
-~~~~~~~~~~~~~~
+=== Authentication
 By default all REST endpoints assume anonymous access and filter
 results to correspond to what anonymous users can read (which may
 be nothing at all).
@@ -41,16 +39,14 @@
 `/projects/` request URL `/a/projects/`.
 
 [[preconditions]]
-Preconditions
-~~~~~~~~~~~~~
+=== Preconditions
 Clients can request PUT to create a new resource and not overwrite
 an existing one by adding `If-None-Match: *` to the request HTTP
 headers. If the named resource already exists the server will respond
 with HTTP 412 Precondition Failed.
 
 [[output]]
-Output Format
-~~~~~~~~~~~~~
+=== Output Format
 Most APIs return pretty printed JSON by default. Compact JSON can be
 requested by setting the `Accept` HTTP request header to include
 `application/json`, for example:
@@ -83,29 +79,25 @@
 save on network transfer time for larger responses.
 
 [[timestamp]]
-Timestamp
-~~~~~~~~~
+=== Timestamp
 Timestamps are given in UTC and have the format
 "'yyyy-mm-dd hh:mm:ss.fffffffff'" where "'ffffffffff'" indicates the
 nanoseconds.
 
 [[encoding]]
-Encoding
-~~~~~~~~
+=== Encoding
 All IDs that appear in the URL of a REST call (e.g. project name, group name)
 must be URL encoded.
 
 [[response-codes]]
-Response Codes
-~~~~~~~~~~~~~~
+=== Response Codes
 HTTP status codes are well defined and the Gerrit REST endpoints use
 them as described in the HTTP spec.
 
 Here are examples for some HTTP status codes that show how they are
 used in the context of the Gerrit REST API.
 
-400 Bad Request
-^^^^^^^^^^^^^^^
+==== 400 Bad Request
 `400 Bad Request` is used if the request is not understood by the
 server due to malformed syntax.
 
@@ -116,8 +108,7 @@
 `400 Bad Request` is also used if required input fields are not set or
 if options are set which cannot be used together.
 
-403 Forbidden
-^^^^^^^^^^^^^
+==== 403 Forbidden
 `403 Forbidden` is used if the operation is not allowed because the
 calling user has no sufficient permissions.
 
@@ -128,14 +119,12 @@
 `403 Forbidden` is also used if `self` is used as account ID and the
 REST call was done without authentication.
 
-404 Not Found
-^^^^^^^^^^^^^
+==== 404 Not Found
 `404 Not Found` is returned if the resource that is specified by the
 URL is not found or is not visible to the calling user. A resource
 cannot be found if the URL contains a non-existing ID or view.
 
-405 Method Not Allowed
-^^^^^^^^^^^^^^^^^^^^^^
+==== 405 Method Not Allowed
 `405 Method Not Allowed` is used if the resource exists but doesn't
 support the operation.
 
@@ -143,8 +132,7 @@
 internal groups, if they are invoked for an external group the response
 is `405 Method Not Allowed`.
 
-409 Conflict
-^^^^^^^^^^^^
+==== 409 Conflict
 `409 Conflict` is used if the request cannot be completed because the
 current state of the resource doesn't allow the operation.
 
@@ -155,17 +143,18 @@
 `409 Conflict` is also used if you try to create a resource but the
 name is already occupied by an existing resource.
 
-412 Precondition Failed
-^^^^^^^^^^^^^^^^^^^^^^^
+==== 412 Precondition Failed
 `412 Precondition Failed` is used if a precondition from the request
 header fields is not fulfilled as described in the link:#preconditions[
 Preconditions] section.
 
-422 Unprocessable Entity
-^^^^^^^^^^^^^^^^^^^^^^^^
+==== 422 Unprocessable Entity
 `422 Unprocessable Entity` is returned if the ID of a resource that is
 specified in the request body cannot be resolved.
 
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-changeid.txt b/Documentation/user-changeid.txt
index c13faa6..fff14b4 100644
--- a/Documentation/user-changeid.txt
+++ b/Documentation/user-changeid.txt
@@ -1,14 +1,14 @@
-Gerrit Code Review - Change-Ids
-===============================
+= Gerrit Code Review - Change-Ids
 
-Description
------------
+== Description
 
-Gerrit Code Review sometimes relies upon a Change-Id line at the
-bottom of a commit message to uniquely identify a change across all
-drafts of it.  By including a unique Change-Id in the commit message,
-Gerrit can automatically associate a new version of a change back
-to its original review, even across cherry-picks and rebases.
+Gerrit needs to identify commits that belong to the same review. For
+instance, when a change needs to be modified, a second commit can be uploaded
+to address the reported issues. Gerrit allows attaching those 2 commits
+to the same change, and relies upon a Change-Id line at the bottom of a
+commit message to do so. With this Change-Id, Gerrit can automatically
+associate a new version of a change back to its original review, even
+across cherry-picks and rebases.
 
 To be picked up by Gerrit, a Change-Id line must be in the footer
 (last paragraph) of a commit message, and may be mixed
@@ -33,55 +33,65 @@
 ----
 
 In the above example, `Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b`
-is the unique identity assigned to this change.  It does not match
-the commit name, `29a6...`, as the change may have been amended or
-rebased to address reviewer comments since its initial inception.
+is the identity assigned to this change. It is independent of the
+commit id. To avoid confusion with commit ids, Change-Ids are typically
+prefixed with an uppercase `I`.
 
-To avoid confusion with commit names, Change-Ids are typically prefixed with
-an uppercase `I`.
+Note that a Change-Id is not necessarily unique within a Gerrit instance. It can
+be reused among different repositories or branches (see below,
+link:user-changeid.html[change-upload]).
 
 [[creation]]
-Creation
---------
+== Creation
 
-Gerrit Code Review provides a standard 'commit-msg' hook which
+Change-Ids are created at commit time on the client side.
+A standard 'commit-msg' hook is provided by Gerrit, and
 can be installed in the local Git repository to automatically
-create and insert a unique Change-Id line during `git commit`.
+generate and insert a Change-Id line during `git commit`, when
+none is defined yet.
+
 To install the hook, copy it from Gerrit's daemon by executing
 one of the following commands while being in the root directory
 of the local Git repository:
 
-  $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
-
   $ curl -Lo .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
 
+  $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
+
 Then ensure that the execute bit is set on the hook script:
 
   $ chmod u+x .git/hooks/commit-msg
 
 For more details, see link:cmd-hook-commit-msg.html[commit-msg].
 
+[[change-upload]]
 Change Upload
 --------------
 
-During upload by pushing to a `refs/for/*` or `refs/heads/*`
-branch, Gerrit will use the Change-Id line to:
+During upload by pushing to `refs/for/*`, `refs/drafts/*` or
+`refs/heads/*`, Gerrit will try to find an existing review the
+uploaded commit relates to. For an existing review to match, the
+following properties have to match:
+
+* Change-Id
+* Repository name
+* Branch name
+
+The following applies in the different scenarios:
 
 * Create a new change
 +
-If this is the first time it has seen the Change-Id mentioned in
-the commit message, Gerrit will create a new change for review.
+If no matching review is found, Gerrit will create a new change for review.
 
 * Update an existing change
 +
-If Gerrit has seen this Change-Id before, but has not yet seen this
-new commit object, Gerrit will add the new commit as a new patch
+If a matching review is found, Gerrit will add the new commit as a new patch
 set on the existing change.
 
 * Close an existing change
 +
-If Gerrit has seen this Change-Id before, and the commit is being
-pushed directly into a branch, the existing change is updated with
+If a matching review is found, and the commit is being
+pushed directly to refs/heads/*, the existing change is updated with
 the new commit, and the change is closed and marked as merged.
 
 If a Change-Id line is not present in the commit message, Gerrit will
@@ -89,15 +99,22 @@
 This line can be manually copied and inserted into an updated commit
 message if additional revisions to a change are required.
 
+By default, Gerrit will prevent pushing for review if no Change-Id is provided,
+with the following message:
+
+  ! [remote rejected] HEAD -> refs/publish/master (missing Change-Id in commit
+  message footer)
+
+However, repositories can be configured to allow commits without Change-Ids
+in the commit message by setting "Require Change-Id in commit message" to "FALSE".
+
 For more details on using git push to upload changes to Gerrit,
 see link:user-upload.html#push_create[creating changes by git push].
 
-Git Tasks
----------
+== Git Tasks
 
 [[new]]
-Creating a new commit
-~~~~~~~~~~~~~~~~~~~~~
+=== Creating a new commit
 
 When creating a new commit, ensure the 'commit-msg' hook has been
 installed in your repository (see above), and don't put a Change-Id
@@ -107,24 +124,21 @@
 commit is complete by executing `git show`.
 
 [[amend]]
-Amending a commit
-~~~~~~~~~~~~~~~~~
+=== Amending a commit
 
 When amending a commit with `git commit --amend`, leave the
 Change-Id line unmodified in the commit message.  This will allow
 Gerrit to automatically update the change with the amended commit.
 
 [[rebase]]
-Rebasing a commit
-~~~~~~~~~~~~~~~~~
+=== Rebasing a commit
 
 When rebasing a commit, leave the Change-Id line unmodified in the
 commit message.  This will allow Gerrit to automatically update
 the change with the rebased commit.
 
 [[squash]]
-Squashing commits
-~~~~~~~~~~~~~~~~~
+=== Squashing commits
 
 When squashing several commits together, try to preserve only one
 Change-Id line, and remove the others from the commit message.
@@ -139,8 +153,7 @@
 other changes through the web interface.
 
 [[cherry-pick]]
-Cherry-picking a commit
-~~~~~~~~~~~~~~~~~~~~~~~
+=== Cherry-picking a commit
 
 When cherry-picking a commit, leave the Change-Id line alone to
 have Gerrit treat the cherry-picked commit as a replacement for
@@ -156,8 +169,7 @@
 release branch.
 
 [[update-old]]
-Updating an old commit
-~~~~~~~~~~~~~~~~~~~~~~
+=== Updating an old commit
 
 If a commit was created before the availability of Change-Id support,
 or was created in a Git repository that was missing the 'commit-msg'
@@ -169,3 +181,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-dashboards.txt b/Documentation/user-dashboards.txt
index 0945153..52916b9 100644
--- a/Documentation/user-dashboards.txt
+++ b/Documentation/user-dashboards.txt
@@ -1,8 +1,6 @@
-Gerrit Code Review - Dashboards
-===============================
+= Gerrit Code Review - Dashboards
 
-Custom Dashboards
------------------
+== Custom Dashboards
 
 A custom dashboard is shown in a layout similar to the per-user
 dashboard, but the sections are entirely configured from the URL.
@@ -57,8 +55,7 @@
 
 
 [[project-dashboards]]
-Project Dashboards
-------------------
+== Project Dashboards
 
 It is possible to share custom dashboards at a project level. To do
 this define the dashboards in a `refs/meta/dashboards/*` branch of the
@@ -91,8 +88,7 @@
 it easy to define common dashboards for every project by simply
 defining project dashboards on the All-Projects project.
 
-Token `${project}`
-~~~~~~~~~~~~~~~~~~
+=== Token `${project}`
 
 Project dashboard queries may contain the special `${project}` token
 which will be replaced with the name of the project to which the
@@ -106,8 +102,7 @@
 dashboard title] and in the link:#dashboard.description[dashboard
 description].
 
-Section `dashboard`
-~~~~~~~~~~~~~~~~~~~
+=== Section `dashboard`
 
 [[dashboard.title]]dashboard.title::
 +
@@ -134,8 +129,7 @@
 ----
 
 
-Section `section`
-~~~~~~~~~~~~~~~~~
+=== Section `section`
 
 section.<name>.query::
 +
@@ -143,8 +137,7 @@
 given name.
 
 [[project-default-dashboard]]
-Project Default Dashboard
--------------------------
+== Project Default Dashboard
 
 It is possible to define a default dashboard for a project in the
 projects `project.config` file in the `refs/meta/config` branch:
@@ -169,3 +162,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-notify.txt b/Documentation/user-notify.txt
index 558dcb5..2f8c162 100644
--- a/Documentation/user-notify.txt
+++ b/Documentation/user-notify.txt
@@ -1,15 +1,12 @@
-Gerrit Code Review - Email Notifications
-========================================
+= Gerrit Code Review - Email Notifications
 
-Description
------------
+== Description
 
 Gerrit can automatically notify users by email when new changes are
 uploaded for review, after comments have been posted on a change,
 or after the change has been submitted to a branch.
 
-User Level Settings
--------------------
+== User Level Settings
 
 Individual users can configure email subscriptions by editing
 watched projects through Settings > Watched Projects with the web UI.
@@ -30,8 +27,7 @@
 Write' option in the user preferences.
 
 
-Project Level Settings
-----------------------
+== Project Level Settings
 
 Project owners and site administrators can configure project level
 notifications, enabling Gerrit Code Review to automatically send
@@ -145,3 +141,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index de0a3f7..b995bc8 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -1,8 +1,6 @@
-Gerrit Code Review - Searching Changes
-======================================
+= Gerrit Code Review - Searching Changes
 
-Default Searches
-----------------
+== Default Searches
 
 Most basic searches can be viewed by clicking on a link along the top
 menu bar.  The link will prefill the search box with a common search
@@ -24,8 +22,7 @@
 |=================================================
 
 
-Basic Change Search
--------------------
+== Basic Change Search
 
 Similar to many popular search engines on the web, just enter some
 text and let Gerrit figure out the meaning:
@@ -41,8 +38,7 @@
 |=============================================================
 
 
-Search Operators
-----------------
+== Search Operators
 
 Operators act as restrictions on the search.  As more operators
 are added to the same query string, they further restrict the
@@ -68,14 +64,35 @@
 * mon, month, months (`1 month` is treated as `30 days`)
 * y, year, years (`1 year` is treated as `365 days`)
 
+[[before_until]]
+before:'TIME'/until:'TIME'::
++
+Changes modified before the given 'TIME', inclusive. Must be in the
+format `2006-01-02[ 15:04:05[.890][ -0700]]`; omitting the time defaults
+to 00:00:00 and omitting the timezone defaults to UTC.
+
+[[after_since]]
+after:'TIME'/since:'TIME'::
++
+Changes modified after the given 'TIME', inclusive. Must be in the
+format `2006-01-02[ 15:04:05[.890][ -0700]]`; omitting the time defaults
+to 00:00:00 and omitting the timezone defaults to UTC.
+
 [[change]]
 change:'ID'::
 +
 Either a legacy numerical 'ID' such as 15183, or a newer style
 Change-Id that was scraped out of the commit message.
 
+[[conflicts]]
+conflicts:'ID'::
++
+Changes that conflict with change 'ID'. Change 'ID' can be specified
+as a legacy numerical 'ID' such as 15183, or a newer style Change-Id
+that was scraped out of the commit message.
+
 [[owner]]
-owner:'USER'::
+owner:'USER', o:'USER'::
 +
 Changes originally submitted by 'USER'. The special case of
 `owner:self` will find changes owned by the caller.
@@ -86,7 +103,7 @@
 Changes originally submitted by a user in 'GROUP'.
 
 [[reviewer]]
-reviewer:'USER'::
+reviewer:'USER', r:'USER'::
 +
 Changes that have been, or need to be, reviewed by 'USER'. The
 special case of `reviewer:self` will find changes where the caller
@@ -103,13 +120,19 @@
 Changes where 'SHA1' is one of the patch sets of the change.
 
 [[project]]
-project:'PROJECT'::
+project:'PROJECT', p:'PROJECT'::
 +
 Changes occurring in 'PROJECT'. If 'PROJECT' starts with `^` it
 matches project names by regular expression.  The
 link:http://www.brics.dk/automaton/[dk.brics.automaton
 library] is used for evaluation of such patterns.
 
+[[parentproject]]
+parentproject:'PROJECT'::
++
+Changes occurring in 'PROJECT' or in one of the child projects of
+'PROJECT'.
+
 [[branch]]
 branch:'BRANCH'::
 +
@@ -173,14 +196,14 @@
 +
 Changes that match 'TEXT' string in any comment left by a reviewer.
 
-[[file]]
-file:^'REGEX'::
+[[path]]
+path:'PATH'::
 +
-Matches any change where REGEX matches a file that was affected
-by the change.  The regular expression pattern must start with
-`^`.  For example, to match all XML files use `file:^.*\.xml$`.
-The link:http://www.brics.dk/automaton/[dk.brics.automaton
-library] is used for the evaluation of such patterns.
+Matches any change touching file at 'PATH'. By default exact path
+matching is used, but regular expressions can be enabled by starting
+with `^`.  For example, to match all XML files use `file:^.*\.xml$`.
+The link:http://www.brics.dk/automaton/[dk.brics.automaton library]
+is used for the evaluation of such patterns.
 +
 The `^` required at the beginning of the regular expression not only
 denotes a regular expression, but it also has the usual meaning of
@@ -193,6 +216,18 @@
 files named like 'name1.xml', 'name2.xml', and 'name3.xml' use
 `file:"^name[1-3].xml"`.
 
+[[file]]
+file:'NAME', f:'NAME'::
++
+Matches any change touching a file containing the path component
+'NAME'.  For example a `file:src` will match changes that modify
+files named `gerrit-server/src/main/java/Foo.java`. Name matching
+is exact match, `file:Foo.java` finds any change touching a file
+named exactly `Foo.java` and does not match `AbstractFoo.java`.
++
+Regular expression matching can be enabled by starting the string
+with `^`. In this mode `file:` is an alias of `path:` (see above).
+
 [[has]]
 has:draft::
 +
@@ -245,6 +280,11 @@
 +
 Same as <<status,status:'STATE'>>.
 
+is:mergeable::
++
+True if the change has no merge conflicts and could be merged into its
+destination branch.
+
 [[status]]
 status:open::
 +
@@ -273,8 +313,7 @@
 Change has been abandoned.
 
 
-Argument Quoting
-----------------
+== Argument Quoting
 
 Operator values that are not bare words (roughly A-Z, a-z, 0-9, @,
 hyphen, dot and underscore) must be quoted for the query parser.
@@ -284,8 +323,7 @@
 curly braces (e.g. `message:{the value}`).
 
 
-Boolean Operators
------------------
+== Boolean Operators
 
 Unless otherwise specified, operators are joined using the `AND`
 boolean operator, thereby restricting the search results.
@@ -293,30 +331,26 @@
 Parentheses can be used to force a particular precedence on complex
 operator expressions, otherwise OR has higher precedence than AND.
 
-Negation
-~~~~~~~~
+=== Negation
 Any operator can be negated by prefixing it with `-`, for example
 `-is:starred` is the exact opposite of `is:starred` and will
 therefore return changes that are *not* starred by the current user.
 
 The operator `NOT` (in all caps) is a synonym.
 
-AND
-~~~
+=== AND
 The boolean operator `AND` (in all caps) can be used to join two
 other operators together.  This results in a restriction of the
 results, returning only changes that match both operators.
 
-OR
-~~
+=== OR
 The boolean operator `OR` (in all caps) can be used to find changes
 that match either operator.  This increases the number of results
 that are returned, as more changes are considered.
 
 
 [[labels]]
-Labels
-------
+== Labels
 Label operators can be used to match approval scores given during
 a code review.  The specific set of supported labels depends on
 the server configuration, however the `Code-Review` label is provided
@@ -384,8 +418,7 @@
 Changes that are blocked from submission due to a blocking score.
 
 
-Magical Operators
------------------
+== Magical Operators
 
 Most of these operators exist to support features of Gerrit Code
 Review, and are not meant to be accessed by the average end-user.
@@ -436,21 +469,9 @@
 preferences.  Including it in a web query may lead to unpredictable
 results with regards to pagination.
 
-resume_sortkey:'KEY'::
-+
-Positions the low level scan routine to start from 'KEY' and
-continue through changes from this point.  This is most often used
-for paginating result sets.  Including this in a web query may lead
-to unpredictable results.
-
-sortkey_after:'KEY', sortkey_before:'KEY'::
-+
-Restart the low level scan routine from 'KEY'.  This is automatically
-set by the pagination system as the user navigates through results
-of a query.  Including either value in a web query may lead to
-unpredictable results.
-
-
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-signedoffby.txt b/Documentation/user-signedoffby.txt
index cc9a4b1..507f4f2 100644
--- a/Documentation/user-signedoffby.txt
+++ b/Documentation/user-signedoffby.txt
@@ -1,13 +1,11 @@
-Gerrit Code Review - Signed-off-by Lines
-=========================================
+= Gerrit Code Review - Signed-off-by Lines
 
 [NOTE]
 This document was literally taken from link:http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=Documentation/SubmittingPatches;hb=4e8a2372f9255a1464ef488ed925455f53fbdaa1[linux-2.6 Documentation/SubmittingPatches]
 and is covered by the GPLv2.
 
 [[Signed-off-by]]
-Signed-off-by:
---------------
+== Signed-off-by:
 
 To improve tracking of who did what, especially with patches that can
 percolate to their final resting place in the kernel through several
@@ -86,8 +84,7 @@
 
 [[Acked-by]]
 [[Cc]]
-Acked-by:, Cc:
---------------
+== Acked-by:, Cc:
 
 The Signed-off-by: tag indicates that the signer was involved in the
 development of the patch, or that he/she was in the patch's delivery path.
@@ -121,8 +118,7 @@
 [[Reported-by]]
 [[Tested-by]]
 [[Reviewed-by]]
-Reported-by:, Tested-by: and Reviewed-by:
------------------------------------------
+== Reported-by:, Tested-by: and Reviewed-by:
 
 If this patch fixes a problem reported by somebody else, consider adding a
 Reported-by: tag to credit the reporter for their contribution.  Please
@@ -175,3 +171,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-submodules.txt b/Documentation/user-submodules.txt
index 6aab43f..8411595 100644
--- a/Documentation/user-submodules.txt
+++ b/Documentation/user-submodules.txt
@@ -1,8 +1,6 @@
-Gerrit Code Review - Superproject subscription to submodules updates
-====================================================================
+= Gerrit Code Review - Superproject subscription to submodules updates
 
-Description
------------
+== Description
 
 Gerrit supports a custom git superproject feature for tracking submodules.
 This feature is useful for automatic updates on superprojects whenever
@@ -22,8 +20,7 @@
 automatically updates the subscribers to the submodule with a new
 commit having the updated gitlinks.
 
-Git Submodules Overview
------------------------
+== Git Submodules Overview
 
 Submodules are a git feature that allows an external repository to be
 attached inside a repository at a specific path. The objective here
@@ -57,11 +54,9 @@
 modify its content, commit, then move back to 'super' and
 commit the modified gitlink for 'a'.
 
-Creating a New Subscription
----------------------------
+== Creating a New Subscription
 
-Defining the Submodule Branch
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Defining the Submodule Branch
 
 This is required because submodule subscription is actually the
 subscription of a submodule project and one of its branches for
@@ -95,16 +90,14 @@
 .gitmodules file, Gerrit will not create a subscription for the
 submodule and there will be no automatic updates to the superproject.
 
-Detecting and Subscribing Submodules
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Detecting and Subscribing Submodules
 
 Whenever a commit is merged to a project, its content is scanned
 to identify if it registers any submodules (if the commit contains new
 gitlinks and a .gitmodules file with all required info) and if so,
 a new submodule subscription is registered.
 
-Automatic Update of Superprojects
----------------------------------
+== Automatic Update of Superprojects
 
 After a superproject is subscribed to a submodule, it is not
 required to push/merge commits to this superproject to update the
@@ -119,8 +112,7 @@
 creates a new commit on branch 'dev' of 'super' updating the gitlink
 to point to the just merged commit.
 
-Subscription Limitations
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== Subscription Limitations
 
 Gerrit will only automatically update superprojects where the
 submodules are hosted on the same Gerrit instance as the
@@ -137,8 +129,7 @@
 should certify to use the correct hostname of the running Gerrit
 instance to add/subscribe submodules.
 
-Relative submodules
-~~~~~~~~~~~~~~~~~~~
+=== Relative submodules
 
 To enable easier usage of Gerrit mirrors and/or distribution over
 several protocols, such as plain git and HTTP(S) as well as SSH, one
@@ -176,8 +167,7 @@
   url = ../framework/subcomponent.git
   branch = master
 
-Removing Subscriptions
-----------------------
+== Removing Subscriptions
 
 If one has added a submodule subscription and drops it, it is
 required to merge a commit updating the subscribed super
@@ -187,3 +177,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index cbb152c3..17ca968 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -1,5 +1,4 @@
-Gerrit Code Review - Uploading Changes
-======================================
+= Gerrit Code Review - Uploading Changes
 
 Gerrit supports three methods of uploading changes:
 
@@ -14,8 +13,7 @@
 public key, and HTTP/HTTPS.
 
 [[http]]
-HTTP/HTTPS
-----------
+== HTTP/HTTPS
 
 On Gerrit installations that do not support SSH authentication, the
 user must authenticate via HTTP/HTTPS.
@@ -33,8 +31,7 @@
 and then following the site-specific instructions.  On sites where this URL is
 not configured, the password can be obtained by clicking on `Generate Password`.
 
-SSH
----
+== SSH
 
 Each user uploading changes to Gerrit must configure one or more SSH
 public keys.  The per-user SSH key list can be accessed over the web
@@ -42,8 +39,7 @@
 tab.
 
 [[configure_ssh]]
-Configuration
-~~~~~~~~~~~~~
+=== Configuration
 
 To register a new SSH key for use with Gerrit, paste the contents of
 your `id_rsa.pub` or `id_dsa.pub` file into the text box and click
@@ -76,8 +72,7 @@
 process and how to add the private key.
 
 [[test_ssh]]
-Testing Connections
-~~~~~~~~~~~~~~~~~~~
+=== Testing Connections
 
 To verify your SSH key is working correctly, try using an SSH client
 to connect to Gerrit's SSHD port.  By default Gerrit runs on
@@ -120,12 +115,10 @@
 or `NOT_AVAILABLE` if the SSHD server is not currently running.
 
 
-git push
---------
+== git push
 
 [[push_create]]
-Create Changes
-~~~~~~~~~~~~~~
+=== Create Changes
 
 To create new changes for review, simply push to the project's
 magical `refs/for/'branch'` ref using any Git client tool:
@@ -207,8 +200,7 @@
 
 
 [[push_replace]]
-Replace Changes
-~~~~~~~~~~~~~~~
+=== Replace Changes
 
 To add an additional patch set to a change, ensure Change-Id
 lines were created in the original commit messages, and just use
@@ -226,8 +218,7 @@
 For more about Change-Ids, see link:user-changeid.html[Change-Id Lines].
 
 [[manual_replacement_mapping]]
-Manual Replacement Mapping
-^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Manual Replacement Mapping
 
 .Deprecation Warning
 ****
@@ -286,8 +277,7 @@
 
 
 [[bypass_review]]
-Bypass Review
-~~~~~~~~~~~~~
+=== Bypass Review
 
 Changes (and annotated tags) can be pushed directly into a
 repository, bypassing the review process.  This is primarily useful
@@ -334,8 +324,7 @@
 
 
 [[auto_merge]]
-Auto-Merge during Push
-~~~~~~~~~~~~~~~~~~~~~~
+=== Auto-Merge during Push
 
 Changes can be directly submitted on push.  This is primarily useful
 for teams that don't want to do code review but want to use Gerrit's
@@ -354,8 +343,7 @@
 
 
 [[base]]
-Selecting Merge Base
-~~~~~~~~~~~~~~~~~~~~
+=== Selecting Merge Base
 
 By default new changes are opened only for new unique commits
 that have never before been seen by the Gerrit server. Clients
@@ -366,17 +354,24 @@
   git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/master%base=$(git rev-parse origin/master)
 ====
 
+It is also possible to specify more than one '%base' argument.
+This may be useful when pushing a merge commit. Note that the '%'
+character has only to be provided once, for the first '%base'
+argument:
 
-repo upload
------------
+====
+  git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/master%base=commit-id1,base=commit-id2
+====
+
+
+== repo upload
 
 repo is a multiple repository management tool, most commonly
 used by the Android Open Source Project.  For more details, see
 link:http://source.android.com/source/using-repo.html[using repo].
 
 [[repo_create]]
-Create Changes
-~~~~~~~~~~~~~~
+=== Create Changes
 
 To upload changes to a project using `repo`, ensure the manifest's
 review field has been configured to point to the Gerrit server.
@@ -394,8 +389,7 @@
 For more details on using `repo upload`, see `repo help upload`.
 
 [[repo_replace]]
-Replace Changes
-~~~~~~~~~~~~~~~
+=== Replace Changes
 
 To replace changes, ensure Change-Id lines were created in the
 commit messages, and just use `repo upload`.
@@ -413,8 +407,7 @@
 For more about Change-Ids, see link:user-changeid.html[Change-Id Lines].
 
 
-Gritty Details
---------------
+== Gritty Details
 
 As Gerrit implements the entire SSH and Git server stack within its
 own process space, Gerrit maintains complete control over how the
@@ -445,3 +438,6 @@
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/ReleaseNotes/ReleaseNotes-2.9.txt b/ReleaseNotes/ReleaseNotes-2.9.txt
new file mode 100644
index 0000000..216bdd3
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.9.txt
@@ -0,0 +1,156 @@
+Release notes for Gerrit 2.9
+============================
+
+
+Gerrit 2.9 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.9.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.9.war]
+
+*WARNING:* Support for Java 1.6 has been discontinued.
+As of Gerrit 2.9, Java 1.7 is required.
+
+Gerrit 2.9 includes the bug fixes done with
+link:ReleaseNotes-2.8.1.html[Gerrit 2.8.1],
+link:ReleaseNotes-2.8.2.html[Gerrit 2.8.2],
+link:ReleaseNotes-2.8.3.html[Gerrit 2.8.3], and
+link:ReleaseNotes-2.8.4.html[Gerrit 2.8.4].
+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
+  java -jar gerrit.war reindex --recheck-mergeable -d site_path
+----
+
+*WARNING:* Upgrading to 2.9.x requires the server be first upgraded to 2.1.7 (or
+a later 2.1.x version), and then to 2.9.x.  If you are upgrading from 2.2.x.x or
+later, you may ignore this warning and upgrade directly to 2.9.x.
+
+
+Release Highlights
+------------------
+
+
+* 'Gerrit Inspector' for interactive inspection and troubleshooting of a running
+Gerrit instance.
+
+
+New Features
+------------
+
+
+Secondary Index
+~~~~~~~~~~~~~~~
+
+
+* New `--recheck-mergeable` option on the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/pgm-reindex.html[
+`reindex` program].
+
+ssh
+~~~
+
+
+* New `--all-reviewers` option on the `query` command allowing query results
+to include information about all reviewers added on the change.
+
+* New link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/cmd-apropos.html[
+`apropos` command] to search the Gerrit documentation.
+
+* Deprecated `@CommandMetaData(descr)` has been discontinued. As of Gerrit 2.9
+  `@CommandMetaData(description)` annotation must be used.
+
+REST API
+~~~~~~~~
+
+
+Documentation
+^^^^^^^^^^^^^
+
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/rest-api-documentation#search-documentation.html[
+Search documentation].
+
+Daemon
+~~~~~~
+
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/dev-inspector.html[
+Gerrit Inspector]: interactive Jython shell.
++
+New `-s` option is added to the Daemon to start an interactive Jython shell for inspection and
+troubleshooting of live data of the Gerrit instance.
+
+Plugins
+~~~~~~~
+
+
+Issue Tracker System plugins
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+*WARNING:* There are new plugins for the integration with Bugzilla,
+Jira and IBM Rational Team Concert:
+
+* link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-bugzilla[plugins/its-bugzilla]
+* link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-jira[plugins/its-jira]
+* link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-rtc[plugins/its-rtc]
+
+The old plugins (`plugins/hooks-bugzilla`, `plugins/hooks-jira` and
+`plugins/hooks-rtc`) are deprecated with Gerrit 2.9.
+
+The new issue tracker system plugins have a common base which is
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/its-base[plugins/its-base].
+
+The configuration of the new plugins is slightly different than the
+configuration of the old plugins because they use different section
+names in the Gerrit configuration. For easy migration the new plugins
+have an init step that allows to take over the configuration from the
+old plugins during the Gerrit initialization phase.
+
+New Features:
+
+* The issue tracker integration can be enabled/disabled per project.
+* Parent projects can enforce the issue tracker integration for their
+  child projects.
+* It can be configured for which branches of a project the issue
+  tracker integration is enabled.
+* Whether the issue tracker integration is enabled/disabled for a
+  project can be changed from the ProjectInfoScreen in the Gerrit
+  WebUI.
+
+
+Bug Fixes
+---------
+
+
+Configuration
+~~~~~~~~~~~~~
+
+
+* The number of accounts shown on the 'Become Any Account' login
+screen is increased from 5 to 100.
+
+Upgrades
+--------
+
+* Update gwtjsonrpc to 1.4
+* Update gwtorm to 1.8
+
+Plugins
+-------
+
+Replication
+~~~~~~~~~~~
+
+* The default push refSpec for the replication plugin has changed from `forced`
+to `non-forced` push (was `+refs/*:refs/*` and now is `refs/*:refs/*`). This change
+should not impact typical replication topologies where the slaves are read-only
+and can be pushed by their masters only. If you wanted explicitly to overwrite
+all changes on the slaves, you need to add a `push=+refs/*:refs/*` configuration
+entry for each replication target.
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 94f6a92..df5a734 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -1,6 +1,11 @@
 Gerrit Code Review - Release Notes
 ==================================
 
+[[2_9]]
+Version 2.9.x
+-------------
+* link:ReleaseNotes-2.9.html[2.9]
+
 [[2_8]]
 Version 2.8.x
 -------------
diff --git a/VERSION b/VERSION
index b647a45..f0594c4 100644
--- a/VERSION
+++ b/VERSION
@@ -2,4 +2,5 @@
 # Used by :api_install and :api_deploy targets
 # when talking to the destination repository.
 #
-GERRIT_VERSION = '2.8.4'
+GERRIT_VERSION = '2.9-SNAPSHOT'
+
diff --git a/contrib/themes/spotify/static/background-gradient.png b/contrib/themes/spotify/static/background-gradient.png
index b40f35b..3b9422e 100644
--- a/contrib/themes/spotify/static/background-gradient.png
+++ b/contrib/themes/spotify/static/background-gradient.png
Binary files differ
diff --git a/contrib/themes/spotify/static/logo.png b/contrib/themes/spotify/static/logo.png
index bfe1fce..c231031 100644
--- a/contrib/themes/spotify/static/logo.png
+++ b/contrib/themes/spotify/static/logo.png
Binary files differ
diff --git a/contrib/trivial_rebase.py b/contrib/trivial_rebase.py
deleted file mode 100755
index c97172e..0000000
--- a/contrib/trivial_rebase.py
+++ /dev/null
@@ -1,253 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (c) 2010, Code Aurora Forum. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#    # Redistributions of source code must retain the above copyright
-#       notice, this list of conditions and the following disclaimer.
-#    # Redistributions in binary form must reproduce the above
-#       copyright notice, this list of conditions and the following
-#       disclaimer in the documentation and/or other materials provided
-#       with the distribution.
-#    # Neither the name of Code Aurora Forum, Inc. nor the names of its
-#       contributors may be used to endorse or promote products derived
-#       from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
-# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
-# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
-# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
-# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-""" This script is designed to detect when a patchset uploaded to Gerrit is
-'identical' (determined via git-patch-id) and reapply reviews onto the new
-patchset from the previous patchset.
-
-Get usage and help info by running: ./trivial_rebase.py --help
-Documentation is available here: https://www.codeaurora.org/xwiki/bin/QAEP/Gerrit
-
-"""
-
-from __future__ import print_function
-
-import argparse
-import json
-import re
-import subprocess
-import sys
-
-class TrivialRebase:
-  def __init__(self):
-    usage = "%(prog)s <required options> [--server-port=PORT]"
-    parser = argparse.ArgumentParser(usage=usage)
-    parser.add_argument("--change-url", dest="changeUrl", help="Change URL")
-    parser.add_argument("--project", help="Project path in Gerrit")
-    parser.add_argument("--commit", help="Git commit-ish for this patchset")
-    parser.add_argument("--patchset", type=int, help="The patchset number")
-    parser.add_argument("--private-key-path", dest="private_key_path",
-                        help="Full path to Gerrit SSH daemon's private host key")
-    parser.add_argument("--server", default='localhost',
-                        help="Gerrit SSH server [default: %(default)s]")
-    parser.add_argument("--server-port", dest="port", default='29418',
-                        help="Port to connect to Gerrit's SSH daemon "
-                             "[default: %(default)s]")
-    parser.add_argument("--ssh", default="ssh", help="SSH executable")
-    parser.add_argument("--ssh-port-flag", dest="ssh_port_flag", default="-p", help="SSH port flag")
-
-    args = parser.parse_known_args()[0]
-    if None in [args.changeUrl, args.project, args.commit, args.patchset]:
-      parser.error("Incomplete arguments")
-    try:
-      self.changeId = re.search(r'\d+$', args.changeUrl).group()
-    except AttributeError:
-      parser.error("Invalid changeId")
-    self.project = args.project
-    self.commit = args.commit
-    self.patchset = args.patchset
-    self.private_key_path = args.private_key_path
-    self.server = args.server
-    self.port = args.port
-    self.ssh = args.ssh
-    self.ssh_port_flag = args.ssh_port_flag
-
-  class CheckCallError(OSError):
-    """CheckCall() returned non-0."""
-    def __init__(self, command, cwd, retcode, stdout, stderr=None):
-      OSError.__init__(self, command, cwd, retcode, stdout, stderr)
-      self.command = command
-      self.cwd = cwd
-      self.retcode = retcode
-      self.stdout = stdout
-      self.stderr = stderr
-
-  def CheckCall(self, command, cwd=None):
-    """Like subprocess.check_call() but returns stdout.
-
-    Works on python 2.4
-
-    """
-    try:
-      process = subprocess.Popen(command, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-      std_out, std_err = process.communicate()
-    except OSError as e:
-      raise self.CheckCallError(command, cwd, e.errno, None)
-    if process.returncode:
-      raise self.CheckCallError(command, cwd, process.returncode, std_out, std_err)
-    return std_out, std_err
-
-  def GsqlQuery(self, sql_query):
-    """Run a gerrit gsql query and return the result."""
-    gsql_cmd = [self.ssh, self.ssh_port_flag, self.port, self.server, 'gerrit', 'gsql',
-                '--format', 'JSON', '-c', sql_query]
-    try:
-      (gsql_out, _gsql_stderr) = self.CheckCall(gsql_cmd)
-    except self.CheckCallError as e:
-      print("return code is %s" % e.retcode)
-      print("stdout and stderr is\n%s%s" % (e.stdout, e.stderr))
-      raise
-
-    new_out = gsql_out.replace('}}\n', '}}\nsplit here\n')
-    return new_out.split('split here\n')
-
-  def FindPrevRev(self):
-    """Find the revision of the previous patch set on the change."""
-    sql_query = ("\"SELECT revision FROM patch_sets WHERE "
-                 "change_id = %s AND patch_set_id = %s\"" %
-                 (self.changeId, (self.patchset - 1)))
-    revisions = self.GsqlQuery(sql_query)
-
-    json_dict = json.loads(revisions[0], strict=False)
-    return json_dict["columns"]["revision"]
-
-  def GetApprovals(self):
-    """Get all the approvals on a specific patch set.
-
-    Returns a list of approval dicts.
-
-    """
-    sql_query = ("\"SELECT value,account_id,category_id AS label FROM patch_set_approvals "
-                 "WHERE change_id = %s AND patch_set_id = %s AND value != 0\""
-                 % (self.changeId, (self.patchset - 1)))
-    gsql_out = self.GsqlQuery(sql_query)
-    approvals = []
-    for json_str in gsql_out:
-      data = json.loads(json_str, strict=False)
-      if data["type"] == "row":
-        approvals.append(data["columns"])
-    return approvals
-
-  def AppendAcctApproval(self, account_id, value):
-    try:
-      newval = self.acct_approvals[account_id] + ' ' + value
-    except KeyError:
-      newval = value
-    self.acct_approvals[account_id] = newval
-
-  def GetEmailFromAcctId(self, account_id):
-    """Return the preferred email address associated with the account_id."""
-    sql_query = ("\"SELECT preferred_email FROM accounts WHERE account_id = %s\""
-                 % account_id)
-    email_addr = self.GsqlQuery(sql_query)
-
-    json_dict = json.loads(email_addr[0], strict=False)
-    return json_dict["columns"]["preferred_email"]
-
-  def GetPatchId(self, revision):
-    git_show_cmd = ['git', 'show', revision]
-    patch_id_cmd = ['git', 'patch-id']
-    git_show_process = subprocess.Popen(git_show_cmd, stdout=subprocess.PIPE)
-    patch_id_process = subprocess.Popen(patch_id_cmd, stdout=subprocess.PIPE,
-                                        stdin=git_show_process.stdout)
-    res = patch_id_process.communicate()[0] or '0'
-    return res.split()[0]
-
-  def SuExec(self, as_user, cmd):
-    suexec_cmd = [self.ssh, '-l', "Gerrit Code Review", self.ssh_port_flag, self.port, self.server]
-    if self.private_key_path:
-      suexec_cmd += ['-i', self.private_key_path]
-    suexec_cmd += ['suexec', '--as', as_user, '--', cmd]
-    self.CheckCall(suexec_cmd)
-
-  def DiffCommitMessages(self, prev_commit):
-    log_cmd1 = ['git', 'log', '--pretty=format:"%an %ae%n%s%n%b"',
-                prev_commit + '^!']
-    commit1_log = self.CheckCall(log_cmd1)
-    log_cmd2 = ['git', 'log', '--pretty=format:"%an %ae%n%s%n%b"',
-                self.commit + '^!']
-    commit2_log = self.CheckCall(log_cmd2)
-    if commit1_log != commit2_log:
-      return True
-    return False
-
-  def Run(self):
-    if self.patchset == 1:
-      # Nothing to detect on first patchset
-      return
-    prev_revision = self.FindPrevRev()
-    assert prev_revision, "Previous revision not found"
-    prev_patch_id = self.GetPatchId(prev_revision)
-    cur_patch_id = self.GetPatchId(self.commit)
-    if prev_patch_id == '0' and cur_patch_id == '0':
-      print("commits %s and %s are both empty or merge commits" % (prev_revision, self.commit))
-      return
-    if cur_patch_id != prev_patch_id:
-      # patch-ids don't match
-      return
-    # Patch ids match. This is a trivial rebase.
-    # In addition to patch-id we should check if the commit message changed. Most
-    # approvers would want to re-review changes when the commit message changes.
-    changed = self.DiffCommitMessages(prev_revision)
-    if changed:
-      # Insert a comment into the change letting the approvers know only the
-      # commit message changed
-      comment_msg = ("\'--message=New patchset patch-id matches previous patchset"
-                     ", but commit message has changed.'")
-      comment_cmd = [self.ssh, self.ssh_port_flag, self.port, self.server, 'gerrit',
-                     'review', '--project', self.project, comment_msg, self.commit]
-      self.CheckCall(comment_cmd)
-      return
-
-    # Need to get all approvals on prior patch set, then suexec them onto
-    # this patchset.
-    approvals = self.GetApprovals()
-    self.acct_approvals = dict()
-    for approval in approvals:
-      # Note: Sites with different 'copy_min_score' values in the
-      # approval_categories DB table might want different behavior here.
-      # Additional categories should also be added if desired.
-      if approval["label"] == "Code-Review":
-        if approval['value'] != '-2':
-          self.AppendAcctApproval(approval['account_id'],
-                                  '--label Code-Review=%s' % approval['value'])
-      elif approval["label"] == "Verified":
-        # Don't re-add verifies
-        # self.AppendAcctApproval(approval['account_id'], '--label Verified=%s' % approval['value'])
-        continue
-      elif approval["label"] == "SUBM":
-        # We don't care about previous submit attempts
-        continue
-      else:
-        self.AppendAcctApproval(approval['account_id'], '--label %s=%s' %
-                                (approval['label'], approval['value']))
-
-    gerrit_review_msg = ("\'Automatically re-added by Gerrit trivial rebase "
-                          "detection script.\'")
-    for acct, flags in list(self.acct_approvals.items()):
-      gerrit_review_cmd = ['gerrit', 'review', '--project', self.project,
-                            '--message', gerrit_review_msg, flags, self.commit]
-      email_addr = self.GetEmailFromAcctId(acct)
-      self.SuExec(email_addr, ' '.join(gerrit_review_cmd))
-
-if __name__ == "__main__":
-  try:
-    TrivialRebase().Run()
-  except AssertionError as e:
-    print(e, file=sys.stderr)
diff --git a/contrib/ui-api-proxy.go b/contrib/ui-api-proxy.go
new file mode 100644
index 0000000..1ae1c1a
--- /dev/null
+++ b/contrib/ui-api-proxy.go
@@ -0,0 +1,65 @@
+// ui-api-proxy is a reverse http proxy that allows the UI to be served from
+// a different host than the API. This allows testing new UI features served
+// from localhost but using live production data.
+//
+// Run the binary, download & install the Go tools available at
+// http://golang.org/doc/install . To run, execute `go run ui-api-proxy.go`.
+// For a description of the available flags, execute
+// `go run ui-api-proxy.go --help`.
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"net"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"strings"
+	"time"
+)
+
+var (
+	ui   = flag.String("ui", "http://localhost:8080", "host to which ui requests will be forwarded")
+	api  = flag.String("api", "https://gerrit-review.googlesource.com", "host to which api requests will be forwarded")
+	port = flag.Int("port", 0, "port on which to run this server")
+)
+
+func main() {
+	flag.Parse()
+
+	uiURL, err := url.Parse(*ui)
+	if err != nil {
+		log.Fatalf("proxy: parsing ui addr %q failed: %v\n", *ui, err)
+	}
+	apiURL, err := url.Parse(*api)
+	if err != nil {
+		log.Fatalf("proxy: parsing api addr %q failed: %v\n", *api, err)
+	}
+
+	l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%v", *port))
+	if err != nil {
+		log.Fatalln("proxy: listen failed: ", err)
+	}
+	defer l.Close()
+	fmt.Printf("OK\nListening on http://%v/\n", l.Addr())
+
+	err = http.Serve(l, &httputil.ReverseProxy{
+		FlushInterval: 500 * time.Millisecond,
+		Director: func(r *http.Request) {
+			if strings.HasPrefix(r.URL.Path, "/changes/") || strings.HasPrefix(r.URL.Path, "/projects/") {
+				r.URL.Scheme, r.URL.Host = apiURL.Scheme, apiURL.Host
+			} else {
+				r.URL.Scheme, r.URL.Host = uiURL.Scheme, uiURL.Host
+			}
+			if r.URL.Scheme == "" {
+				r.URL.Scheme = "http"
+			}
+			r.Host, r.URL.Opaque, r.URL.RawQuery = r.URL.Host, r.RequestURI, ""
+		},
+	})
+	if err != nil {
+		log.Fatalln("proxy: serve failed: ", err)
+	}
+}
diff --git a/gerrit-acceptance-tests/.gitignore b/gerrit-acceptance-tests/.gitignore
deleted file mode 100644
index e1914aa..0000000
--- a/gerrit-acceptance-tests/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-/bin
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-common.iml
diff --git a/gerrit-acceptance-tests/.settings/org.eclipse.core.resources.prefs b/gerrit-acceptance-tests/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index 8dd9b1d..0000000
--- a/gerrit-acceptance-tests/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-acceptance-tests/.settings/org.eclipse.core.runtime.prefs b/gerrit-acceptance-tests/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 5a0ad22..0000000
--- a/gerrit-acceptance-tests/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,2 +0,0 @@
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-acceptance-tests/.settings/org.eclipse.jdt.core.prefs b/gerrit-acceptance-tests/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index a6765b0..0000000
--- a/gerrit-acceptance-tests/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,263 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.join_lines_in_comments=true
-org.eclipse.jdt.core.formatter.join_wrapped_lines=true
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-acceptance-tests/.settings/org.eclipse.jdt.ui.prefs b/gerrit-acceptance-tests/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index c6dd34f..0000000
--- a/gerrit-acceptance-tests/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,60 +0,0 @@
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
index cb946d6..3c26720 100644
--- a/gerrit-acceptance-tests/BUCK
+++ b/gerrit-acceptance-tests/BUCK
@@ -1,9 +1,8 @@
-include_defs('//gerrit-acceptance-tests/tests.defs')
-
 java_library(
   name = 'lib',
   srcs = glob(['src/test/java/com/google/gerrit/acceptance/*.java']),
-  deps = [
+  exported_deps = [
+    '//gerrit-common:annotations',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
     '//gerrit-launcher:launcher',
@@ -19,22 +18,23 @@
     '//lib:args4j',
     '//lib:gson',
     '//lib:guava',
+    '//lib:gwtjsonrpc',
     '//lib:gwtorm',
     '//lib:h2',
     '//lib:jsch',
     '//lib:junit',
-    '//lib:servlet-api-3_0',
+    '//lib:servlet-api-3_1',
 
     '//lib/commons:httpclient',
     '//lib/commons:httpcore',
     '//lib/log:impl_log4j',
     '//lib/log:log4j',
     '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
     '//lib/jgit:jgit',
     '//lib/jgit:junit',
     '//lib/mina:sshd',
   ],
-  export_deps = True,
   visibility = [
     '//tools/eclipse:classpath',
     '//gerrit-acceptance-tests/...',
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 784b461..5c5746b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -14,14 +14,77 @@
 
 package com.google.gerrit.acceptance;
 
+import static com.google.gerrit.acceptance.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
+import static com.google.gerrit.acceptance.GitUtil.initSsh;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.base.Joiner;
+import com.google.common.primitives.Chars;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.RevisionApi;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gson.Gson;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.util.Providers;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Rule;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
+import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
 
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+
+@RunWith(ConfigSuite.class)
 public abstract class AbstractDaemonTest {
+  @ConfigSuite.Parameter
+  public Config baseConfig;
+
+  @Inject
+  protected AccountCreator accounts;
+
+  @Inject
+  private SchemaFactory<ReviewDb> reviewDbProvider;
+
+  @Inject
+  protected GerritApi gApi;
+
+  @Inject
+  private AcceptanceTestRequestScope atrScope;
+
+  @Inject
+  private IdentifiedUser.GenericFactory identifiedUserFactory;
+
+  @Inject
+  protected PushOneCommit.Factory pushFactory;
+
+  protected Git git;
   protected GerritServer server;
+  protected TestAccount admin;
+  protected TestAccount user;
+  protected RestSession adminSession;
+  protected RestSession userSession;
+  protected SshSession sshSession;
+  protected ReviewDb db;
+  protected Project.NameKey project;
 
   @Rule
   public TestRule testRunner = new TestRule() {
@@ -31,7 +94,9 @@
         @Override
         public void evaluate() throws Throwable {
           boolean mem = description.getAnnotation(UseLocalDisk.class) == null;
-          beforeTest(config(description), mem);
+          boolean enableHttpd = description.getAnnotation(NoHttpd.class) == null
+              && description.getTestClass().getAnnotation(NoHttpd.class) == null;
+          beforeTest(config(description), mem, enableHttpd);
           base.evaluate();
           afterTest();
         }
@@ -39,27 +104,103 @@
     }
   };
 
-  private static Config config(Description description) {
+  private Config config(Description description) {
     GerritConfigs cfgs = description.getAnnotation(GerritConfigs.class);
     GerritConfig cfg = description.getAnnotation(GerritConfig.class);
     if (cfgs != null && cfg != null) {
       throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig not both");
     }
     if (cfgs != null) {
-      return ConfigAnnotationParser.parse(cfgs);
+      return ConfigAnnotationParser.parse(baseConfig, cfgs);
     } else if (cfg != null) {
-      return ConfigAnnotationParser.parse(cfg);
+      return ConfigAnnotationParser.parse(baseConfig, cfg);
     } else {
-      return null;
+      return baseConfig;
     }
   }
 
-  private void beforeTest(Config cfg, boolean memory) throws Exception {
-    server = GerritServer.start(cfg, memory);
+  private void beforeTest(Config cfg, boolean memory, boolean enableHttpd) throws Exception {
+    server = startServer(cfg, memory, enableHttpd);
     server.getTestInjector().injectMembers(this);
+    admin = accounts.admin();
+    user = accounts.user();
+    adminSession = new RestSession(server, admin);
+    userSession = new RestSession(server, user);
+    initSsh(admin);
+    db = reviewDbProvider.open();
+    atrScope.set(atrScope.newContext(reviewDbProvider, sshSession,
+        identifiedUserFactory.create(Providers.of(db), admin.getId())));
+    sshSession = new SshSession(server, admin);
+    project = new Project.NameKey("p");
+    createProject(sshSession, project.get());
+    git = cloneProject(sshSession.getUrl() + "/" + project.get());
+  }
+
+  protected GerritServer startServer(Config cfg, boolean memory,
+      boolean enableHttpd) throws Exception {
+    return GerritServer.start(cfg, memory, enableHttpd);
   }
 
   private void afterTest() throws Exception {
+    db.close();
+    sshSession.close();
     server.stop();
   }
+
+  protected PushOneCommit.Result createChange() throws GitAPIException,
+      IOException {
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
+    return push.to(git, "refs/for/master");
+  }
+
+  private static final List<Character> RANDOM =
+      Chars.asList(new char[]{'a','b','c','d','e','f','g','h'});
+  protected PushOneCommit.Result amendChange(String changeId)
+      throws GitAPIException, IOException {
+    Collections.shuffle(RANDOM);
+    PushOneCommit push =
+        pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
+            PushOneCommit.FILE_NAME, new String(Chars.toArray(RANDOM)), changeId);
+    return push.to(git, "refs/for/master");
+  }
+
+  protected ChangeJson.ChangeInfo getChange(String changeId, ListChangesOption... options)
+      throws IOException {
+    return getChange(adminSession, changeId, options);
+  }
+
+  protected ChangeJson.ChangeInfo getChange(RestSession session, String changeId,
+      ListChangesOption... options) throws IOException {
+    String q = options.length > 0 ? "?o=" + Joiner.on("&o=").join(options) : "";
+    RestResponse r = session.get("/changes/" + changeId + q);
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    return newGson().fromJson(r.getReader(), ChangeJson.ChangeInfo.class);
+  }
+
+  protected ChangeInfo info(String id)
+      throws RestApiException {
+    return gApi.changes().id(id).info();
+  }
+
+  protected ChangeInfo get(String id)
+      throws RestApiException {
+    return gApi.changes().id(id).get();
+  }
+
+  protected ChangeInfo get(String id, ListChangesOption... options)
+      throws RestApiException {
+    EnumSet<ListChangesOption> s = EnumSet.noneOf(ListChangesOption.class);
+    s.addAll(Arrays.asList(options));
+    return gApi.changes().id(id).get(s);
+  }
+
+  protected static Gson newGson() {
+    return OutputFormat.JSON_COMPACT.newGson();
+  }
+
+  protected RevisionApi revision(PushOneCommit.Result r) throws Exception {
+    return gApi.changes()
+        .id(r.getChangeId())
+        .current();
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
new file mode 100644
index 0000000..63bdfd2
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
@@ -0,0 +1,186 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.RequestCleanup;
+import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
+import com.google.gerrit.server.util.TimeUtil;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Key;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.util.Providers;
+
+import java.util.Map;
+
+/** Guice scopes for state during an Acceptance Test connection. */
+public class AcceptanceTestRequestScope {
+  private static final Key<RequestCleanup> RC_KEY =
+      Key.get(RequestCleanup.class);
+
+  private static final Key<RequestScopedReviewDbProvider> DB_KEY =
+      Key.get(RequestScopedReviewDbProvider.class);
+
+  public class Context implements RequestContext {
+    private final RequestCleanup cleanup = new RequestCleanup();
+    private final Map<Key<?>, Object> map = Maps.newHashMap();
+    private final SchemaFactory<ReviewDb> schemaFactory;
+    private final SshSession session;
+    private final CurrentUser user;
+
+    final long created;
+    volatile long started;
+    volatile long finished;
+
+    private Context(SchemaFactory<ReviewDb> sf, SshSession s,
+        CurrentUser u, long at) {
+      schemaFactory = sf;
+      session = s;
+      user = u;
+      created = started = finished = at;
+      map.put(RC_KEY, cleanup);
+      map.put(DB_KEY, new RequestScopedReviewDbProvider(
+          schemaFactory,
+          Providers.of(cleanup)));
+    }
+
+    private Context(Context p, SshSession s, CurrentUser c) {
+      this(p.schemaFactory, s, c, p.created);
+      started = p.started;
+      finished = p.finished;
+    }
+
+    SshSession getSession() {
+      return session;
+    }
+
+    @Override
+    public CurrentUser getCurrentUser() {
+      if (user == null) {
+        throw new IllegalStateException("user == null, forgot to set it?");
+      }
+      return user;
+    }
+
+    @Override
+    public Provider<ReviewDb> getReviewDbProvider() {
+      return (RequestScopedReviewDbProvider) map.get(DB_KEY);
+    }
+
+    synchronized <T> T get(Key<T> key, Provider<T> creator) {
+      @SuppressWarnings("unchecked")
+      T t = (T) map.get(key);
+      if (t == null) {
+        t = creator.get();
+        map.put(key, t);
+      }
+      return t;
+    }
+  }
+
+  static class ContextProvider implements Provider<Context> {
+    @Override
+    public Context get() {
+      return requireContext();
+    }
+  }
+
+  static class SshSessionProvider implements Provider<SshSession> {
+    @Override
+    public SshSession get() {
+      return requireContext().getSession();
+    }
+  }
+
+  static class Propagator extends ThreadLocalRequestScopePropagator<Context> {
+    private final AcceptanceTestRequestScope atrScope;
+
+    @Inject
+    Propagator(AcceptanceTestRequestScope atrScope, ThreadLocalRequestContext local,
+        Provider<RequestScopedReviewDbProvider> dbProviderProvider) {
+      super(REQUEST, current, local, dbProviderProvider);
+      this.atrScope = atrScope;
+    }
+
+    @Override
+    protected Context continuingContext(Context ctx) {
+      // The cleanup is not chained, since the RequestScopePropagator executors
+      // the Context's cleanup when finished executing.
+      return atrScope.newContinuingContext(ctx);
+    }
+  }
+
+  private static final ThreadLocal<Context> current =
+      new ThreadLocal<Context>();
+
+  private static Context requireContext() {
+    final Context ctx = current.get();
+    if (ctx == null) {
+      throw new OutOfScopeException("Not in command/request");
+    }
+    return ctx;
+  }
+
+  private final ThreadLocalRequestContext local;
+
+  @Inject
+  AcceptanceTestRequestScope(ThreadLocalRequestContext local) {
+    this.local = local;
+  }
+
+  public Context newContext(SchemaFactory<ReviewDb> sf, SshSession s, CurrentUser user) {
+    return new Context(sf, s, user, TimeUtil.nowMs());
+  }
+
+  private Context newContinuingContext(Context ctx) {
+    return new Context(ctx, ctx.getSession(), ctx.getCurrentUser());
+  }
+
+  public Context set(Context ctx) {
+    Context old = current.get();
+    current.set(ctx);
+    local.setContext(ctx);
+    return old;
+  }
+
+  /** Returns exactly one instance per command executed. */
+  static final Scope REQUEST = new Scope() {
+    public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
+      return new Provider<T>() {
+        public T get() {
+          return requireContext().get(key, creator);
+        }
+
+        @Override
+        public String toString() {
+          return String.format("%s[%s]", creator, REQUEST);
+        }
+      };
+    }
+
+    @Override
+    public String toString() {
+      return "Acceptance Test Scope.REQUEST";
+    }
+  };
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index 12add3e..0fc053e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -122,6 +122,22 @@
       "Administrators");
   }
 
+  public TestAccount admin2()
+      throws UnsupportedEncodingException, OrmException, JSchException {
+    return create("admin2", "admin2@example.com", "Administrator2",
+      "Administrators");
+  }
+
+  public TestAccount user()
+      throws UnsupportedEncodingException, OrmException, JSchException {
+    return create("user", "user@example.com", "User");
+  }
+
+  public TestAccount user2()
+      throws UnsupportedEncodingException, OrmException, JSchException {
+    return create("user2", "user2@example.com", "User2");
+  }
+
   private AccountExternalId.Key getEmailKey(String email) {
     return new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
index cf60fb4..41df3e6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ConfigAnnotationParser.java
@@ -22,28 +22,27 @@
 import java.util.ArrayList;
 
 class ConfigAnnotationParser {
-
   private static Splitter splitter = Splitter.on(".").trimResults();
 
-  static Config parse(GerritConfigs annotation) {
+  static Config parse(Config base, GerritConfigs annotation) {
     if (annotation == null) {
       return null;
     }
 
-    Config cfg = new Config();
+    Config cfg = new Config(base);
     for (GerritConfig c : annotation.value()) {
-      parse(cfg, c);
+      parseAnnotation(cfg, c);
     }
     return cfg;
   }
 
-  static Config parse(GerritConfig annotation) {
-    Config cfg = new Config();
-    parse(cfg, annotation);
+  static Config parse(Config base, GerritConfig annotation) {
+    Config cfg = new Config(base);
+    parseAnnotation(cfg, annotation);
     return cfg;
   }
 
-  static private void parse(Config cfg, GerritConfig c) {
+  static private void parseAnnotation(Config cfg, GerritConfig c) {
     ArrayList<String> l = Lists.newArrayList(splitter.split(c.name()));
     if (l.size() == 2) {
       cfg.setString(l.get(0), null, l.get(1), c.value());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index daf9d38..a8ce229 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -15,10 +15,12 @@
 package com.google.gerrit.acceptance;
 
 import com.google.common.collect.ImmutableList;
+import com.google.gerrit.lucene.LuceneIndexModule;
 import com.google.gerrit.pgm.Daemon;
 import com.google.gerrit.pgm.Init;
 import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.index.ChangeSchemas;
 import com.google.gerrit.server.util.SocketUtil;
 import com.google.inject.Injector;
 import com.google.inject.Key;
@@ -47,7 +49,8 @@
 public class GerritServer {
 
   /** Returns fully started Gerrit server */
-  static GerritServer start(Config base, boolean memory) throws Exception {
+  static GerritServer start(Config cfg, boolean memory, boolean enableHttpd)
+      throws Exception {
     final CyclicBarrier serverStarted = new CyclicBarrier(2);
     final Daemon daemon = new Daemon(new Runnable() {
       public void run() {
@@ -65,15 +68,19 @@
     ExecutorService daemonService = null;
     if (memory) {
       site = null;
-      Config cfg = base != null ? base : new Config();
       mergeTestConfig(cfg);
       cfg.setBoolean("httpd", null, "requestLog", false);
       cfg.setBoolean("sshd", null, "requestLog", false);
+      cfg.setBoolean("index", "lucene", "testInmemory", true);
+      daemon.setEnableHttpd(enableHttpd);
+      daemon.setLuceneModule(new LuceneIndexModule(
+          ChangeSchemas.getLatest().getVersion(),
+          Runtime.getRuntime().availableProcessors(), null));
       daemon.setDatabaseForTesting(ImmutableList.<Module>of(
           new InMemoryTestingDatabaseModule(cfg)));
       daemon.start();
     } else {
-      site = initSite(base);
+      site = initSite(cfg);
       daemonService = Executors.newSingleThreadExecutor();
       daemonService.submit(new Callable<Void>() {
         public Void call() throws Exception {
@@ -84,7 +91,7 @@
             serverStarted.reset();
           }
           return null;
-        };
+        }
       });
       serverStarted.await();
       System.out.println("Gerrit Server Started");
@@ -139,6 +146,7 @@
       @Override
       protected void configure() {
         bind(AccountCreator.class);
+        factory(PushOneCommit.Factory.class);
       }
     };
     return sysInjector.createChildInjector(module);
@@ -162,15 +170,7 @@
   }
 
   private static InetAddress getLocalHost() throws UnknownHostException {
-    try {
-      return InetAddress.getLocalHost();
-    } catch (UnknownHostException e1) {
-      try {
-        return InetAddress.getByName("localhost");
-      } catch (UnknownHostException e2) {
-        return InetAddress.getByName("127.0.0.1");
-      }
-    }
+    return InetAddress.getLoopbackAddress();
   }
 
   private File sitePath;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
similarity index 91%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
rename to gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
index c17598f..3234ffd 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
@@ -12,12 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.git;
+package com.google.gerrit.acceptance;
 
 import com.google.common.collect.Iterables;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TempFileUtil;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.reviewdb.client.Project;
 
 import com.jcraft.jsch.JSch;
@@ -28,6 +25,7 @@
 import org.eclipse.jgit.api.CheckoutCommand;
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.CommitCommand;
+import org.eclipse.jgit.api.FetchCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.PushCommand;
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -105,10 +103,15 @@
   }
 
   public static Git cloneProject(String url) throws GitAPIException, IOException {
+    return cloneProject(url, true);
+  }
+
+  public static Git cloneProject(String url, boolean checkout) throws GitAPIException, IOException {
     final File gitDir = TempFileUtil.createTempDirectory();
     final CloneCommand cloneCmd = Git.cloneRepository();
     cloneCmd.setURI(url);
     cloneCmd.setDirectory(gitDir);
+    cloneCmd.setNoCheckout(!checkout);
     return cloneCmd.call();
   }
 
@@ -132,6 +135,13 @@
     addCmd.call();
   }
 
+  public static void rm(Git gApi, String path)
+      throws GitAPIException, IOException {
+    gApi.rm()
+        .addFilepattern(path)
+        .call();
+  }
+
   public static Commit createCommit(Git git, PersonIdent i, String msg)
       throws GitAPIException, IOException {
     return createCommit(git, i, msg, null);
@@ -178,6 +188,12 @@
     }
   }
 
+  public static void fetch(Git git, String spec) throws GitAPIException {
+    FetchCommand fetch = git.fetch();
+    fetch.setRefSpecs(new RefSpec(spec));
+    fetch.call();
+  }
+
   public static void checkout(Git git, String name) throws GitAPIException {
     CheckoutCommand checkout = git.checkout();
     checkout.setName(name);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java
new file mode 100644
index 0000000..c58a5a2
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpResponse.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import com.google.common.base.Preconditions;
+
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+public class HttpResponse {
+
+  protected org.apache.http.HttpResponse response;
+  protected Reader reader;
+
+  HttpResponse(org.apache.http.HttpResponse response) {
+    this.response = response;
+  }
+
+  public Reader getReader() throws IllegalStateException, IOException {
+    if (reader == null && response.getEntity() != null) {
+      reader = new InputStreamReader(response.getEntity().getContent());
+    }
+    return reader;
+  }
+
+  public void consume() throws IllegalStateException, IOException {
+    Reader reader = getReader();
+    if (reader != null) {
+      while (reader.read() != -1);
+    }
+  }
+
+  public int getStatusCode() {
+    return response.getStatusLine().getStatusCode();
+  }
+
+  public String getEntityContent() throws IOException {
+    Preconditions.checkNotNull(response,
+        "Response is not initialized.");
+    Preconditions.checkNotNull(response.getEntity(),
+        "Response.Entity is not initialized.");
+      ByteBuffer buf = IO.readWholeStream(
+          response.getEntity().getContent(),
+          1024);
+      return RawParseUtils.decode(
+          buf.array(),
+          buf.arrayOffset(),
+          buf.limit())
+          .trim();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java
new file mode 100644
index 0000000..b543aa7
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/HttpSession.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import com.google.common.base.CharMatcher;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+import java.io.IOException;
+import java.net.URI;
+
+public class HttpSession {
+
+  protected final String url;
+  private final TestAccount account;
+  private DefaultHttpClient client;
+
+  public HttpSession(GerritServer server, TestAccount account) {
+    this.url = CharMatcher.is('/').trimTrailingFrom(server.getUrl());
+    this.account = account;
+  }
+
+  public HttpResponse get(String path) throws IOException {
+    HttpGet get = new HttpGet(url + path);
+    return new HttpResponse(getClient().execute(get));
+  }
+
+  protected DefaultHttpClient getClient() {
+    if (client == null) {
+      URI uri = URI.create(url);
+      client = new DefaultHttpClient();
+      client.getCredentialsProvider().setCredentials(
+          new AuthScope(uri.getHost(), uri.getPort()),
+          new UsernamePasswordCredentials(account.username, account.httpPassword));
+    }
+    return client;
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index 5576c4f..8548b5c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -34,7 +34,6 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.OrmRuntimeException;
 import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
@@ -46,7 +45,7 @@
 
 import java.io.File;
 
-class InMemoryTestingDatabaseModule extends AbstractModule {
+class InMemoryTestingDatabaseModule extends LifecycleModule {
   private final Config cfg;
 
   InMemoryTestingDatabaseModule(Config cfg) {
@@ -71,12 +70,7 @@
     bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {})
       .to(InMemoryDatabase.class);
 
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().to(CreateDatabase.class);
-      }
-    });
+    listener().to(CreateDatabase.class);
 
     bind(SitePaths.class);
     bind(TrackingFooters.class)
@@ -126,4 +120,4 @@
       mem.drop();
     }
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/NoHttpd.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/NoHttpd.java
new file mode 100644
index 0000000..378439c
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/NoHttpd.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({TYPE, METHOD})
+@Retention(RUNTIME)
+public @interface NoHttpd {
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushOneCommit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
similarity index 62%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushOneCommit.java
rename to gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
index 1c0fafe..cc19c15 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushOneCommit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.git;
+package com.google.gerrit.acceptance;
 
-import static com.google.gerrit.acceptance.git.GitUtil.add;
-import static com.google.gerrit.acceptance.git.GitUtil.amendCommit;
-import static com.google.gerrit.acceptance.git.GitUtil.createCommit;
-import static com.google.gerrit.acceptance.git.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.GitUtil.add;
+import static com.google.gerrit.acceptance.GitUtil.amendCommit;
+import static com.google.gerrit.acceptance.GitUtil.createCommit;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -26,17 +26,23 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.GitUtil.Commit;
+import com.google.gerrit.acceptance.GitUtil.Commit;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gwtorm.server.OrmException;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
 
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidTagNameException;
+import org.eclipse.jgit.api.errors.NoHeadException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -50,10 +56,32 @@
 
 public class PushOneCommit {
   public static final String SUBJECT = "test commit";
-
-  private static final String FILE_NAME = "a.txt";
+  static final String FILE_NAME = "a.txt";
   private static final String FILE_CONTENT = "some content";
 
+  public interface Factory {
+    PushOneCommit create(
+        ReviewDb db,
+        PersonIdent i);
+
+    PushOneCommit create(
+        ReviewDb db,
+        PersonIdent i,
+        @Assisted("subject") String subject,
+        @Assisted("fileName") String fileName,
+        @Assisted("content") String content);
+
+    PushOneCommit create(
+        ReviewDb db,
+        PersonIdent i,
+        @Assisted("subject") String subject,
+        @Assisted("fileName") String fileName,
+        @Assisted("content") String content,
+        @Assisted("changeId") String changeId);
+  }
+
+  private final ChangeNotes.Factory notesFactory;
+  private final ApprovalsUtil approvalsUtil;
   private final ReviewDb db;
   private final PersonIdent i;
 
@@ -63,18 +91,37 @@
   private String changeId;
   private String tagName;
 
-  public PushOneCommit(ReviewDb db, PersonIdent i) {
-    this(db, i, SUBJECT, FILE_NAME, FILE_CONTENT);
+  @AssistedInject
+  PushOneCommit(ChangeNotes.Factory notesFactory,
+      ApprovalsUtil approvalsUtil,
+      @Assisted ReviewDb db,
+      @Assisted PersonIdent i) {
+    this(notesFactory, approvalsUtil, db, i, SUBJECT, FILE_NAME, FILE_CONTENT);
   }
 
-  public PushOneCommit(ReviewDb db, PersonIdent i, String subject,
-      String fileName, String content) {
-    this(db, i, subject, fileName, content, null);
+  @AssistedInject
+  PushOneCommit(ChangeNotes.Factory notesFactory,
+      ApprovalsUtil approvalsUtil,
+      @Assisted ReviewDb db,
+      @Assisted PersonIdent i,
+      @Assisted("subject") String subject,
+      @Assisted("fileName") String fileName,
+      @Assisted("content") String content) {
+    this(notesFactory, approvalsUtil, db, i, subject, fileName, content, null);
   }
 
-  public PushOneCommit(ReviewDb db, PersonIdent i, String subject,
-      String fileName, String content, String changeId) {
+  @AssistedInject
+  PushOneCommit(ChangeNotes.Factory notesFactory,
+      ApprovalsUtil approvalsUtil,
+      @Assisted ReviewDb db,
+      @Assisted PersonIdent i,
+      @Assisted("subject") String subject,
+      @Assisted("fileName") String fileName,
+      @Assisted("content") String content,
+      @Nullable @Assisted("changeId") String changeId) {
     this.db = db;
+    this.notesFactory = notesFactory;
+    this.approvalsUtil = approvalsUtil;
     this.i = i;
     this.subject = subject;
     this.fileName = fileName;
@@ -85,6 +132,18 @@
   public Result to(Git git, String ref)
       throws GitAPIException, IOException {
     add(git, fileName, content);
+    return execute(git, ref);
+  }
+
+  public Result rm(Git git, String ref)
+      throws GitAPIException, IOException {
+    GitUtil.rm(git, fileName);
+    return execute(git, ref);
+  }
+
+  private Result execute(Git git, String ref) throws GitAPIException,
+      IOException, ConcurrentRefUpdateException, InvalidTagNameException,
+      NoHeadException {
     Commit c;
     if (changeId != null) {
       c = amendCommit(git, i, subject, changeId);
@@ -95,23 +154,21 @@
     if (tagName != null) {
       git.tag().setName(tagName).setAnnotated(false).call();
     }
-    return new Result(db, ref, pushHead(git, ref, tagName != null), c, subject);
+    return new Result(ref, pushHead(git, ref, tagName != null), c, subject);
   }
 
   public void setTag(final String tagName) {
     this.tagName = tagName;
   }
 
-  public static class Result {
-    private final ReviewDb db;
+  public class Result {
     private final String ref;
     private final PushResult result;
     private final Commit commit;
     private final String subject;
 
-    private Result(ReviewDb db, String ref, PushResult result, Commit commit,
+    private Result(String ref, PushResult result, Commit commit,
         String subject) {
-      this.db = db;
       this.ref = ref;
       this.result = result;
       this.commit = commit;
@@ -157,10 +214,10 @@
                 }
               }));
 
-      for (PatchSetApproval psa : db.patchSetApprovals().byPatchSet(
-          c.currentPatchSetId())) {
-        assertTrue("unexpected reviewer " + psa.getAccountId(),
-            expectedReviewerIds.remove(psa.getAccountId()));
+      for (Account.Id accountId
+          : approvalsUtil.getReviewers(db, notesFactory.create(c)).values()) {
+        assertTrue("unexpected reviewer " + accountId,
+            expectedReviewerIds.remove(accountId));
       }
       assertTrue("missing reviewers: " + expectedReviewerIds,
           expectedReviewerIds.isEmpty());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
index 9e5d702..73dc1f0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestResponse.java
@@ -16,26 +16,17 @@
 
 import static com.google.gerrit.httpd.restapi.RestApiServlet.JSON_MAGIC;
 
-import com.google.common.base.Preconditions;
-
-import org.apache.http.HttpResponse;
-import org.eclipse.jgit.util.IO;
-import org.eclipse.jgit.util.RawParseUtils;
-
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.Reader;
-import java.nio.ByteBuffer;
 
-public class RestResponse {
+public class RestResponse extends HttpResponse {
 
-  private HttpResponse response;
-  private Reader reader;
-
-  RestResponse(HttpResponse response) {
-    this.response = response;
+  RestResponse(org.apache.http.HttpResponse response) {
+    super(response);
   }
 
+  @Override
   public Reader getReader() throws IllegalStateException, IOException {
     if (reader == null && response.getEntity() != null) {
       reader = new InputStreamReader(response.getEntity().getContent());
@@ -43,30 +34,4 @@
     }
     return reader;
   }
-
-  public void consume() throws IllegalStateException, IOException {
-    Reader reader = getReader();
-    if (reader != null) {
-      while (reader.read() != -1);
-    }
-  }
-
-  public int getStatusCode() {
-    return response.getStatusLine().getStatusCode();
-  }
-
-  public String getEntityContent() throws IOException {
-    Preconditions.checkNotNull(response,
-        "Response is not initialized.");
-    Preconditions.checkNotNull(response.getEntity(),
-        "Response.Entity is not initialized.");
-      ByteBuffer buf = IO.readWholeStream(
-          response.getEntity().getContent(),
-          1024);
-      return RawParseUtils.decode(
-          buf.array(),
-          buf.arrayOffset(),
-          buf.limit())
-          .trim();
-  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
index 9132be8..45befd0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -14,34 +14,31 @@
 
 package com.google.gerrit.acceptance;
 
-import com.google.common.base.CharMatcher;
 import com.google.common.base.Charsets;
-import com.google.gson.Gson;
+import com.google.common.base.Preconditions;
+import com.google.gerrit.extensions.restapi.RawInput;
+import com.google.gerrit.server.OutputFormat;
 
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.BufferedHttpEntity;
+import org.apache.http.entity.InputStreamEntity;
 import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.message.BasicHeader;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.net.URI;
+import java.io.InputStream;
 
-public class RestSession {
-
-  private final TestAccount account;
-  private final String url;
-  DefaultHttpClient client;
+public class RestSession extends HttpSession {
 
   public RestSession(GerritServer server, TestAccount account) {
-    this.url = CharMatcher.is('/').trimTrailingFrom(server.getUrl());
-    this.account = account;
+    super(server, account);
   }
 
+  @Override
   public RestResponse get(String endPoint) throws IOException {
     HttpGet get = new HttpGet(url + "/a" + endPoint);
     return new RestResponse(getClient().execute(get));
@@ -56,12 +53,23 @@
     if (content != null) {
       put.addHeader(new BasicHeader("Content-Type", "application/json"));
       put.setEntity(new StringEntity(
-          new Gson().toJson(content),
+          OutputFormat.JSON_COMPACT.newGson().toJson(content),
           Charsets.UTF_8.name()));
     }
     return new RestResponse(getClient().execute(put));
   }
 
+  public RestResponse putRaw(String endPoint, RawInput stream) throws IOException {
+    Preconditions.checkNotNull(stream);
+    HttpPut put = new HttpPut(url + "/a" + endPoint);
+    put.addHeader(new BasicHeader("Content-Type", stream.getContentType()));
+    put.setEntity(new BufferedHttpEntity(
+        new InputStreamEntity(
+            stream.getInputStream(),
+            stream.getContentLength())));
+    return new RestResponse(getClient().execute(put));
+  }
+
   public RestResponse post(String endPoint) throws IOException {
     return post(endPoint, null);
   }
@@ -71,7 +79,7 @@
     if (content != null) {
       post.addHeader(new BasicHeader("Content-Type", "application/json"));
       post.setEntity(new StringEntity(
-          new Gson().toJson(content),
+          OutputFormat.JSON_COMPACT.newGson().toJson(content),
           Charsets.UTF_8.name()));
     }
     return new RestResponse(getClient().execute(post));
@@ -82,14 +90,27 @@
     return new RestResponse(getClient().execute(delete));
   }
 
-  private DefaultHttpClient getClient() {
-    if (client == null) {
-      URI uri = URI.create(url);
-      client = new DefaultHttpClient();
-      client.getCredentialsProvider().setCredentials(
-          new AuthScope(uri.getHost(), uri.getPort()),
-          new UsernamePasswordCredentials(account.username, account.httpPassword));
-    }
-    return client;
+
+  public static RawInput newRawInput(final String content) throws IOException {
+    Preconditions.checkNotNull(content);
+    Preconditions.checkArgument(!content.isEmpty());
+    return new RawInput() {
+      byte bytes[] = content.getBytes("UTF-8");
+
+      @Override
+      public InputStream getInputStream() throws IOException {
+        return new ByteArrayInputStream(bytes);
+      }
+
+      @Override
+      public String getContentType() {
+        return "application/octet-stream";
+      }
+
+      @Override
+      public long getContentLength() {
+        return bytes.length;
+      }
+    };
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
index a150eba..dc648fc 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
@@ -36,6 +36,7 @@
     this.account = account;
   }
 
+  @SuppressWarnings("resource")
   public String exec(String command) throws JSchException, IOException {
     ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
     try {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
index b85ffea..31ed136 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java
@@ -58,4 +58,8 @@
         server.getHttpAddress().getAddress().getHostAddress(),
         server.getHttpAddress().getPort());
   }
+
+  public Account.Id getId() {
+    return id;
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
index 0931e12..88d46a4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/UseGerritConfigAnnotationTest.java
@@ -37,7 +37,7 @@
   @Test
   @GerritConfigs({
       @GerritConfig(name="x.y", value="z"),
-      @GerritConfig(name="a.b", value="c"),
+      @GerritConfig(name="a.b", value="c")
   })
   public void testMultiple() {
     assertEquals("z", serverConfig.getString("x", null, "y"));
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/BUCK
new file mode 100644
index 0000000..aa9703c
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/BUCK
@@ -0,0 +1,6 @@
+include_defs('//gerrit-acceptance-tests/tests.defs')
+
+acceptance_tests(
+  srcs = glob(['*IT.java']),
+  deps = ['//gerrit-acceptance-tests:lib'],
+)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
new file mode 100644
index 0000000..6dff20a
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.change;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeStatus;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.Test;
+
+import java.io.IOException;
+
+@NoHttpd
+public class ChangeIT extends AbstractDaemonTest {
+
+  @Test
+  public void get() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    String triplet = "p~master~" + r.getChangeId();
+    ChangeInfo c = info(triplet);
+    assertEquals(triplet, c.id);
+    assertEquals("p", c.project);
+    assertEquals("master", c.branch);
+    assertEquals(ChangeStatus.NEW, c.status);
+    assertEquals("test commit", c.subject);
+    assertEquals(true, c.mergeable);
+    assertEquals(r.getChangeId(), c.changeId);
+    assertEquals(c.created, c.updated);
+  }
+
+  @Test
+  public void abandon() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .abandon();
+  }
+
+  @Test
+  public void restore() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .abandon();
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .restore();
+  }
+
+  @Test
+  public void revert() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .revision(r.getCommit().name())
+        .review(ReviewInput.approve());
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .revision(r.getCommit().name())
+        .submit();
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .revert();
+  }
+
+  // Change is already up to date
+  @Test(expected = ResourceConflictException.class)
+  public void rebase() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .revision(r.getCommit().name())
+        .rebase();
+  }
+
+  @Test
+  public void addReviewer() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    AddReviewerInput in = new AddReviewerInput();
+    in.reviewer = user.email;
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .addReviewer(in);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/BUCK
new file mode 100644
index 0000000..8da456d
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/BUCK
@@ -0,0 +1,7 @@
+include_defs('//gerrit-acceptance-tests/tests.defs')
+
+acceptance_tests(
+  srcs = glob(['*IT.java']),
+  deps = ['//gerrit-acceptance-tests:lib'],
+)
+
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
new file mode 100644
index 0000000..54afd0b
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.project;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.Test;
+
+import java.io.IOException;
+
+@NoHttpd
+public class ProjectIT extends AbstractDaemonTest  {
+
+  @Test
+  public void createBranch() throws GitAPIException,
+      IOException, RestApiException {
+    gApi.projects()
+        .name(project.get())
+        .branch("foo")
+        .create(new BranchInput());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/BUCK
new file mode 100644
index 0000000..aa9703c
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/BUCK
@@ -0,0 +1,6 @@
+include_defs('//gerrit-acceptance-tests/tests.defs')
+
+acceptance_tests(
+  srcs = glob(['*IT.java']),
+  deps = ['//gerrit-acceptance-tests:lib'],
+)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
new file mode 100644
index 0000000..50522cd
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -0,0 +1,155 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.revision;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+@NoHttpd
+public class RevisionIT extends AbstractDaemonTest {
+
+  private TestAccount admin2;
+
+  @Before
+  public void setUp() throws Exception {
+    admin2 = accounts.admin2();
+  }
+
+  @Test
+  public void reviewTriplet() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .revision(r.getCommit().name())
+        .review(ReviewInput.approve());
+  }
+
+  @Test
+  public void reviewCurrent() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    gApi.changes()
+        .id(r.getChangeId())
+        .current()
+        .review(ReviewInput.approve());
+  }
+
+  @Test
+  public void reviewNumber() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    gApi.changes()
+        .id(r.getChangeId())
+        .revision(1)
+        .review(ReviewInput.approve());
+
+    r = updateChange(r, "new content");
+    gApi.changes()
+        .id(r.getChangeId())
+        .revision(2)
+        .review(ReviewInput.approve());
+  }
+
+  @Test
+  public void submit() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .current()
+        .review(ReviewInput.approve());
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .current()
+        .submit();
+  }
+
+  @Test(expected = AuthException.class)
+  public void submitOnBehalfOf() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .current()
+        .review(ReviewInput.approve());
+    SubmitInput in = new SubmitInput();
+    in.onBehalfOf = admin2.email;
+    in.waitForMerge = true;
+    gApi.changes()
+        .id("p~master~" + r.getChangeId())
+        .current()
+        .submit(in);
+  }
+
+  @Test
+  public void deleteDraft() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createDraft();
+    gApi.changes()
+        .id(r.getChangeId())
+        .revision(r.getCommit().name())
+        .delete();
+  }
+
+  @Test
+  public void cherryPick() throws GitAPIException,
+      IOException, RestApiException {
+    PushOneCommit.Result r = createChange();
+    CherryPickInput in = new CherryPickInput();
+    in.destination = "foo";
+    in.message = "it goes to stable branch";
+    gApi.projects()
+        .name(project.get())
+        .branch(in.destination)
+        .create(new BranchInput());
+    ChangeApi cApi = gApi.changes()
+        .id(r.getChangeId())
+        .revision(r.getCommit().name())
+        .cherryPick(in);
+    cApi.current()
+        .review(ReviewInput.approve());
+    cApi.current()
+        .submit();
+  }
+
+  private PushOneCommit.Result updateChange(PushOneCommit.Result r,
+      String content) throws GitAPIException, IOException {
+    PushOneCommit push = pushFactory.create(db, admin.getIdent(),
+        "test commit", "a.txt", content, r.getChangeId());
+    return push.to(git, "refs/for/master");
+  }
+
+  private PushOneCommit.Result createDraft() throws GitAPIException,
+      IOException {
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
+    return push.to(git, "refs/drafts/master");
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index a6ce132..5a8ba0c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -14,26 +14,17 @@
 
 package com.google.gerrit.acceptance.git;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static com.google.gerrit.acceptance.GitUtil.cloneProject;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
-import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
 
 import com.jcraft.jsch.JSchException;
 
-import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -44,32 +35,11 @@
     SSH, HTTP
   }
 
-  @Inject
-  private AccountCreator accounts;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  private TestAccount admin;
-  private Project.NameKey project;
-  private Git git;
-  private ReviewDb db;
   private String sshUrl;
 
   @Before
   public void setUp() throws Exception {
-    admin =
-        accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-
-    project = new Project.NameKey("p");
-    initSsh(admin);
-    SshSession sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
     sshUrl = sshSession.getUrl();
-    sshSession.close();
-
-    db = reviewDbProvider.open();
   }
 
   protected void selectProtocol(Protocol p) throws GitAPIException, IOException {
@@ -87,11 +57,6 @@
     git = cloneProject(url + "/" + project.get());
   }
 
-  @After
-  public void cleanup() {
-    db.close();
-  }
-
   @Test
   public void testPushForMaster() throws GitAPIException, OrmException,
       IOException {
@@ -119,7 +84,6 @@
   public void testPushForMasterWithCc() throws GitAPIException, OrmException,
       IOException, JSchException {
     // cc one user
-    TestAccount user = accounts.create("user", "user@example.com", "User");
     String topic = "my/topic";
     PushOneCommit.Result r = pushTo("refs/for/master/" + topic + "%cc=" + user.email);
     r.assertOkStatus();
@@ -144,7 +108,6 @@
   public void testPushForMasterWithReviewer() throws GitAPIException,
       OrmException, IOException, JSchException {
     // add one reviewer
-    TestAccount user = accounts.create("user", "user@example.com", "User");
     String topic = "my/topic";
     PushOneCommit.Result r = pushTo("refs/for/master/" + topic + "%r=" + user.email);
     r.assertOkStatus();
@@ -190,7 +153,7 @@
 
   private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
       IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
     return push.to(git, ref);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
index d8e839a..5976f54 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUCK
@@ -2,7 +2,7 @@
 
 acceptance_tests(
   srcs = ['DraftChangeBlockedIT.java', 'SubmitOnPushIT.java'],
-  deps = [':util'],
+  deps = ['//gerrit-acceptance-tests:lib'],
 )
 
 acceptance_tests(
@@ -13,26 +13,5 @@
 java_library(
   name = 'push_for_review',
   srcs = ['AbstractPushForReview.java'],
-  deps = [
-    ':util',
-    '//gerrit-acceptance-tests:lib',
-  ],
-)
-
-java_library(
-  name = 'util',
-  srcs = [
-    'GitUtil.java',
-    'PushOneCommit.java',
-  ],
-  deps = [
-    '//gerrit-acceptance-tests:lib',
-    '//gerrit-reviewdb:server',
-    '//lib:guava',
-    '//lib:gwtorm',
-    '//lib:jsch',
-    '//lib/jgit:jgit',
-    '//lib:junit',
-  ],
-  visibility = ['//gerrit-acceptance-tests/...'],
+  deps = ['//gerrit-acceptance-tests:lib'],
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java
index 578d48b..b69c264 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/DraftChangeBlockedIT.java
@@ -14,43 +14,30 @@
 
 package com.google.gerrit.acceptance.git;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
-import static com.google.gerrit.server.project.Util.ANONYMOUS;
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
 import static com.google.gerrit.server.project.Util.grant;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 
-import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
 
+@NoHttpd
 public class DraftChangeBlockedIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  @Inject
   private ProjectCache projectCache;
 
   @Inject
@@ -59,27 +46,13 @@
   @Inject
   private MetaDataUpdate.Server metaDataUpdateFactory;
 
-  private TestAccount admin;
-  private Project.NameKey project;
-  private Git git;
-  private ReviewDb db;
-
   @Before
   public void setUp() throws Exception {
     ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
-    grant(cfg, Permission.PUSH, ANONYMOUS,
+    grant(cfg, Permission.PUSH, ANONYMOUS_USERS,
         "refs/drafts/*").setBlock();
     saveProjectConfig(cfg);
-
-    project = new Project.NameKey("p");
-    admin = accounts.admin();
-    initSsh(admin);
-    SshSession sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
-
-    db = reviewDbProvider.open();
-    git = cloneProject(sshSession.getUrl() + "/" + project.get());
-    sshSession.close();
+    projectCache.evict(cfg.getProject());
   }
 
   @Test
@@ -100,7 +73,7 @@
 
   private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
       IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
     return push.to(git, ref);
   }
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java
index 5251d2d..c037e76 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SshPushForReviewIT.java
@@ -14,11 +14,14 @@
 
 package com.google.gerrit.acceptance.git;
 
+import com.google.gerrit.acceptance.NoHttpd;
+
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.junit.Before;
 
 import java.io.IOException;
 
+@NoHttpd
 public class SshPushForReviewIT extends AbstractPushForReview {
   @Before
   public void selectSshUrl() throws GitAPIException, IOException {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
index baeafe1..a73169d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
@@ -14,16 +14,17 @@
 
 package com.google.gerrit.acceptance.git;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static com.google.gerrit.acceptance.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
@@ -33,12 +34,14 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.git.CommitMergeStatus;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
@@ -59,20 +62,20 @@
 import org.junit.Test;
 
 import java.io.IOException;
-import java.util.List;
 
+@NoHttpd
 public class SubmitOnPushIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private SchemaFactory<ReviewDb> reviewDbProvider;
 
   @Inject
   private GitRepositoryManager repoManager;
 
   @Inject
+  private ApprovalsUtil approvalsUtil;
+
+  @Inject
   private MetaDataUpdate.Server metaDataUpdateFactory;
 
   @Inject
@@ -82,21 +85,21 @@
   private GroupCache groupCache;
 
   @Inject
+  private ChangeNotes.Factory changeNotesFactory;
+
+  @Inject
   private @GerritPersonIdent PersonIdent serverIdent;
 
-  private TestAccount admin;
+  @Inject
+  private PushOneCommit.Factory pushFactory;
+
   private Project.NameKey project;
   private Git git;
   private ReviewDb db;
 
   @Before
   public void setUp() throws Exception {
-    admin =
-        accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-
     project = new Project.NameKey("p");
-    initSsh(admin);
     SshSession sshSession = new SshSession(server, admin);
     createProject(sshSession, project.get());
     git = cloneProject(sshSession.getUrl() + "/" + project.get());
@@ -127,7 +130,7 @@
     grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
     grant(Permission.CREATE, project, "refs/tags/*");
     final String tag = "v1.0";
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
     push.setTag(tag);
     PushOneCommit.Result r = push.to(git, "refs/for/master%submit");
     r.assertOkStatus();
@@ -236,6 +239,22 @@
     r.assertErrorStatus("branch " + branchName + " not found");
   }
 
+  @Test
+  public void mergeOnPushToBranch() throws Exception {
+    grant(Permission.PUSH, project, "refs/heads/master");
+    PushOneCommit.Result r =
+        push("refs/for/master", PushOneCommit.SUBJECT, "a.txt", "some content");
+    r.assertOkStatus();
+
+    git.push()
+        .setRefSpecs(new RefSpec(r.getCommitId().name() + ":refs/heads/master"))
+        .call();
+    assertCommit(project, "refs/heads/master");
+    assertNull(getSubmitter(r.getPatchSetId()));
+    Change c = db.changes().get(r.getPatchSetId().getParentKey());
+    assertEquals(Change.Status.MERGED, c.getStatus());
+  }
+
   private void grant(String permission, Project.NameKey project, String ref)
       throws RepositoryNotFoundException, IOException, ConfigInvalidException {
     MetaDataUpdate md = metaDataUpdateFactory.create(project);
@@ -249,11 +268,16 @@
     projectCache.evict(config.getProject());
   }
 
+  private PatchSetApproval getSubmitter(PatchSet.Id patchSetId)
+      throws OrmException {
+    Change c = db.changes().get(patchSetId.getParentKey());
+    ChangeNotes notes = changeNotesFactory.create(c).load();
+    return approvalsUtil.getSubmitter(db, notes, patchSetId);
+  }
+
   private void assertSubmitApproval(PatchSet.Id patchSetId) throws OrmException {
-    List<PatchSetApproval> approvals = db.patchSetApprovals().byPatchSet(patchSetId).toList();
-    assertEquals(1, approvals.size());
-    PatchSetApproval a = approvals.get(0);
-    assertEquals(PatchSetApproval.LabelId.SUBMIT.get(), a.getLabel());
+    PatchSetApproval a = getSubmitter(patchSetId);
+    assertTrue(a.isSubmit());
     assertEquals(1, a.getValue());
     assertEquals(admin.id, a.getAccountId());
   }
@@ -307,21 +331,21 @@
 
   private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
       IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
     return push.to(git, ref);
   }
 
   private PushOneCommit.Result push(String ref, String subject,
       String fileName, String content) throws GitAPIException, IOException {
     PushOneCommit push =
-        new PushOneCommit(db, admin.getIdent(), subject, fileName, content);
+        pushFactory.create(db, admin.getIdent(), subject, fileName, content);
     return push.to(git, ref);
   }
 
   private PushOneCommit.Result push(String ref, String subject,
       String fileName, String content, String changeId) throws GitAPIException,
       IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent(), subject,
+    PushOneCommit push = pushFactory.create(db, admin.getIdent(), subject,
         fileName, content, changeId);
     return push.to(git, ref);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountAssert.java
index fb26669..2c41f9a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountAssert.java
@@ -18,11 +18,12 @@
 import static org.junit.Assert.assertTrue;
 
 import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.server.account.AccountInfo;
 
 public class AccountAssert {
 
   public static void assertAccountInfo(TestAccount a, AccountInfo ai) {
-    assertTrue(a.id.get() == ai._account_id);
+    assertTrue(a.id.get() == ai._accountId);
     assertEquals(a.fullName, ai.name);
     assertEquals(a.email, ai.email);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
index 1fca451..77e0419 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/BUCK
@@ -4,14 +4,16 @@
   srcs = glob(['*IT.java']),
   deps = [
     ':util',
-    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
-    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change:util',
+    '//gerrit-acceptance-tests:lib',
   ],
 )
 
 java_library(
   name = 'util',
-  srcs = ['AccountAssert.java', 'AccountInfo.java'],
+  srcs = [
+    'AccountAssert.java',
+    'CapabilityInfo.java',
+  ],
   deps = [
     '//gerrit-acceptance-tests:lib',
     '//gerrit-reviewdb:server',
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java
new file mode 100644
index 0000000..9d22ee4
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilitiesIT.java
@@ -0,0 +1,128 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.account;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class CapabilitiesIT extends AbstractDaemonTest {
+
+  @Inject
+  private AllProjectsName allProjects;
+
+  @Inject
+  private MetaDataUpdate.Server metaDataUpdateFactory;
+
+  @Inject
+  private ProjectCache projectCache;
+
+  @Test
+  public void testCapabilitiesUser() throws IOException,
+      ConfigInvalidException, IllegalArgumentException,
+      IllegalAccessException, NoSuchFieldException,
+      SecurityException {
+    grantAllCapabilities();
+    RestResponse r =
+        userSession.get("/accounts/self/capabilities");
+    int code = r.getStatusCode();
+    assertEquals(code, 200);
+    CapabilityInfo info = (new Gson()).fromJson(r.getReader(),
+        new TypeToken<CapabilityInfo>() {}.getType());
+    for (String c: GlobalCapability.getAllNames()) {
+      if (GlobalCapability.ADMINISTRATE_SERVER.equals(c)) {
+        assertFalse(info.administrateServer);
+      } else if (GlobalCapability.PRIORITY.equals(c)) {
+        assertFalse(info.priority);
+      } else if (GlobalCapability.QUERY_LIMIT.equals(c)) {
+        assertEquals(0, info.queryLimit.min);
+        assertEquals(0, info.queryLimit.max);
+      } else {
+        assertTrue(String.format("capability %s was not granted", c),
+            (Boolean)CapabilityInfo.class.getField(c).get(info));
+      }
+    }
+  }
+
+  @Test
+  public void testCapabilitiesAdmin() throws IOException,
+      ConfigInvalidException, IllegalArgumentException,
+      IllegalAccessException, NoSuchFieldException,
+      SecurityException {
+    RestResponse r =
+        adminSession.get("/accounts/self/capabilities");
+    int code = r.getStatusCode();
+    assertEquals(code, 200);
+    CapabilityInfo info = (new Gson()).fromJson(r.getReader(),
+        new TypeToken<CapabilityInfo>() {}.getType());
+    for (String c: GlobalCapability.getAllNames()) {
+      if (GlobalCapability.PRIORITY.equals(c)) {
+        assertFalse(info.priority);
+      } else if (GlobalCapability.QUERY_LIMIT.equals(c)) {
+        assertNotNull("missing queryLimit", info.queryLimit);
+        assertEquals(0, info.queryLimit.min);
+        assertEquals(500, info.queryLimit.max);
+      } else if (GlobalCapability.ACCESS_DATABASE.equals(c)) {
+        assertFalse(info.accessDatabase);
+      } else if (GlobalCapability.RUN_AS.equals(c)) {
+        assertFalse(info.runAs);
+      } else {
+        assertTrue(String.format("capability %s was not granted", c),
+            (Boolean)CapabilityInfo.class.getField(c).get(info));
+      }
+    }
+  }
+
+  private void grantAllCapabilities() throws IOException,
+      ConfigInvalidException {
+    MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
+    md.setMessage("Make super user");
+    ProjectConfig config = ProjectConfig.read(md);
+    AccessSection s = config.getAccessSection(
+        AccessSection.GLOBAL_CAPABILITIES);
+    for (String c: GlobalCapability.getAllNames()) {
+      if (GlobalCapability.ADMINISTRATE_SERVER.equals(c)) {
+        continue;
+      }
+      Permission p = s.getPermission(c, true);
+      p.add(new PermissionRule(
+          config.resolve(SystemGroupBackend.getGroup(
+              SystemGroupBackend.REGISTERED_USERS))));
+    }
+    config.commit(md);
+    projectCache.evict(config.getProject());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java
new file mode 100644
index 0000000..dbc1dd8
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/CapabilityInfo.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.account;
+
+class CapabilityInfo {
+  public boolean accessDatabase;
+  public boolean administrateServer;
+  public boolean createAccount;
+  public boolean createGroup;
+  public boolean createProject;
+  public boolean emailReviewers;
+  public boolean flushCaches;
+  public boolean generateHttpPassword;
+  public boolean killTask;
+  public boolean priority;
+  public QueryLimit queryLimit;
+  public boolean runAs;
+  public boolean runGC;
+  public boolean streamEvents;
+  public boolean viewAllAccounts;
+  public boolean viewCaches;
+  public boolean viewConnections;
+  public boolean viewQueue;
+
+  static class QueryLimit {
+    short min;
+    short max;
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetAccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetAccountIT.java
index 3e8183e..1ac9cdf 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetAccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetAccountIT.java
@@ -18,40 +18,21 @@
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.extensions.restapi.Url;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
-import com.google.inject.Inject;
+import com.google.gerrit.server.account.AccountInfo;
 
 import org.apache.http.HttpStatus;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
 
 public class GetAccountIT extends AbstractDaemonTest {
-
-  @Inject
-  private AccountCreator accounts;
-
-  private TestAccount admin;
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void getNonExistingAccount_NotFound() throws IOException {
-    assertEquals(HttpStatus.SC_NOT_FOUND, session.get("/accounts/non-existing")
-        .getStatusCode());
+    assertEquals(HttpStatus.SC_NOT_FOUND,
+        adminSession.get("/accounts/non-existing").getStatusCode());
   }
 
   @Test
@@ -78,11 +59,9 @@
 
   private void testGetAccount(String url, TestAccount expectedAccount)
       throws IOException {
-    RestResponse r = session.get(url);
+    RestResponse r = adminSession.get(url);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    AccountInfo account =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<AccountInfo>() {}.getType());
-    assertAccountInfo(expectedAccount, account);
+    assertAccountInfo(expectedAccount, newGson()
+        .fromJson(r.getReader(), AccountInfo.class));
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetDiffPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetDiffPreferencesIT.java
new file mode 100644
index 0000000..3850513
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/GetDiffPreferencesIT.java
@@ -0,0 +1,70 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.account;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.server.account.GetDiffPreferences.DiffPreferencesInfo;
+import com.google.gwtorm.server.OrmException;
+
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class GetDiffPreferencesIT extends AbstractDaemonTest {
+  @Test
+  public void getDiffPreferencesOfNonExistingAccount_NotFound()
+      throws IOException {
+    assertEquals(HttpStatus.SC_NOT_FOUND,
+        adminSession.get("/accounts/non-existing/preferences.diff").getStatusCode());
+  }
+
+  @Test
+  public void getDiffPreferences() throws IOException, OrmException {
+    RestResponse r = adminSession.get("/accounts/" + admin.email + "/preferences.diff");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    DiffPreferencesInfo diffPreferences =
+        newGson().fromJson(r.getReader(), DiffPreferencesInfo.class);
+    assertDiffPreferences(new AccountDiffPreference(admin.id), diffPreferences);
+  }
+
+  private static void assertDiffPreferences(AccountDiffPreference expected, DiffPreferencesInfo actual) {
+    assertEquals(expected.getContext(), actual.context);
+    assertEquals(expected.isExpandAllComments(), toBoolean(actual.expandAllComments));
+    assertEquals(expected.getIgnoreWhitespace(), actual.ignoreWhitespace);
+    assertEquals(expected.isIntralineDifference(), toBoolean(actual.intralineDifference));
+    assertEquals(expected.getLineLength(), actual.lineLength);
+    assertEquals(expected.isManualReview(), toBoolean(actual.manualReview));
+    assertEquals(expected.isRetainHeader(), toBoolean(actual.retainHeader));
+    assertEquals(expected.isShowLineEndings(), toBoolean(actual.showLineEndings));
+    assertEquals(expected.isShowTabs(), toBoolean(actual.showTabs));
+    assertEquals(expected.isShowWhitespaceErrors(), toBoolean(actual.showWhitespaceErrors));
+    assertEquals(expected.isSkipDeleted(), toBoolean(actual.skipDeleted));
+    assertEquals(expected.isSkipUncommented(), toBoolean(actual.skipUncommented));
+    assertEquals(expected.isSyntaxHighlighting(), toBoolean(actual.syntaxHighlighting));
+    assertEquals(expected.getTabSize(), actual.tabSize);
+  }
+
+  private static boolean toBoolean(Boolean b) {
+    if (b == null) {
+      return false;
+    }
+    return b.booleanValue();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
index b5ae7de..9470df0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/StarredChangesIT.java
@@ -14,72 +14,23 @@
 
 package com.google.gerrit.acceptance.rest.account;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.PushOneCommit.Result;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.PushOneCommit;
-import com.google.gerrit.acceptance.git.PushOneCommit.Result;
-import com.google.gerrit.acceptance.rest.change.ChangeInfo;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
 
-import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
-import java.util.List;
 
 public class StarredChangesIT extends AbstractDaemonTest {
 
-  @Inject
-  private AccountCreator accounts;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  private TestAccount admin;
-
-  private RestSession session;
-  private Git git;
-  private ReviewDb db;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.admin();
-    session = new RestSession(server, admin);
-    initSsh(admin);
-    Project.NameKey project = new Project.NameKey("p");
-    SshSession sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
-    git = cloneProject(sshSession.getUrl() + "/" + project.get());
-    sshSession.close();
-    db = reviewDbProvider.open();
-  }
-
-  @After
-  public void cleanup() {
-    db.close();
-  }
-
   @Test
   public void starredChangeState() throws GitAPIException, IOException,
       OrmException {
@@ -97,26 +48,14 @@
     assertNull(getChange(c2.getChangeId()).starred);
   }
 
-  private ChangeInfo getChange(String changeId) throws IOException {
-    RestResponse r = session.get("/changes/?q=" + changeId);
-    List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
-        new TypeToken<List<ChangeInfo>>() {}.getType());
-    return c.get(0);
-  }
-
   private void starChange(boolean on, Change.Id id) throws IOException {
     String url = "/accounts/self/starred.changes/" + id.get();
     if (on) {
-      RestResponse r = session.put(url);
+      RestResponse r = adminSession.put(url);
       assertEquals(204, r.getStatusCode());
     } else {
-      RestResponse r = session.delete(url);
+      RestResponse r = adminSession.delete(url);
       assertEquals(204, r.getStatusCode());
     }
   }
-
-  private Result createChange() throws GitAPIException, IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
-    return push.to(git, "refs/for/master");
-  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 1fad997..13901c3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -14,32 +14,37 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static com.google.gerrit.acceptance.GitUtil.cloneProject;
+import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_REVISION;
+import static com.google.gerrit.extensions.common.ListChangesOption.DETAILED_LABELS;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.GitUtil;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
 import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.GitUtil;
-import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.SubmitInput;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
-import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
+import com.google.gerrit.server.change.ChangeJson.LabelInfo;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gson.Gson;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.PutConfig;
 import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 
 import com.jcraft.jsch.JSchException;
@@ -65,29 +70,18 @@
 public abstract class AbstractSubmit extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  @Inject
   private GitRepositoryManager repoManager;
 
-  protected RestSession session;
+  @Inject
+  private ChangeNotes.Factory notesFactory;
 
-  private TestAccount admin;
-  private Project.NameKey project;
-  private ReviewDb db;
+  @Inject
+  private ApprovalsUtil approvalsUtil;
+
 
   @Before
   public void setUp() throws Exception {
-    admin = accounts.admin();
-    session = new RestSession(server, admin);
-    initSsh(admin);
-
-    project = new Project.NameKey("p");
-
-    db = reviewDbProvider.open();
+    project = new Project.NameKey("p2");
   }
 
   @After
@@ -124,32 +118,34 @@
   }
 
   private void setSubmitType(SubmitType submitType) throws IOException {
-    ProjectConfigInput in = new ProjectConfigInput();
-    in.submit_type = submitType;
-    in.use_content_merge = InheritableBoolean.FALSE;
-    RestResponse r = session.put("/projects/" + project.get() + "/config", in);
+    PutConfig.Input in = new PutConfig.Input();
+    in.submitType = submitType;
+    in.useContentMerge = InheritableBoolean.FALSE;
+    RestResponse r =
+        adminSession.put("/projects/" + project.get() + "/config", in);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     r.consume();
   }
 
   protected void setUseContentMerge() throws IOException {
-    ProjectConfigInput in = new ProjectConfigInput();
-    in.use_content_merge = InheritableBoolean.TRUE;
-    RestResponse r = session.put("/projects/" + project.get() + "/config", in);
+    PutConfig.Input in = new PutConfig.Input();
+    in.useContentMerge = InheritableBoolean.TRUE;
+    RestResponse r =
+        adminSession.put("/projects/" + project.get() + "/config", in);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     r.consume();
   }
 
   protected PushOneCommit.Result createChange(Git git) throws GitAPIException,
       IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
     return push.to(git, "refs/for/master");
   }
 
   protected PushOneCommit.Result createChange(Git git, String subject,
       String fileName, String content) throws GitAPIException, IOException {
     PushOneCommit push =
-        new PushOneCommit(db, admin.getIdent(), subject, fileName, content);
+        pushFactory.create(db, admin.getIdent(), subject, fileName, content);
     return push.to(git, "refs/for/master");
   }
 
@@ -179,13 +175,14 @@
 
   private void submit(String changeId, int expectedStatus) throws IOException {
     approve(changeId);
+    SubmitInput subm = new SubmitInput();
+    subm.waitForMerge = true;
     RestResponse r =
-        session.post("/changes/" + changeId + "/submit",
-            SubmitInput.waitForMerge());
+        adminSession.post("/changes/" + changeId + "/submit", subm);
     assertEquals(expectedStatus, r.getStatusCode());
     if (expectedStatus == HttpStatus.SC_OK) {
       ChangeInfo change =
-          (new Gson()).fromJson(r.getReader(),
+          newGson().fromJson(r.getReader(),
               new TypeToken<ChangeInfo>() {}.getType());
       assertEquals(Change.Status.MERGED, change.status);
     }
@@ -193,21 +190,48 @@
   }
 
   private void approve(String changeId) throws IOException {
-    RestResponse r =
-        session.post("/changes/" + changeId + "/revisions/current/review",
-            ReviewInput.approve());
+    RestResponse r = adminSession.post(
+        "/changes/" + changeId + "/revisions/current/review",
+        new ReviewInput().label("Code-Review", 2));
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     r.consume();
   }
 
-  protected void assertCherryPick(Git localGit, boolean contentMerge) throws IOException {
+  protected void assertCurrentRevision(String changeId, int expectedNum,
+      ObjectId expectedId) throws IOException {
+    ChangeInfo c = getChange(changeId, CURRENT_REVISION);
+    assertEquals(expectedId.name(), c.currentRevision);
+    assertEquals(expectedNum, c.revisions.get(expectedId.name())._number);
+  }
+
+  protected void assertApproved(String changeId) throws IOException {
+    ChangeInfo c = getChange(changeId, DETAILED_LABELS);
+    LabelInfo cr = c.labels.get("Code-Review");
+    assertEquals(1, cr.all.size());
+    assertEquals(2, cr.all.get(0).value.intValue());
+    assertEquals("Administrator", cr.all.get(0).name);
+  }
+
+  protected void assertSubmitter(String changeId, int psId)
+      throws OrmException, IOException {
+    ChangeNotes cn = notesFactory.create(
+        Iterables.getOnlyElement(db.changes().byKey(new Change.Key(changeId))));
+    PatchSetApproval submitter = approvalsUtil.getSubmitter(
+        db, cn, new PatchSet.Id(cn.getChangeId(), psId));
+    assertTrue(submitter.isSubmit());
+    assertEquals(admin.getId(), submitter.getAccountId());
+  }
+
+  protected void assertCherryPick(Git localGit, boolean contentMerge)
+      throws IOException {
     assertRebase(localGit, contentMerge);
     RevCommit remoteHead = getRemoteHead();
     assertFalse(remoteHead.getFooterLines("Reviewed-On").isEmpty());
     assertFalse(remoteHead.getFooterLines("Reviewed-By").isEmpty());
   }
 
-  protected void assertRebase(Git localGit, boolean contentMerge) throws IOException {
+  protected void assertRebase(Git localGit, boolean contentMerge)
+      throws IOException {
     Repository repo = localGit.getRepository();
     RevCommit localHead = getHead(repo);
     RevCommit remoteHead = getRemoteHead();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
index 770e554..eb1a6be 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.checkout;
+import static com.google.gerrit.acceptance.GitUtil.checkout;
 import static org.junit.Assert.assertEquals;
 
-import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.acceptance.PushOneCommit;
 
 import com.jcraft.jsch.JSchException;
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AccountInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AccountInfo.java
deleted file mode 100644
index ee94476..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AccountInfo.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.change;
-
-public class AccountInfo {
-  public Integer _account_id;
-  public String name;
-  public String email;
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
index 9210669..c76c9a6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
@@ -1,43 +1,32 @@
 include_defs('//gerrit-acceptance-tests/tests.defs')
 
+SUBMIT_UTIL_SRCS = [
+  'AbstractSubmit.java',
+  'AbstractSubmitByMerge.java',
+]
+
+SUBMIT_TESTS = glob(['Submit*IT.java'], excludes = SUBMIT_UTIL_SRCS)
+OTHER_TESTS = glob(['*IT.java'], excludes = SUBMIT_TESTS + SUBMIT_UTIL_SRCS)
+
 acceptance_tests(
-  srcs = ['CustomLabelIT.java', 'ChangeMessagesIT.java',
-          'DeleteDraftChangeIT.java', 'DeleteDraftPatchSetIT.java'],
+  srcs = OTHER_TESTS,
   deps = [
-    ':util',
-    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
+    '//gerrit-acceptance-tests:lib',
   ],
 )
 
 acceptance_tests(
-  srcs = ['SubmitByCherryPickIT.java', 'SubmitByFastForwardIT.java',
-          'SubmitByMergeAlwaysIT.java', 'SubmitByMergeIfNecessaryIT.java',
-          'SubmitByRebaseIfNecessaryIT.java'],
+  srcs = SUBMIT_TESTS,
   deps = [
-    ':submit',
-    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
+    ':submit_util',
+    '//gerrit-acceptance-tests:lib',
   ],
 )
 
 java_library(
-  name = 'submit',
-  srcs = ['AbstractSubmit.java', 'AbstractSubmitByMerge.java'],
+  name = 'submit_util',
+  srcs = SUBMIT_UTIL_SRCS,
   deps = [
-    ':util',
-    '//gerrit-acceptance-tests:lib',
-    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
-  ],
-)
-
-java_library(
-  name = 'util',
-  srcs = ['AccountInfo.java', 'ChangeInfo.java', 'ChangeMessageInfo.java',
-          'GroupInfo.java', 'ProjectConfigInput.java', 'ReviewInput.java',
-          'SubmitInput.java', 'SuggestReviewerInfo.java'],
-  deps = [
-    '//lib:guava',
-    '//gerrit-reviewdb:server',
     '//gerrit-acceptance-tests:lib',
   ],
-  visibility = ['//gerrit-acceptance-tests/...'],
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
deleted file mode 100644
index 385fa68..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.change;
-
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.change.ChangeJson.LabelInfo;
-
-import java.util.List;
-import java.util.Map;
-
-public class ChangeInfo {
-  String id;
-  String project;
-  String branch;
-  List<ChangeMessageInfo> messages;
-  Change.Status status;
-  public Boolean starred;
-  Map<String, LabelInfo> labels;
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
index ec0e31f..6352b50 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -14,139 +14,65 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
-import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.PushOneCommit;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
 
-import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
-import java.util.List;
+import java.util.Iterator;
 
 public class ChangeMessagesIT extends AbstractDaemonTest {
 
-  @Inject
-  private AccountCreator accounts;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  private TestAccount admin;
-  private RestSession session;
-  private Git git;
-  private ReviewDb db;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-    initSsh(admin);
-    Project.NameKey project = new Project.NameKey("p");
-    SshSession sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
-    git = cloneProject(sshSession.getUrl() + "/" + project.get());
-    sshSession.close();
-    db = reviewDbProvider.open();
-  }
-
-  @After
-  public void cleanup() {
-    db.close();
-  }
-
   @Test
-  public void messagesNotReturnedByDefault() throws GitAPIException,
-      IOException {
-    String changeId = createChange();
+  public void messagesNotReturnedByDefault() throws Exception {
+    String changeId = createChange().getChangeId();
     postMessage(changeId, "Some nits need to be fixed.");
-    ChangeInfo c = getChange(changeId);
+    ChangeInfo c = info(changeId);
     assertNull(c.messages);
   }
 
   @Test
-  public void defaultMessage() throws GitAPIException,
-  IOException {
-    String changeId = createChange();
-    ChangeInfo c = getChangeWithMessages(changeId);
+  public void defaultMessage() throws GitAPIException, IOException,
+      RestApiException {
+    String changeId = createChange().getChangeId();
+    ChangeInfo c = get(changeId);
     assertNotNull(c.messages);
     assertEquals(1, c.messages.size());
-    assertEquals("Uploaded patch set 1.", c.messages.get(0).message);
+    assertEquals("Uploaded patch set 1.", c.messages.iterator().next().message);
   }
 
   @Test
-  public void messagesReturnedInChronologicalOrder() throws GitAPIException,
-      IOException {
-    String changeId = createChange();
+  public void messagesReturnedInChronologicalOrder() throws Exception {
+    String changeId = createChange().getChangeId();
     String firstMessage = "Some nits need to be fixed.";
     postMessage(changeId, firstMessage);
     String secondMessage = "I like this feature.";
     postMessage(changeId, secondMessage);
-    ChangeInfo c = getChangeWithMessages(changeId);
+    ChangeInfo c = get(changeId);
     assertNotNull(c.messages);
     assertEquals(3, c.messages.size());
-    assertEquals("Uploaded patch set 1.", c.messages.get(0).message);
-    assertMessage(firstMessage, c.messages.get(1).message);
-    assertMessage(secondMessage, c.messages.get(2).message);
-  }
-
-  private String createChange() throws GitAPIException,
-      IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
-    return push.to(git, "refs/for/master").getChangeId();
-  }
-
-  private ChangeInfo getChange(String changeId) throws IOException {
-    return getChange(changeId, false);
-  }
-
-  private ChangeInfo getChangeWithMessages(String changeId) throws IOException {
-    return getChange(changeId, true);
-  }
-
-  private ChangeInfo getChange(String changeId, boolean includeMessages)
-      throws IOException {
-    RestResponse r =
-        session.get("/changes/?q=" + changeId
-            + (includeMessages ? "&o=MESSAGES" : ""));
-    List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
-        new TypeToken<List<ChangeInfo>>() {}.getType());
-    return c.get(0);
+    Iterator<ChangeMessageInfo> it = c.messages.iterator();
+    assertEquals("Uploaded patch set 1.", it.next().message);
+    assertMessage(firstMessage, it.next().message);
+    assertMessage(secondMessage, it.next().message);
   }
 
   private void assertMessage(String expected, String actual) {
     assertEquals("Patch Set 1:\n\n" + expected, actual);
   }
 
-  private void postMessage(String changeId, String msg) throws IOException {
+  private void postMessage(String changeId, String msg) throws Exception {
     ReviewInput in = new ReviewInput();
     in.message = msg;
-    session.post("/changes/" + changeId + "/revisions/1/review", in).consume();
-  }
-
-  @SuppressWarnings("unused")
-  private class ReviewInput {
-    String message;
+    gApi.changes().id(changeId).current().review(in);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
new file mode 100644
index 0000000..83efd30
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeOwnerIT.java
@@ -0,0 +1,124 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+import static com.google.gerrit.acceptance.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
+import static com.google.gerrit.acceptance.GitUtil.initSsh;
+import static com.google.gerrit.common.data.Permission.LABEL;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class ChangeOwnerIT extends AbstractDaemonTest {
+
+  @Inject
+  private MetaDataUpdate.Server metaDataUpdateFactory;
+
+  @Inject
+  private ProjectCache projectCache;
+
+  private TestAccount user2;
+
+  private RestSession sessionOwner;
+  private RestSession sessionDev;
+
+  @Before
+  public void setUp() throws Exception {
+    sessionOwner = new RestSession(server, user);
+    SshSession sshSession = new SshSession(server, user);
+    initSsh(user);
+    // need to initialize intern session
+    createProject(sshSession, "foo");
+    git = cloneProject(sshSession.getUrl() + "/" + project.get());
+    sshSession.close();
+    user2 = accounts.user2();
+    sessionDev = new RestSession(server, user2);
+  }
+
+  @Test
+  public void testChangeOwner_OwnerACLNotGranted() throws GitAPIException,
+      IOException, OrmException, ConfigInvalidException {
+    approve(sessionOwner, createMyChange(), HttpStatus.SC_FORBIDDEN);
+  }
+
+  @Test
+  public void testChangeOwner_OwnerACLGranted() throws GitAPIException,
+      IOException, OrmException, ConfigInvalidException {
+    grantApproveToChangeOwner();
+    approve(sessionOwner, createMyChange(), HttpStatus.SC_OK);
+  }
+
+  @Test
+  public void testChangeOwner_NotOwnerACLGranted() throws GitAPIException,
+      IOException, OrmException, ConfigInvalidException {
+    grantApproveToChangeOwner();
+    approve(sessionDev, createMyChange(), HttpStatus.SC_FORBIDDEN);
+  }
+
+  private void approve(RestSession s, String changeId, int expected)
+      throws IOException {
+    RestResponse r =
+        s.post("/changes/" + changeId + "/revisions/current/review",
+            new ReviewInput().label("Code-Review", 2));
+    assertEquals(expected, r.getStatusCode());
+    r.consume();
+  }
+
+  private void grantApproveToChangeOwner() throws IOException,
+      ConfigInvalidException {
+    MetaDataUpdate md = metaDataUpdateFactory.create(project);
+    md.setMessage(String.format("Grant approve to change owner"));
+    ProjectConfig config = ProjectConfig.read(md);
+    AccessSection s = config.getAccessSection("refs/heads/*", true);
+    Permission p = s.getPermission(LABEL + "Code-Review", true);
+    PermissionRule rule = new PermissionRule(config
+        .resolve(SystemGroupBackend.getGroup(SystemGroupBackend.CHANGE_OWNER)));
+    rule.setMin(-2);
+    rule.setMax(+2);
+    p.add(rule);
+    config.commit(md);
+    projectCache.evict(config.getProject());
+  }
+
+  private String createMyChange() throws GitAPIException,
+      IOException {
+    PushOneCommit push = pushFactory.create(db, user.getIdent());
+    return push.to(git, "refs/for/master").getChangeId();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConflictsOperatorIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConflictsOperatorIT.java
new file mode 100644
index 0000000..3370cb6
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConflictsOperatorIT.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+import static com.google.gerrit.acceptance.GitUtil.checkout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
+import com.google.gson.reflect.TypeToken;
+
+import com.jcraft.jsch.JSchException;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Set;
+
+public class ConflictsOperatorIT extends AbstractDaemonTest {
+
+  private int count;
+
+  @Test
+  public void noConflictingChanges() throws JSchException, IOException,
+      GitAPIException {
+    PushOneCommit.Result change = createChange(git, true);
+    createChange(git, false);
+
+    Set<String> changes = queryConflictingChanges(change);
+    assertEquals(0, changes.size());
+  }
+
+  @Test
+  public void conflictingChanges() throws JSchException, IOException,
+      GitAPIException {
+    PushOneCommit.Result change = createChange(git, true);
+    PushOneCommit.Result conflictingChange1 = createChange(git, true);
+    PushOneCommit.Result conflictingChange2 = createChange(git, true);
+    createChange(git, false);
+
+    Set<String> changes = queryConflictingChanges(change);
+    assertChanges(changes, conflictingChange1, conflictingChange2);
+  }
+
+  private PushOneCommit.Result createChange(Git git, boolean conflicting)
+      throws GitAPIException, IOException {
+    checkout(git, "origin/master");
+    String file = conflicting ? "test.txt" : "test-" + count + ".txt";
+    PushOneCommit push =
+        pushFactory.create(db, admin.getIdent(), "Change " + count, file,
+            "content " + count);
+    count++;
+    return push.to(git, "refs/for/master");
+  }
+
+  private Set<String> queryConflictingChanges(PushOneCommit.Result change)
+      throws IOException {
+    RestResponse r =
+        adminSession.get("/changes/?q=conflicts:" + change.getChangeId());
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    Set<ChangeInfo> changes =
+        newGson().fromJson(r.getReader(),
+            new TypeToken<Set<ChangeInfo>>() {}.getType());
+    r.consume();
+    return ImmutableSet.copyOf(Iterables.transform(changes,
+        new Function<ChangeInfo, String>() {
+          @Override
+          public String apply(ChangeInfo input) {
+            return input.id;
+          }
+        }));
+  }
+
+  private void assertChanges(Set<String> actualChanges,
+      PushOneCommit.Result... expectedChanges) {
+    assertEquals(expectedChanges.length, actualChanges.size());
+    for (PushOneCommit.Result c : expectedChanges) {
+      assertTrue(actualChanges.contains(id(c)));
+    }
+  }
+
+  private String id(PushOneCommit.Result change) {
+    return project.get() + "~master~" + change.getChangeId();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CustomLabelIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CustomLabelIT.java
deleted file mode 100644
index f33603f..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CustomLabelIT.java
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright (C) 2014 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.change;
-
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
-import static com.google.gerrit.server.project.Util.REGISTERED;
-import static com.google.gerrit.server.project.Util.category;
-import static com.google.gerrit.server.project.Util.grant;
-import static com.google.gerrit.server.project.Util.value;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import com.google.common.collect.Maps;
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
-import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.PushOneCommit;
-import com.google.gerrit.common.changes.ListChangesOption;
-import com.google.gerrit.common.data.LabelType;
-import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.OutputFormat;
-import com.google.gerrit.server.change.ChangeJson.LabelInfo;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-
-import org.apache.http.HttpStatus;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.IOException;
-
-public class CustomLabelIT extends AbstractDaemonTest {
-
-  @Inject
-  private ProjectCache projectCache;
-
-  @Inject
-  private AllProjectsName allProjects;
-
-  @Inject
-  private MetaDataUpdate.Server metaDataUpdateFactory;
-
-  @Inject
-  private AccountCreator accounts;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  private TestAccount admin;
-
-  private RestSession session;
-  private Git git;
-  private ReviewDb db;
-
-  private final LabelType Q = category("CustomLabel",
-      value(1, "Positive"),
-      value(0, "No score"),
-      value(-1, "Negative"));
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.admin();
-    session = new RestSession(server, admin);
-    initSsh(admin);
-    Project.NameKey project = new Project.NameKey("p");
-    SshSession sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
-    git = cloneProject(sshSession.getUrl() + "/" + project.get());
-    sshSession.close();
-    db = reviewDbProvider.open();
-    ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
-    grant(cfg, Permission.forLabel(Q.getName()), -1, +1, REGISTERED,
-        "refs/heads/*");
-    saveProjectConfig(cfg);
-  }
-
-  @After
-  public void cleanup() {
-    db.close();
-  }
-
-  @Test
-  public void customLabelNoOp_NegativeVoteNotBlock() throws Exception {
-    Q.setFunctionName("NoOp");
-    saveLabelConfig();
-    ChangeInfo c = get(disliked(change()));
-    LabelInfo q = c.labels.get(Q.getName());
-    assertEquals(1, q.all.size());
-    assertNotNull(q.rejected);
-    assertNull(q.blocking);
-  }
-
-  @Test
-  public void customLabelNoBlock_NegativeVoteNotBlock() throws Exception {
-    Q.setFunctionName("NoBlock");
-    saveLabelConfig();
-    ChangeInfo c = get(disliked(change()));
-    LabelInfo q = c.labels.get(Q.getName());
-    assertEquals(1, q.all.size());
-    assertNotNull(q.rejected);
-    assertNull(q.blocking);
-  }
-
-  @Test
-  public void customLabelMaxNoBlock_NegativeVoteNotBlock() throws Exception {
-    Q.setFunctionName("MaxNoBlock");
-    saveLabelConfig();
-    ChangeInfo c = get(disliked(change()));
-    LabelInfo q = c.labels.get(Q.getName());
-    assertEquals(1, q.all.size());
-    assertNotNull(q.rejected);
-    assertNull(q.blocking);
-  }
-
-  @Test
-  public void customLabelAnyWithBlock_NegativeVoteBlock() throws Exception {
-    Q.setFunctionName("AnyWithBlock");
-    saveLabelConfig();
-    ChangeInfo c = get(disliked(change()));
-    LabelInfo q = c.labels.get(Q.getName());
-    assertEquals(1, q.all.size());
-    assertNull(q.disliked);
-    assertNotNull(q.rejected);
-    assertTrue(q.blocking);
-  }
-
-  @Test
-  public void customLabelMaxWithBlock_NegativeVoteBlock() throws Exception {
-    saveLabelConfig();
-    ChangeInfo c = get(disliked(change()));
-    LabelInfo q = c.labels.get(Q.getName());
-    assertEquals(1, q.all.size());
-    assertNull(q.disliked);
-    assertNotNull(q.rejected);
-    assertTrue(q.blocking);
-  }
-
-  private String disliked(String changeId) throws IOException {
-    ReviewInput in = new ReviewInput();
-    in.labels = Maps.newHashMap();
-    in.labels.put(Q.getName(), -1);
-    RestResponse r =
-        session.post("/changes/" + changeId + "/revisions/current/review",
-            in);
-    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    r.consume();
-    return changeId;
-  }
-
-  private ChangeInfo get(String changeId) throws IOException {
-    RestResponse r = session.get("/changes/"
-        + changeId
-        + "?o="
-        + ListChangesOption.DETAILED_LABELS
-        + "&o="
-        + ListChangesOption.LABELS);
-    return OutputFormat.JSON.newGson()
-        .fromJson(r.getReader(), ChangeInfo.class);
-  }
-
-  private String change() throws GitAPIException,
-      IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
-    return push.to(git, "refs/for/master").getChangeId();
-  }
-
-  private void saveLabelConfig() throws Exception {
-    ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
-    cfg.getLabelSections().put(Q.getName(), Q);
-    saveProjectConfig(cfg);
-  }
-
-  private void saveProjectConfig(ProjectConfig cfg) throws Exception {
-    MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
-    try {
-      cfg.commit(md);
-    } finally {
-      md.close();
-    }
-    projectCache.evict(allProjects);
-  }
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java
index 7d4aa3e..3ba5ed7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java
@@ -14,135 +14,81 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
 import static org.junit.Assert.assertEquals;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeStatus;
+import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
 
-import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
-import java.util.List;
 
 public class DeleteDraftChangeIT extends AbstractDaemonTest {
 
-  @Inject
-  private AccountCreator accounts;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  private TestAccount admin;
-
-  private RestSession session;
-  private Git git;
-  private ReviewDb db;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-    initSsh(admin);
-    Project.NameKey project = new Project.NameKey("p");
-    SshSession sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
-    git = cloneProject(sshSession.getUrl() + "/" + project.get());
-    sshSession.close();
-    db = reviewDbProvider.open();
-  }
-
-  @After
-  public void cleanup() {
-    db.close();
-  }
-
   @Test
   public void deleteChange() throws GitAPIException,
-      IOException {
-    String changeId = createChange();
-    ChangeInfo c = getChange(changeId);
-    assertEquals("p~master~" + changeId, c.id);
-    assertEquals(Change.Status.NEW, c.status);
-    RestResponse r = deleteChange(changeId, session);
+      IOException, RestApiException {
+    String changeId = createChange().getChangeId();
+    String triplet = "p~master~" + changeId;
+    ChangeInfo c = get(triplet);
+    assertEquals(triplet, c.id);
+    assertEquals(ChangeStatus.NEW, c.status);
+    RestResponse r = deleteChange(changeId, adminSession);
     assertEquals("Change is not a draft", r.getEntityContent());
     assertEquals(409, r.getStatusCode());
   }
 
   @Test
   public void deleteDraftChange() throws GitAPIException,
-      IOException, OrmException {
+      IOException, RestApiException, OrmException {
     String changeId = createDraftChange();
-    ChangeInfo c = getChange(changeId);
-    assertEquals("p~master~" + changeId, c.id);
-    assertEquals(Change.Status.DRAFT, c.status);
-    RestResponse r = deleteChange(changeId, session);
+    String triplet = "p~master~" + changeId;
+    ChangeInfo c = get(triplet);
+    assertEquals(triplet, c.id);
+    assertEquals(ChangeStatus.DRAFT, c.status);
+    RestResponse r = deleteChange(changeId, adminSession);
     assertEquals(204, r.getStatusCode());
   }
 
   @Test
   public void publishDraftChange() throws GitAPIException,
-      IOException {
+      IOException, RestApiException {
     String changeId = createDraftChange();
-    ChangeInfo c = getChange(changeId);
-    assertEquals("p~master~" + changeId, c.id);
-    assertEquals(Change.Status.DRAFT, c.status);
+    String triplet = "p~master~" + changeId;
+    ChangeInfo c = get(triplet);
+    assertEquals(triplet, c.id);
+    assertEquals(ChangeStatus.DRAFT, c.status);
     RestResponse r = publishChange(changeId);
     assertEquals(204, r.getStatusCode());
-    c = getChange(changeId);
-    assertEquals(Change.Status.NEW, c.status);
+    c = get(triplet);
+    assertEquals(ChangeStatus.NEW, c.status);
   }
 
   @Test
   public void publishDraftPatchSet() throws GitAPIException,
-      IOException, OrmException {
+      IOException, OrmException, RestApiException {
     String changeId = createDraftChange();
-    ChangeInfo c = getChange(changeId);
-    assertEquals("p~master~" + changeId, c.id);
-    assertEquals(Change.Status.DRAFT, c.status);
+    String triplet = "p~master~" + changeId;
+    ChangeInfo c = get(triplet);
+    assertEquals(triplet, c.id);
+    assertEquals(ChangeStatus.DRAFT, c.status);
     RestResponse r = publishPatchSet(changeId);
     assertEquals(204, r.getStatusCode());
-    c = getChange(changeId);
-    assertEquals(Change.Status.NEW, c.status);
-  }
-
-  private ChangeInfo getChange(String changeId) throws IOException {
-    RestResponse r = session.get("/changes/?q=" + changeId);
-    List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
-        new TypeToken<List<ChangeInfo>>() {}.getType());
-    return c.get(0);
-  }
-
-  private String createChange() throws GitAPIException,
-      IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
-    return push.to(git, "refs/for/master").getChangeId();
+    assertEquals(ChangeStatus.NEW, get(triplet).status);
   }
 
   private String createDraftChange() throws GitAPIException, IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
     return push.to(git, "refs/drafts/master").getChangeId();
   }
 
@@ -152,7 +98,7 @@
   }
 
   private RestResponse publishChange(String changeId) throws IOException {
-    return session.post("/changes/" + changeId + "/publish");
+    return adminSession.post("/changes/" + changeId + "/publish");
   }
 
   private RestResponse publishPatchSet(String changeId) throws IOException,
@@ -161,7 +107,7 @@
         .get(Iterables.getOnlyElement(db.changes()
             .byKey(new Change.Key(changeId)))
             .currentPatchSetId());
-    return session.post("/changes/"
+    return adminSession.post("/changes/"
         + changeId
         + "/revisions/"
         + patchSet.getRevision().get()
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
index 834b7c3..10fbed0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
@@ -14,137 +14,81 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
 import static org.junit.Assert.assertEquals;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.PushOneCommit.Result;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.PushOneCommit;
-import com.google.gerrit.acceptance.git.PushOneCommit.Result;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeStatus;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
 
-import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
-import java.util.List;
 
 public class DeleteDraftPatchSetIT extends AbstractDaemonTest {
 
-  @Inject
-  private AccountCreator accounts;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  private TestAccount admin;
-  private TestAccount user;
-
-  private RestSession session;
-  private RestSession userSession;
-  private Git git;
-  private ReviewDb db;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    user = accounts.create("user", "user@example.com", "User");
-    session = new RestSession(server, admin);
-    userSession = new RestSession(server, user);
-    initSsh(admin);
-    Project.NameKey project = new Project.NameKey("p");
-    SshSession sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
-    git = cloneProject(sshSession.getUrl() + "/" + project.get());
-    sshSession.close();
-    db = reviewDbProvider.open();
-  }
-
-  @After
-  public void cleanup() {
-    db.close();
-  }
-
   @Test
-  public void deletePatchSet() throws GitAPIException,
-      IOException, OrmException {
-    String changeId = createChangeWith2PS("refs/for/master");
+  public void deletePatchSet() throws Exception {
+    String changeId = createChange().getChangeId();
     PatchSet ps = getCurrentPatchSet(changeId);
-    ChangeInfo c = getChange(changeId);
-    assertEquals("p~master~" + changeId, c.id);
-    assertEquals(Change.Status.NEW, c.status);
-    RestResponse r = deletePatchSet(changeId, ps, session);
+    String triplet = "p~master~" + changeId;
+    ChangeInfo c = get(triplet);
+    assertEquals(triplet, c.id);
+    assertEquals(ChangeStatus.NEW, c.status);
+    RestResponse r = deletePatchSet(changeId, ps, adminSession);
     assertEquals("Patch set is not a draft.", r.getEntityContent());
     assertEquals(409, r.getStatusCode());
   }
 
   @Test
-  public void deleteDraftPatchSetNoACL() throws GitAPIException,
-      IOException, OrmException {
-    String changeId = createChangeWith2PS("refs/drafts/master");
+  public void deleteDraftPatchSetNoACL() throws Exception {
+    String changeId = createDraftChangeWith2PS();
     PatchSet ps = getCurrentPatchSet(changeId);
-    ChangeInfo c = getChange(changeId);
-    assertEquals("p~master~" + changeId, c.id);
-    assertEquals(Change.Status.DRAFT, c.status);
+    String triplet = "p~master~" + changeId;
+    ChangeInfo c = get(triplet);
+    assertEquals(triplet, c.id);
+    assertEquals(ChangeStatus.DRAFT, c.status);
     RestResponse r = deletePatchSet(changeId, ps, userSession);
     assertEquals("Not found", r.getEntityContent());
     assertEquals(404, r.getStatusCode());
   }
 
   @Test
-  public void deleteDraftPatchSetAndChange() throws GitAPIException,
-      IOException, OrmException {
-    String changeId = createChangeWith2PS("refs/drafts/master");
+  public void deleteDraftPatchSetAndChange() throws Exception {
+    String changeId = createDraftChangeWith2PS();
     PatchSet ps = getCurrentPatchSet(changeId);
-    ChangeInfo c = getChange(changeId);
-    assertEquals("p~master~" + changeId, c.id);
-    assertEquals(Change.Status.DRAFT, c.status);
-    RestResponse r = deletePatchSet(changeId, ps, session);
+    String triplet = "p~master~" + changeId;
+    ChangeInfo c = get(triplet);
+    assertEquals(triplet, c.id);
+    assertEquals(ChangeStatus.DRAFT, c.status);
+    RestResponse r = deletePatchSet(changeId, ps, adminSession);
     assertEquals(204, r.getStatusCode());
     Change change = Iterables.getOnlyElement(db.changes().byKey(
         new Change.Key(changeId)).toList());
     assertEquals(1, db.patchSets().byChange(change.getId())
         .toList().size());
     ps = getCurrentPatchSet(changeId);
-    r = deletePatchSet(changeId, ps, session);
+    r = deletePatchSet(changeId, ps, adminSession);
     assertEquals(204, r.getStatusCode());
     assertEquals(0, db.changes().byKey(new Change.Key(changeId))
         .toList().size());
   }
 
-  private String createChangeWith2PS(String ref) throws GitAPIException,
+  private String createDraftChangeWith2PS() throws GitAPIException,
       IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
-    Result result = push.to(git, ref);
-    push = new PushOneCommit(db, admin.getIdent(), PushOneCommit.SUBJECT,
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
+    Result result = push.to(git, "refs/drafts/master");
+    push = pushFactory.create(db, admin.getIdent(), PushOneCommit.SUBJECT,
         "b.txt", "4711", result.getChangeId());
-    return push.to(git, ref).getChangeId();
-  }
-
-  private ChangeInfo getChange(String changeId) throws IOException {
-    RestResponse r = session.get("/changes/?q=" + changeId);
-    List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
-        new TypeToken<List<ChangeInfo>>() {}.getType());
-    return c.get(0);
+    return push.to(git, "refs/drafts/master").getChangeId();
   }
 
   private PatchSet getCurrentPatchSet(String changeId) throws OrmException {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/GroupInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/GroupInfo.java
deleted file mode 100644
index 2c0efff..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/GroupInfo.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.change;
-
-public class GroupInfo {
-  public String id;
-  public String name;
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java
new file mode 100644
index 0000000..97ae2fd
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+import static com.google.gerrit.extensions.common.ListChangesOption.ALL_REVISIONS;
+import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_REVISION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.common.ChangeInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+@NoHttpd
+public class ListChangesOptionsIT extends AbstractDaemonTest {
+
+  private String changeId;
+  private List<PushOneCommit.Result> results;
+
+  @Before
+  public void setUp() throws Exception {
+    results = Lists.newArrayList();
+    results.add(push("file contents", null));
+    changeId = results.get(0).getChangeId();
+    results.add(push("new contents 1", changeId));
+    results.add(push("new contents 2", changeId));
+  }
+
+  private PushOneCommit.Result push(String content, String baseChangeId)
+      throws Exception {
+    String subject = "Change subject";
+    String fileName = "a.txt";
+    PushOneCommit push = pushFactory.create(
+        db, admin.getIdent(), subject, fileName, content, baseChangeId);
+    PushOneCommit.Result r = push.to(git, "refs/for/master");
+    r.assertOkStatus();
+    return r;
+  }
+
+  @Test
+  public void noRevisionOptions() throws Exception {
+    ChangeInfo c = info(changeId);
+    assertNull(c.currentRevision);
+    assertNull(c.revisions);
+  }
+
+  @Test
+  public void currentRevision() throws Exception {
+    ChangeInfo c = get(changeId, CURRENT_REVISION);
+    assertEquals(commitId(2), c.currentRevision);
+    assertEquals(ImmutableSet.of(commitId(2)), c.revisions.keySet());
+    assertEquals(3, c.revisions.get(commitId(2))._number);
+  }
+
+  @Test
+  public void allRevisions() throws Exception {
+    ChangeInfo c = get(changeId, ALL_REVISIONS);
+    assertEquals(commitId(2), c.currentRevision);
+    assertEquals(ImmutableSet.of(commitId(0), commitId(1), commitId(2)),
+        c.revisions.keySet());
+    assertEquals(1, c.revisions.get(commitId(0))._number);
+    assertEquals(2, c.revisions.get(commitId(1))._number);
+    assertEquals(3, c.revisions.get(commitId(2))._number);
+  }
+
+  private String commitId(int i) {
+    return results.get(i).getCommitId().name();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ProjectConfigInput.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ProjectConfigInput.java
deleted file mode 100644
index 4d2e4b6..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ProjectConfigInput.java
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.change;
-
-import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
-
-public class ProjectConfigInput {
-  public SubmitType submit_type;
-  public InheritableBoolean use_content_merge;
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ReviewInput.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ReviewInput.java
deleted file mode 100644
index a5371d2..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ReviewInput.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.change;
-
-import com.google.common.collect.Maps;
-
-import java.util.Map;
-
-public class ReviewInput {
-  Map<String, Integer> labels;
-
-  public static ReviewInput approve() {
-    ReviewInput in = new ReviewInput();
-    in.labels = Maps.newHashMap();
-    in.labels.put("Code-Review", 2);
-    return in;
-  }
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
index f90d12a..d137e62 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.checkout;
+import static com.google.gerrit.acceptance.GitUtil.checkout;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 
-import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gwtorm.server.OrmException;
 
@@ -51,8 +51,7 @@
   }
 
   @Test
-  public void submitWithCherryPick() throws JSchException, IOException,
-      GitAPIException {
+  public void submitWithCherryPick() throws Exception {
     Git git = createProject();
     RevCommit initialHead = getRemoteHead();
     PushOneCommit.Result change =
@@ -65,12 +64,16 @@
         createChange(git, "Change 2", "b.txt", "other content");
     submit(change2.getChangeId());
     assertCherryPick(git, false);
-    assertEquals(oldHead, getRemoteHead().getParent(0));
+    RevCommit newHead = getRemoteHead();
+    assertEquals(1, newHead.getParentCount());
+    assertEquals(oldHead, newHead.getParent(0));
+    assertCurrentRevision(change2.getChangeId(), 2, newHead);
+    assertSubmitter(change2.getChangeId(), 1);
+    assertSubmitter(change2.getChangeId(), 2);
   }
 
   @Test
-  public void submitWithContentMerge() throws JSchException, IOException,
-      GitAPIException {
+  public void submitWithContentMerge() throws Exception {
     Git git = createProject();
     setUseContentMerge();
     PushOneCommit.Result change =
@@ -86,12 +89,16 @@
         createChange(git, "Change 3", "a.txt", "bbb\nccc\n");
     submit(change3.getChangeId());
     assertCherryPick(git, true);
-    assertEquals(oldHead, getRemoteHead().getParent(0));
+    RevCommit newHead = getRemoteHead();
+    assertEquals(oldHead, newHead.getParent(0));
+    assertApproved(change3.getChangeId());
+    assertCurrentRevision(change3.getChangeId(), 2, newHead);
+    assertSubmitter(change2.getChangeId(), 1);
+    assertSubmitter(change2.getChangeId(), 2);
   }
 
   @Test
-  public void submitWithContentMerge_Conflict() throws JSchException,
-      IOException, GitAPIException {
+  public void submitWithContentMerge_Conflict() throws Exception {
     Git git = createProject();
     setUseContentMerge();
     RevCommit initialHead = getRemoteHead();
@@ -105,11 +112,12 @@
         createChange(git, "Change 2", "a.txt", "other content");
     submitWithConflict(change2.getChangeId());
     assertEquals(oldHead, getRemoteHead());
+    assertCurrentRevision(change2.getChangeId(), 1, change2.getCommitId());
+    assertSubmitter(change2.getChangeId(), 1);
   }
 
   @Test
-  public void submitOutOfOrder() throws JSchException, IOException,
-      GitAPIException {
+  public void submitOutOfOrder() throws Exception {
     Git git = createProject();
     RevCommit initialHead = getRemoteHead();
     PushOneCommit.Result change =
@@ -123,12 +131,16 @@
         createChange(git, "Change 3", "c.txt", "different content");
     submit(change3.getChangeId());
     assertCherryPick(git, false);
-    assertEquals(oldHead, getRemoteHead().getParent(0));
+    RevCommit newHead = getRemoteHead();
+    assertEquals(oldHead, newHead.getParent(0));
+    assertApproved(change3.getChangeId());
+    assertCurrentRevision(change3.getChangeId(), 2, newHead);
+    assertSubmitter(change3.getChangeId(), 1);
+    assertSubmitter(change3.getChangeId(), 2);
   }
 
   @Test
-  public void submitOutOfOrder_Conflict() throws JSchException, IOException,
-      GitAPIException {
+  public void submitOutOfOrder_Conflict() throws Exception {
     Git git = createProject();
     RevCommit initialHead = getRemoteHead();
     PushOneCommit.Result change =
@@ -142,6 +154,8 @@
         createChange(git, "Change 3", "b.txt", "different content");
     submitWithConflict(change3.getChangeId());
     assertEquals(oldHead, getRemoteHead());
+    assertCurrentRevision(change3.getChangeId(), 1, change3.getCommitId());
+    assertSubmitter(change3.getChangeId(), 1);
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index 9d56e18..d6fbca4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -14,21 +14,16 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.checkout;
+import static com.google.gerrit.acceptance.GitUtil.checkout;
 import static org.junit.Assert.assertEquals;
 
-import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 
-import com.jcraft.jsch.JSchException;
-
 import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Test;
 
-import java.io.IOException;
-
 public class SubmitByFastForwardIT extends AbstractSubmit {
 
   @Override
@@ -37,8 +32,7 @@
   }
 
   @Test
-  public void submitWithFastForward() throws JSchException, IOException,
-      GitAPIException {
+  public void submitWithFastForward() throws Exception {
     Git git = createProject();
     RevCommit oldHead = getRemoteHead();
     PushOneCommit.Result change = createChange(git);
@@ -46,11 +40,11 @@
     RevCommit head = getRemoteHead();
     assertEquals(change.getCommitId(), head.getId());
     assertEquals(oldHead, head.getParent(0));
+    assertSubmitter(change.getChangeId(), 1);
   }
 
   @Test
-  public void submitFastForwardNotPossible_Conflict() throws JSchException, IOException,
-      GitAPIException {
+  public void submitFastForwardNotPossible_Conflict() throws Exception {
     Git git = createProject();
     RevCommit initialHead = getRemoteHead();
     PushOneCommit.Result change =
@@ -63,5 +57,6 @@
         createChange(git, "Change 2", "b.txt", "other content");
     submitWithConflict(change2.getChangeId());
     assertEquals(oldHead, getRemoteHead());
+    assertSubmitter(change.getChangeId(), 1);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
index e0b7405..2a01d84 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.checkout;
+import static com.google.gerrit.acceptance.GitUtil.checkout;
 import static org.junit.Assert.assertEquals;
 
-import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gwtorm.server.OrmException;
 
@@ -39,8 +39,7 @@
   }
 
   @Test
-  public void submitWithMergeIfFastForwardPossible() throws JSchException,
-      IOException, GitAPIException {
+  public void submitWithMergeIfFastForwardPossible() throws Exception {
     Git git = createProject();
     RevCommit oldHead = getRemoteHead();
     PushOneCommit.Result change = createChange(git);
@@ -49,6 +48,7 @@
     assertEquals(2, head.getParentCount());
     assertEquals(oldHead, head.getParent(0));
     assertEquals(change.getCommitId(), head.getParent(1));
+    assertSubmitter(change.getChangeId(), 1);
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
index 9996f3d..486c529 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -1,9 +1,9 @@
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.checkout;
+import static com.google.gerrit.acceptance.GitUtil.checkout;
 import static org.junit.Assert.assertEquals;
 
-import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gwtorm.server.OrmException;
 
@@ -25,8 +25,7 @@
   }
 
   @Test
-  public void submitWithFastForward() throws JSchException, IOException,
-      GitAPIException {
+  public void submitWithFastForward() throws Exception {
     Git git = createProject();
     RevCommit oldHead = getRemoteHead();
     PushOneCommit.Result change = createChange(git);
@@ -34,6 +33,7 @@
     RevCommit head = getRemoteHead();
     assertEquals(change.getCommitId(), head.getId());
     assertEquals(oldHead, head.getParent(0));
+    assertSubmitter(change.getChangeId(), 1);
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
index 07594a6..8968084 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
@@ -14,21 +14,16 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.checkout;
+import static com.google.gerrit.acceptance.GitUtil.checkout;
 import static org.junit.Assert.assertEquals;
 
-import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 
-import com.jcraft.jsch.JSchException;
-
 import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Test;
 
-import java.io.IOException;
-
 public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
 
   @Override
@@ -37,8 +32,7 @@
   }
 
   @Test
-  public void submitWithFastForward() throws JSchException, IOException,
-      GitAPIException {
+  public void submitWithFastForward() throws Exception {
     Git git = createProject();
     RevCommit oldHead = getRemoteHead();
     PushOneCommit.Result change = createChange(git);
@@ -46,11 +40,13 @@
     RevCommit head = getRemoteHead();
     assertEquals(change.getCommitId(), head.getId());
     assertEquals(oldHead, head.getParent(0));
+    assertApproved(change.getChangeId());
+    assertCurrentRevision(change.getChangeId(), 1, head);
+    assertSubmitter(change.getChangeId(), 1);
   }
 
   @Test
-  public void submitWithRebase() throws JSchException, IOException,
-      GitAPIException {
+  public void submitWithRebase() throws Exception {
     Git git = createProject();
     RevCommit initialHead = getRemoteHead();
     PushOneCommit.Result change =
@@ -63,12 +59,16 @@
         createChange(git, "Change 2", "b.txt", "other content");
     submit(change2.getChangeId());
     assertRebase(git, false);
-    assertEquals(oldHead, getRemoteHead().getParent(0));
+    RevCommit head = getRemoteHead();
+    assertEquals(oldHead, head.getParent(0));
+    assertApproved(change2.getChangeId());
+    assertCurrentRevision(change2.getChangeId(), 2, head);
+    assertSubmitter(change2.getChangeId(), 1);
+    assertSubmitter(change2.getChangeId(), 2);
   }
 
   @Test
-  public void submitWithContentMerge() throws JSchException, IOException,
-      GitAPIException {
+  public void submitWithContentMerge() throws Exception {
     Git git = createProject();
     setUseContentMerge();
     PushOneCommit.Result change =
@@ -84,12 +84,16 @@
         createChange(git, "Change 3", "a.txt", "bbb\nccc\n");
     submit(change3.getChangeId());
     assertRebase(git, true);
-    assertEquals(oldHead, getRemoteHead().getParent(0));
+    RevCommit head = getRemoteHead();
+    assertEquals(oldHead, head.getParent(0));
+    assertApproved(change3.getChangeId());
+    assertCurrentRevision(change3.getChangeId(), 2, head);
+    assertSubmitter(change3.getChangeId(), 1);
+    assertSubmitter(change3.getChangeId(), 2);
   }
 
   @Test
-  public void submitWithContentMerge_Conflict() throws JSchException,
-      IOException, GitAPIException {
+  public void submitWithContentMerge_Conflict() throws Exception {
     Git git = createProject();
     setUseContentMerge();
     RevCommit initialHead = getRemoteHead();
@@ -104,5 +108,7 @@
     submitWithConflict(change2.getChangeId());
     RevCommit head = getRemoteHead();
     assertEquals(oldHead, head);
+    assertCurrentRevision(change2.getChangeId(), 1, change2.getCommitId());
+    assertSubmitter(change2.getChangeId(), 1);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewerInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewerInfo.java
deleted file mode 100644
index 212ee86..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewerInfo.java
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.change;
-
-public class SuggestReviewerInfo {
-  public AccountInfo account;
-  public GroupInfo group;
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
index 1ed4d61..7266c97 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
@@ -14,82 +14,74 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.GerritConfigs;
 import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
 import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.PushOneCommit;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gson.Gson;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.CreateGroupArgs;
+import com.google.gerrit.server.account.PerformCreateGroup;
+import com.google.gerrit.server.change.SuggestReviewers.SuggestedReviewerInfo;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gson.reflect.TypeToken;
-import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 
-import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
 
 public class SuggestReviewersIT extends AbstractDaemonTest {
+  @Inject
+  private PerformCreateGroup.Factory createGroupFactory;
 
   @Inject
-  private AccountCreator accounts;
+  private MetaDataUpdate.Server metaDataUpdateFactory;
 
   @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
+  private AllProjectsName allProjects;
 
-  private TestAccount admin;
-  private RestSession session;
-  private Git git;
-  private ReviewDb db;
-  private Project.NameKey project;
+  @Inject
+  private ProjectCache projectCache;
+
+  private AccountGroup group1;
+  private TestAccount user1;
+  private TestAccount user2;
+  private TestAccount user3;
 
   @Before
   public void setUp() throws Exception {
-    admin = accounts.admin();
-    session = new RestSession(server, admin);
-
-    group("users1");
+    group1 = group("users1");
     group("users2");
     group("users3");
 
-    accounts.create("user1", "user1@example.com", "User1", "users1");
-    accounts.create("user2", "user2@example.com", "User2", "users2");
-    accounts.create("user3", "user3@example.com", "User3", "users1", "users2");
-
-    initSsh(admin);
-    project = new Project.NameKey("p");
-    SshSession sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
-    git = cloneProject(sshSession.getUrl() + "/" + project.get());
-    sshSession.close();
-    db = reviewDbProvider.open();
-  }
-
-  @After
-  public void cleanup() {
-    db.close();
+    user1 = accounts.create("user1", "user1@example.com", "User1", "users1");
+    user2 = accounts.create("user2", "user2@example.com", "User2", "users2");
+    user3 = accounts.create("user3", "user3@example.com", "User3",
+        "users1", "users2");
   }
 
   @Test
   @GerritConfig(name = "suggest.accounts", value = "false")
   public void suggestReviewersNoResult1() throws GitAPIException, IOException,
       Exception {
-    String changeId = createChange(admin);
-    List<SuggestReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
+    String changeId = createChange().getChangeId();
+    List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
     assertEquals(reviewers.size(), 0);
   }
 
@@ -97,12 +89,12 @@
   @GerritConfigs(
       {@GerritConfig(name = "suggest.accounts", value = "true"),
        @GerritConfig(name = "suggest.from", value = "1"),
-       @GerritConfig(name = "accounts.visibility", value = "NONE"),
+       @GerritConfig(name = "accounts.visibility", value = "NONE")
       })
   public void suggestReviewersNoResult2() throws GitAPIException, IOException,
       Exception {
-    String changeId = createChange(admin);
-    List<SuggestReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
+    String changeId = createChange().getChangeId();
+    List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
     assertEquals(reviewers.size(), 0);
   }
 
@@ -110,16 +102,16 @@
   @GerritConfig(name = "suggest.from", value = "2")
   public void suggestReviewersNoResult3() throws GitAPIException, IOException,
       Exception {
-    String changeId = createChange(admin);
-    List<SuggestReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
+    String changeId = createChange().getChangeId();
+    List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
     assertEquals(reviewers.size(), 0);
   }
 
   @Test
   public void suggestReviewersChange() throws GitAPIException,
       IOException, Exception {
-    String changeId = createChange(admin);
-    List<SuggestReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
+    String changeId = createChange().getChangeId();
+    List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "u", 6);
     assertEquals(reviewers.size(), 6);
     reviewers = suggestReviewers(changeId, "u", 5);
     assertEquals(reviewers.size(), 5);
@@ -127,10 +119,51 @@
     assertEquals(reviewers.size(), 1);
   }
 
-  private List<SuggestReviewerInfo> suggestReviewers(String changeId,
-      String query, int n)
-      throws IOException {
-    return new Gson().fromJson(
+  @Test
+  @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
+  public void suggestReviewersSameGroupVisibility() throws Exception {
+    String changeId = createChange().getChangeId();
+    List<SuggestedReviewerInfo> reviewers;
+
+    reviewers = suggestReviewers(changeId, "user2", 2);
+    assertEquals(1, reviewers.size());
+    assertEquals("User2", Iterables.getOnlyElement(reviewers).account.name);
+
+    reviewers = suggestReviewers(new RestSession(server, user1),
+        changeId, "user2", 2);
+    assertTrue(reviewers.isEmpty());
+
+    reviewers = suggestReviewers(new RestSession(server, user2),
+        changeId, "user2", 2);
+    assertEquals(1, reviewers.size());
+    assertEquals("User2", Iterables.getOnlyElement(reviewers).account.name);
+
+    reviewers = suggestReviewers(new RestSession(server, user3),
+        changeId, "user2", 2);
+    assertEquals(1, reviewers.size());
+    assertEquals("User2", Iterables.getOnlyElement(reviewers).account.name);
+  }
+
+  @Test
+  @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
+  public void suggestReviewersViewAllAccounts() throws Exception {
+    String changeId = createChange().getChangeId();
+    List<SuggestedReviewerInfo> reviewers;
+
+    reviewers = suggestReviewers(new RestSession(server, user1),
+        changeId, "user2", 2);
+    assertTrue(reviewers.isEmpty());
+
+    grantCapability(GlobalCapability.VIEW_ALL_ACCOUNTS, group1);
+    reviewers = suggestReviewers(new RestSession(server, user1),
+        changeId, "user2", 2);
+    assertEquals(1, reviewers.size());
+    assertEquals("User2", Iterables.getOnlyElement(reviewers).account.name);
+  }
+
+  private List<SuggestedReviewerInfo> suggestReviewers(RestSession session,
+      String changeId, String query, int n) throws IOException {
+    return newGson().fromJson(
         session.get("/changes/"
             + changeId
             + "/suggest_reviewers?q="
@@ -138,17 +171,31 @@
             + "&n="
             + n)
         .getReader(),
-        new TypeToken<List<SuggestReviewerInfo>>() {}
+        new TypeToken<List<SuggestedReviewerInfo>>() {}
         .getType());
   }
 
-  private void group(String name) throws IOException {
-    session.put("/groups/" + name, new Object()).consume();
+  private List<SuggestedReviewerInfo> suggestReviewers(String changeId,
+      String query, int n) throws IOException {
+    return suggestReviewers(adminSession, changeId, query, n);
   }
 
-  private String createChange(TestAccount account) throws GitAPIException,
-      IOException {
-    PushOneCommit push = new PushOneCommit(db, account.getIdent());
-    return push.to(git, "refs/for/master").getChangeId();
+  private AccountGroup group(String name) throws Exception {
+    CreateGroupArgs args = new CreateGroupArgs();
+    args.setGroupName(name);
+    args.initialMembers = Collections.singleton(admin.getId());
+    return createGroupFactory.create(args).createGroup();
   }
-}
\ No newline at end of file
+
+  private void grantCapability(String name, AccountGroup group)
+      throws Exception {
+    MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
+    ProjectConfig config = ProjectConfig.read(md);
+    AccessSection s = config.getAccessSection(
+        AccessSection.GLOBAL_CAPABILITIES);
+    Permission p = s.getPermission(name, true);
+    p.add(new PermissionRule(config.resolve(group)));
+    config.commit(md);
+    projectCache.evict(config.getProject());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddRemoveGroupMembersIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddRemoveGroupMembersIT.java
index 08211d0..3646e57 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddRemoveGroupMembersIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/AddRemoveGroupMembersIT.java
@@ -25,18 +25,19 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
 import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.rest.account.AccountInfo;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupById;
 import com.google.gerrit.reviewdb.client.AccountGroupMember;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AccountInfo;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gson.Gson;
+import com.google.gerrit.server.group.AddIncludedGroups;
+import com.google.gerrit.server.group.AddMembers;
+import com.google.gerrit.server.group.CreateGroup;
+import com.google.gerrit.server.group.GroupJson.GroupInfo;
 import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
@@ -57,22 +58,15 @@
 public class AddRemoveGroupMembersIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private SchemaFactory<ReviewDb> reviewDbProvider;
 
   @Inject
   private GroupCache groupCache;
 
-  private RestSession session;
-  private TestAccount admin;
   private ReviewDb db;
 
   @Before
   public void setUp() throws Exception {
-    admin = accounts.create("admin", "Administrators");
-    session = new RestSession(server, admin);
     db = reviewDbProvider.open();
   }
 
@@ -95,13 +89,11 @@
 
   @Test
   public void addRemoveMember() throws Exception {
-    TestAccount u = accounts.create("user", "user@example.com", "Full Name");
     RestResponse r = PUT("/groups/Administrators/members/user");
     assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
-    AccountInfo ai = (new Gson()).fromJson(r.getReader(),
-        new TypeToken<AccountInfo>() {}.getType());
-    assertAccountInfo(u, ai);
-    assertMembers("Administrators", admin, u);
+    AccountInfo ai = newGson().fromJson(r.getReader(), AccountInfo.class);
+    assertAccountInfo(user, ai);
+    assertMembers("Administrators", admin, user);
     r.consume();
 
     assertEquals(HttpStatus.SC_NO_CONTENT,
@@ -120,12 +112,12 @@
     group("users");
     TestAccount u1 = accounts.create("u1", "u1@example.com", "Full Name 1");
     TestAccount u2 = accounts.create("u2", "u2@example.com", "Full Name 2");
-    MembersInput input = new MembersInput();
-    input.members = Lists.newLinkedList();
-    input.members.add(u1.username);
-    input.members.add(u2.username);
+    List<String> members = Lists.newLinkedList();
+    members.add(u1.username);
+    members.add(u2.username);
+    AddMembers.Input input = AddMembers.Input.fromMembers(members);
     RestResponse r = POST("/groups/users/members", input);
-    List<AccountInfo> ai = (new Gson()).fromJson(r.getReader(),
+    List<AccountInfo> ai = newGson().fromJson(r.getReader(),
         new TypeToken<List<AccountInfo>>() {}.getType());
     assertMembers(ai, u1, u2);
   }
@@ -135,7 +127,7 @@
     group("newGroup");
     RestResponse r = PUT("/groups/Administrators/groups/newGroup");
     assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
-    GroupInfo i = (new Gson()).fromJson(r.getReader(), new TypeToken<GroupInfo>() {}.getType());
+    GroupInfo i = newGson().fromJson(r.getReader(), GroupInfo.class);
     r.consume();
     assertGroupInfo(groupCache.get(new AccountGroup.NameKey("newGroup")), i);
     assertIncludes("Administrators", "newGroup");
@@ -157,37 +149,39 @@
   public void addMultipleIncludes() throws Exception {
     group("newGroup1");
     group("newGroup2");
-    GroupsInput input = new GroupsInput();
-    input.groups = Lists.newLinkedList();
-    input.groups.add("newGroup1");
-    input.groups.add("newGroup2");
+    List<String> groups = Lists.newLinkedList();
+    groups.add("newGroup1");
+    groups.add("newGroup2");
+    AddIncludedGroups.Input input = AddIncludedGroups.Input.fromGroups(groups);
     RestResponse r = POST("/groups/Administrators/groups", input);
-    List<GroupInfo> gi = (new Gson()).fromJson(r.getReader(),
+    List<GroupInfo> gi = newGson().fromJson(r.getReader(),
         new TypeToken<List<GroupInfo>>() {}.getType());
     assertIncludes(gi, "newGroup1", "newGroup2");
   }
 
   private RestResponse PUT(String endpoint) throws IOException {
-    return session.put(endpoint);
+    return adminSession.put(endpoint);
   }
 
   private int DELETE(String endpoint) throws IOException {
-    RestResponse r = session.delete(endpoint);
+    RestResponse r = adminSession.delete(endpoint);
     r.consume();
     return r.getStatusCode();
   }
 
-  private RestResponse POST(String endPoint, MembersInput mi) throws IOException {
-    return session.post(endPoint, mi);
+  private RestResponse POST(String endPoint, AddMembers.Input mi)
+      throws IOException {
+    return adminSession.post(endPoint, mi);
   }
 
-  private RestResponse POST(String endPoint, GroupsInput gi) throws IOException {
-    return session.post(endPoint, gi);
+  private RestResponse POST(String endPoint, AddIncludedGroups.Input gi)
+      throws IOException {
+    return adminSession.post(endPoint, gi);
   }
 
   private void group(String name) throws IOException {
-    GroupInput in = new GroupInput();
-    session.put("/groups/" + name, in).consume();
+    CreateGroup.Input in = new CreateGroup.Input();
+    adminSession.put("/groups/" + name, in).consume();
   }
 
   private void assertMembers(String group, TestAccount... members)
@@ -208,7 +202,7 @@
   private void assertMembers(List<AccountInfo> ai, TestAccount... members) {
     Map<Integer, AccountInfo> infoById = Maps.newHashMap();
     for (AccountInfo i : ai) {
-      infoById.put(i._account_id, i);
+      infoById.put(i._accountId, i);
     }
 
     for (TestAccount a : members) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
index b6e017d..108cc8d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/BUCK
@@ -10,17 +10,11 @@
 
 java_library(
   name = 'util',
-  srcs = [
-    'GroupAssert.java',
-    'GroupInfo.java',
-    'GroupInput.java',
-    'GroupOptionsInfo.java',
-    'GroupsInput.java',
-    'MembersInput.java',
-  ],
+  srcs = ['GroupAssert.java'],
   deps = [
     '//gerrit-extension-api:api',
     '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
     '//lib:gwtorm',
     '//lib:junit',
   ],
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/CreateGroupIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/CreateGroupIT.java
index b7ab0fc..a6954b2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/CreateGroupIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/CreateGroupIT.java
@@ -15,26 +15,22 @@
 package com.google.gerrit.acceptance.rest.group;
 
 import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroupInfo;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
+import com.google.gerrit.server.group.CreateGroup;
+import com.google.gerrit.server.group.GroupJson.GroupInfo;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
 import com.jcraft.jsch.JSchException;
 
 import org.apache.http.HttpStatus;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -42,26 +38,13 @@
 public class CreateGroupIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private GroupCache groupCache;
 
-  private TestAccount admin;
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void testCreateGroup() throws IOException {
     final String newGroupName = "newGroup";
-    RestResponse r = session.put("/groups/" + newGroupName);
-    GroupInfo g = (new Gson()).fromJson(r.getReader(), new TypeToken<GroupInfo>() {}.getType());
+    RestResponse r = adminSession.put("/groups/" + newGroupName);
+    GroupInfo g = newGson().fromJson(r.getReader(), GroupInfo.class);
     assertEquals(newGroupName, g.name);
     AccountGroup group = groupCache.get(new AccountGroup.NameKey(newGroupName));
     assertNotNull(group);
@@ -71,23 +54,22 @@
   @Test
   public void testCreateGroupWithProperties() throws IOException {
     final String newGroupName = "newGroup";
-    GroupInput in = new GroupInput();
+    CreateGroup.Input in = new CreateGroup.Input();
     in.description = "Test description";
-    in.visible_to_all = true;
-    in.owner_id = groupCache.get(new AccountGroup.NameKey("Administrators")).getGroupUUID().get();
-    RestResponse r = session.put("/groups/" + newGroupName, in);
-    GroupInfo g = (new Gson()).fromJson(r.getReader(), new TypeToken<GroupInfo>() {}.getType());
+    in.visibleToAll = true;
+    in.ownerId = groupCache.get(new AccountGroup.NameKey("Administrators")).getGroupUUID().get();
+    RestResponse r = adminSession.put("/groups/" + newGroupName, in);
+    GroupInfo g = newGson().fromJson(r.getReader(), GroupInfo.class);
     assertEquals(newGroupName, g.name);
     AccountGroup group = groupCache.get(new AccountGroup.NameKey(newGroupName));
     assertEquals(in.description, group.getDescription());
-    assertEquals(in.visible_to_all, group.isVisibleToAll());
-    assertEquals(in.owner_id, group.getOwnerGroupUUID().get());
+    assertEquals(in.visibleToAll, group.isVisibleToAll());
+    assertEquals(in.ownerId, group.getOwnerGroupUUID().get());
   }
 
   @Test
   public void testCreateGroupWithoutCapability_Forbidden() throws OrmException,
       JSchException, IOException {
-    TestAccount user = accounts.create("user", "user@example.com", "User");
     RestResponse r = (new RestSession(server, user)).put("/groups/newGroup");
     assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
   }
@@ -95,7 +77,7 @@
   @Test
   public void testCreateGroupWhenGroupAlreadyExists_Conflict()
       throws OrmException, JSchException, IOException {
-    RestResponse r = session.put("/groups/Administrators");
+    RestResponse r = adminSession.put("/groups/Administrators");
     assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java
new file mode 100644
index 0000000..0d229ca
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/DefaultGroupsIT.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.group;
+
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.group.GroupJson.GroupInfo;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.OrmException;
+
+import com.jcraft.jsch.JSchException;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An example test that tests presence of default groups in a newly initialized
+ * review site.
+ *
+ * The test shows how to perform these checks via SSH, REST or using Gerrit
+ * internals.
+ */
+public class DefaultGroupsIT extends AbstractDaemonTest {
+
+  @Test
+  public void defaultGroupsCreated_ssh() throws JSchException, IOException {
+    SshSession session = new SshSession(server, admin);
+    String result = session.exec("gerrit ls-groups");
+    assertTrue(result.contains("Administrators"));
+    assertTrue(result.contains("Non-Interactive Users"));
+    session.close();
+  }
+
+  @Test
+  public void defaultGroupsCreated_rest() throws IOException {
+    RestSession session = new RestSession(server, admin);
+    RestResponse r = session.get("/groups/");
+    Map<String, GroupInfo> result =
+        newGson().fromJson(r.getReader(),
+            new TypeToken<Map<String, GroupInfo>>() {}.getType());
+    Set<String> names = result.keySet();
+    assertTrue(names.contains("Administrators"));
+    assertTrue(names.contains("Non-Interactive Users"));
+  }
+
+  @Test
+  public void defaultGroupsCreated_internals() throws OrmException {
+    Set<String> names = Sets.newHashSet();
+    for (AccountGroup g : db.accountGroups().all()) {
+      names.add(g.getName());
+    }
+    assertTrue(names.contains("Administrators"));
+    assertTrue(names.contains("Non-Interactive Users"));
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GetGroupIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GetGroupIT.java
index 67a7197..91c9898 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GetGroupIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GetGroupIT.java
@@ -17,17 +17,12 @@
 import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroupInfo;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
+import com.google.gerrit.server.group.GroupJson.GroupInfo;
 import com.google.inject.Inject;
 
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -35,21 +30,8 @@
 public class GetGroupIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private GroupCache groupCache;
 
-  private TestAccount admin;
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void testGetGroup() throws IOException {
     AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators"));
@@ -64,9 +46,10 @@
     testGetGroup("/groups/" + adminGroup.getId().get(), adminGroup);
   }
 
-  private void testGetGroup(String url, AccountGroup expectedGroup) throws IOException {
-    RestResponse r = session.get(url);
-    GroupInfo group = (new Gson()).fromJson(r.getReader(), new TypeToken<GroupInfo>() {}.getType());
+  private void testGetGroup(String url, AccountGroup expectedGroup)
+      throws IOException {
+    RestResponse r = adminSession.get(url);
+    GroupInfo group = newGson().fromJson(r.getReader(), GroupInfo.class);
     assertGroupInfo(expectedGroup, group);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupAssert.java
index 792d8c6..70107fe 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupAssert.java
@@ -19,6 +19,7 @@
 
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.group.GroupJson.GroupInfo;
 
 import java.util.Set;
 
@@ -37,11 +38,11 @@
       assertEquals(group.getName(), info.name);
     }
     assertEquals(group.getGroupUUID().get(), Url.decode(info.id));
-    assertEquals(Integer.valueOf(group.getId().get()), info.group_id);
+    assertEquals(Integer.valueOf(group.getId().get()), info.groupId);
     assertEquals("#/admin/groups/uuid-" + Url.encode(group.getGroupUUID().get()), info.url);
-    assertEquals(group.isVisibleToAll(), toBoolean(info.options.visible_to_all));
+    assertEquals(group.isVisibleToAll(), toBoolean(info.options.visibleToAll));
     assertEquals(group.getDescription(), info.description);
-    assertEquals(group.getOwnerGroupUUID().get(), Url.decode(info.owner_id));
+    assertEquals(group.getOwnerGroupUUID().get(), Url.decode(info.ownerId));
   }
 
   public static boolean toBoolean(Boolean b) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInput.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInput.java
deleted file mode 100644
index 86864839..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInput.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.group;
-
-public class GroupInput {
-  String description;
-  Boolean visible_to_all;
-  String owner_id;
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupPropertiesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupPropertiesIT.java
index 05d74a3..c630919 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupPropertiesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupPropertiesIT.java
@@ -14,26 +14,27 @@
 
 package com.google.gerrit.acceptance.rest.group;
 
-import static com.google.gerrit.acceptance.rest.group.GroupAssert.toBoolean;
 import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroupInfo;
-
+import static com.google.gerrit.acceptance.rest.group.GroupAssert.toBoolean;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
+import com.google.gerrit.server.group.GroupJson.GroupInfo;
+import com.google.gerrit.server.group.GroupOptionsInfo;
+import com.google.gerrit.server.group.PutDescription;
+import com.google.gerrit.server.group.PutName;
+import com.google.gerrit.server.group.PutOptions;
+import com.google.gerrit.server.group.PutOwner;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.inject.Inject;
 
 import org.apache.http.HttpStatus;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -41,52 +42,43 @@
 public class GroupPropertiesIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private GroupCache groupCache;
 
-  private TestAccount admin;
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void testGroupName() throws IOException {
     AccountGroup.NameKey adminGroupName = new AccountGroup.NameKey("Administrators");
     String url = "/groups/" + groupCache.get(adminGroupName).getGroupUUID().get() + "/name";
 
     // get name
-    RestResponse r = session.get(url);
-    String name = (new Gson()).fromJson(r.getReader(), new TypeToken<String>() {}.getType());
+    RestResponse r = adminSession.get(url);
+    String name = newGson().fromJson(r.getReader(), String.class);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     assertEquals("Administrators", name);
     r.consume();
 
     // set name with name conflict
-    GroupNameInput in = new GroupNameInput();
-    in.name = "Registered Users";
-    r = session.put(url, in);
+    String newGroupName = "newGroup";
+    r = adminSession.put("/groups/" + newGroupName);
+    r.consume();
+    assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+    PutName.Input in = new PutName.Input();
+    in.name = newGroupName;
+    r = adminSession.put(url, in);
     assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
     r.consume();
 
     // set name to same name
-    in = new GroupNameInput();
+    in = new PutName.Input();
     in.name = "Administrators";
-    r = session.put(url, in);
+    r = adminSession.put(url, in);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     r.consume();
 
     // rename
-    in = new GroupNameInput();
+    in = new PutName.Input();
     in.name = "Admins";
-    r = session.put(url, in);
-    String newName = (new Gson()).fromJson(r.getReader(), new TypeToken<String>() {}.getType());
+    r = adminSession.put(url, in);
+    String newName = newGson().fromJson(r.getReader(), String.class);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     assertNotNull(groupCache.get(new AccountGroup.NameKey(in.name)));
     assertNull(groupCache.get(adminGroupName));
@@ -101,17 +93,17 @@
     String url = "/groups/" + adminGroup.getGroupUUID().get() + "/description";
 
     // get description
-    RestResponse r = session.get(url);
-    String description = (new Gson()).fromJson(r.getReader(), new TypeToken<String>() {}.getType());
+    RestResponse r = adminSession.get(url);
+    String description = newGson().fromJson(r.getReader(), String.class);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     assertEquals(adminGroup.getDescription(), description);
     r.consume();
 
     // set description
-    GroupDescriptionInput in = new GroupDescriptionInput();
+    PutDescription.Input in = new PutDescription.Input();
     in.description = "All users that can administrate the Gerrit Server.";
-    r = session.put(url, in);
-    String newDescription = (new Gson()).fromJson(r.getReader(), new TypeToken<String>() {}.getType());
+    r = adminSession.put(url, in);
+    String newDescription = newGson().fromJson(r.getReader(), String.class);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     assertEquals(in.description, newDescription);
     adminGroup = groupCache.get(adminGroupName);
@@ -119,15 +111,15 @@
     r.consume();
 
     // delete description
-    r = session.delete(url);
+    r = adminSession.delete(url);
     assertEquals(HttpStatus.SC_NO_CONTENT, r.getStatusCode());
     adminGroup = groupCache.get(adminGroupName);
     assertNull(adminGroup.getDescription());
 
     // set description to empty string
-    in = new GroupDescriptionInput();
+    in = new PutDescription.Input();
     in.description = "";
-    r = session.put(url, in);
+    r = adminSession.put(url, in);
     assertEquals(HttpStatus.SC_NO_CONTENT, r.getStatusCode());
     adminGroup = groupCache.get(adminGroupName);
     assertNull(adminGroup.getDescription());
@@ -140,21 +132,21 @@
     String url = "/groups/" + adminGroup.getGroupUUID().get() + "/options";
 
     // get options
-    RestResponse r = session.get(url);
-    GroupOptionsInfo options = (new Gson()).fromJson(r.getReader(), new TypeToken<GroupOptionsInfo>() {}.getType());
+    RestResponse r = adminSession.get(url);
+    GroupOptionsInfo options = newGson().fromJson(r.getReader(), GroupOptionsInfo.class);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    assertEquals(adminGroup.isVisibleToAll(), toBoolean(options.visible_to_all));
+    assertEquals(adminGroup.isVisibleToAll(), toBoolean(options.visibleToAll));
     r.consume();
 
     // set options
-    GroupOptionsInput in = new GroupOptionsInput();
-    in.visible_to_all = !adminGroup.isVisibleToAll();
-    r = session.put(url, in);
-    GroupOptionsInfo newOptions = (new Gson()).fromJson(r.getReader(), new TypeToken<GroupOptionsInfo>() {}.getType());
+    PutOptions.Input in = new PutOptions.Input();
+    in.visibleToAll = !adminGroup.isVisibleToAll();
+    r = adminSession.put(url, in);
+    GroupOptionsInfo newOptions = newGson().fromJson(r.getReader(), GroupOptionsInfo.class);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    assertEquals(in.visible_to_all, toBoolean(newOptions.visible_to_all));
+    assertEquals(in.visibleToAll, toBoolean(newOptions.visibleToAll));
     adminGroup = groupCache.get(adminGroupName);
-    assertEquals(in.visible_to_all, adminGroup.isVisibleToAll());
+    assertEquals(in.visibleToAll, adminGroup.isVisibleToAll());
     r.consume();
   }
 
@@ -165,53 +157,41 @@
     String url = "/groups/" + adminGroup.getGroupUUID().get() + "/owner";
 
     // get owner
-    RestResponse r = session.get(url);
-    GroupInfo options = (new Gson()).fromJson(r.getReader(), new TypeToken<GroupInfo>() {}.getType());
+    RestResponse r = adminSession.get(url);
+    GroupInfo options = newGson().fromJson(r.getReader(), GroupInfo.class);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     assertGroupInfo(groupCache.get(adminGroup.getOwnerGroupUUID()), options);
     r.consume();
 
     // set owner by name
-    GroupOwnerInput in = new GroupOwnerInput();
+    PutOwner.Input in = new PutOwner.Input();
     in.owner = "Registered Users";
-    r = session.put(url, in);
-    GroupInfo newOwner = (new Gson()).fromJson(r.getReader(), new TypeToken<GroupInfo>() {}.getType());
+    r = adminSession.put(url, in);
+    GroupInfo newOwner = newGson().fromJson(r.getReader(), GroupInfo.class);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     assertEquals(in.owner, newOwner.name);
-    adminGroup = groupCache.get(adminGroupName);
-    assertGroupInfo(groupCache.get(adminGroup.getOwnerGroupUUID()), newOwner);
+    assertEquals(
+        SystemGroupBackend.getGroup(SystemGroupBackend.REGISTERED_USERS).getName(),
+        newOwner.name);
+    assertEquals(
+        SystemGroupBackend.REGISTERED_USERS.get(),
+        Url.decode(newOwner.id));
     r.consume();
 
     // set owner by UUID
-    in = new GroupOwnerInput();
+    in = new PutOwner.Input();
     in.owner = adminGroup.getGroupUUID().get();
-    r = session.put(url, in);
+    r = adminSession.put(url, in);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     adminGroup = groupCache.get(adminGroupName);
     assertEquals(in.owner, groupCache.get(adminGroup.getOwnerGroupUUID()).getGroupUUID().get());
     r.consume();
 
     // set non existing owner
-    in = new GroupOwnerInput();
+    in = new PutOwner.Input();
     in.owner = "Non-Existing Group";
-    r = session.put(url, in);
+    r = adminSession.put(url, in);
     assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
     r.consume();
   }
-
-  private static class GroupNameInput {
-    String name;
-  }
-
-  private static class GroupDescriptionInput {
-    String description;
-  }
-
-  private static class GroupOptionsInput {
-    Boolean visible_to_all;
-  }
-
-  private static class GroupOwnerInput {
-    String owner;
-  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupsInput.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupsInput.java
deleted file mode 100644
index 1eceda9..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupsInput.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.group;
-
-import java.util.List;
-
-public class GroupsInput {
-  List<String> groups;
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupIncludesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupIncludesIT.java
index f4c289c..d639f54 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupIncludesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupIncludesIT.java
@@ -20,17 +20,13 @@
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.common.Nullable;
-import com.google.gson.Gson;
+import com.google.gerrit.server.group.CreateGroup;
+import com.google.gerrit.server.group.GroupJson.GroupInfo;
 import com.google.gson.reflect.TypeToken;
-import com.google.inject.Inject;
 
 import org.apache.http.HttpStatus;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -39,21 +35,10 @@
 
 public class ListGroupIncludesIT extends AbstractDaemonTest {
 
-  @Inject
-  private AccountCreator accounts;
-
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    TestAccount admin = accounts.create("admin", "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void listNonExistingGroupIncludes_NotFound() throws Exception {
     assertEquals(HttpStatus.SC_NOT_FOUND,
-      session.get("/groups/non-existing/groups/").getStatusCode());
+      adminSession.get("/groups/non-existing/groups/").getStatusCode());
   }
 
   @Test
@@ -82,27 +67,26 @@
   }
 
   private List<GroupInfo> GET(String endpoint) throws IOException {
-    RestResponse r = session.get(endpoint);
+    RestResponse r = adminSession.get(endpoint);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    return (new Gson()).fromJson(r.getReader(),
+    return newGson().fromJson(r.getReader(),
         new TypeToken<List<GroupInfo>>() {}.getType());
   }
 
   private GroupInfo GET_ONE(String endpoint) throws IOException {
-    RestResponse r = session.get(endpoint);
+    RestResponse r = adminSession.get(endpoint);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    return (new Gson()).fromJson(r.getReader(),
-        new TypeToken<GroupInfo>() {}.getType());
+    return newGson().fromJson(r.getReader(), GroupInfo.class);
   }
 
   private void PUT(String endpoint) throws IOException {
-    session.put(endpoint).consume();
+    adminSession.put(endpoint).consume();
   }
 
   private void group(String name, String ownerGroup) throws IOException {
-    GroupInput in = new GroupInput();
-    in.owner_id = ownerGroup;
-    session.put("/groups/" + name, in).consume();
+    CreateGroup.Input in = new CreateGroup.Input();
+    in.ownerId = ownerGroup;
+    adminSession.put("/groups/" + name, in).consume();
   }
 
   private void assertIncludes(List<GroupInfo> includes, String name,
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupMembersIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupMembersIT.java
index 0e7cc71..4db13e5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupMembersIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupMembersIT.java
@@ -20,18 +20,13 @@
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.rest.account.AccountInfo;
 import com.google.gerrit.common.Nullable;
-import com.google.gson.Gson;
+import com.google.gerrit.server.account.AccountInfo;
+import com.google.gerrit.server.group.CreateGroup;
 import com.google.gson.reflect.TypeToken;
-import com.google.inject.Inject;
 
 import org.apache.http.HttpStatus;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -40,21 +35,10 @@
 
 public class ListGroupMembersIT extends AbstractDaemonTest {
 
-  @Inject
-  private AccountCreator accounts;
-
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    TestAccount admin = accounts.create("admin", "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void listNonExistingGroupMembers_NotFound() throws Exception {
     assertEquals(HttpStatus.SC_NOT_FOUND,
-        session.get("/groups/non-existing/members/").getStatusCode());
+        adminSession.get("/groups/non-existing/members/").getStatusCode());
   }
 
   @Test
@@ -65,16 +49,17 @@
 
   @Test
   public void listNonEmptyGroupMembers() throws Exception {
-    assertMembers(GET("/groups/Administrators/members/"), "admin");
+    assertMembers(GET("/groups/Administrators/members/"), admin.fullName);
 
     accounts.create("admin2", "Administrators");
-    assertMembers(GET("/groups/Administrators/members/"), "admin", "admin2");
+    assertMembers(GET("/groups/Administrators/members/"),
+        admin.fullName, "admin2");
   }
 
   @Test
   public void listOneGroupMember() throws IOException {
     assertEquals(GET_ONE("/groups/Administrators/members/admin").name,
-        "admin");
+        admin.fullName);
   }
 
   @Test
@@ -88,32 +73,31 @@
     PUT("/groups/Administrators/groups/gx");
     PUT("/groups/gx/groups/gy");
     assertMembers(GET("/groups/Administrators/members/?recursive"),
-        "admin", "ux", "uy");
+        admin.fullName, "ux", "uy");
   }
 
   private List<AccountInfo> GET(String endpoint) throws IOException {
-    RestResponse r = session.get(endpoint);
+    RestResponse r = adminSession.get(endpoint);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    return (new Gson()).fromJson(r.getReader(),
+    return newGson().fromJson(r.getReader(),
         new TypeToken<List<AccountInfo>>() {}.getType());
   }
 
   private AccountInfo GET_ONE(String endpoint) throws IOException {
-    RestResponse r = session.get(endpoint);
+    RestResponse r = adminSession.get(endpoint);
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    return (new Gson()).fromJson(r.getReader(),
-        new TypeToken<AccountInfo>() {}.getType());
+    return newGson().fromJson(r.getReader(), AccountInfo.class);
   }
 
   private void PUT(String endpoint) throws IOException {
-    session.put(endpoint).consume();
+    adminSession.put(endpoint).consume();
   }
 
   private void group(String name, String ownerGroup)
       throws IOException {
-    GroupInput in = new GroupInput();
-    in.owner_id = ownerGroup;
-    session.put("/groups/" + name, in).consume();
+    CreateGroup.Input in = new CreateGroup.Input();
+    in.ownerId = ownerGroup;
+    adminSession.put("/groups/" + name, in).consume();
   }
 
   private void assertMembers(List<AccountInfo> members, String name,
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java
index 4db2ac8..8b5dde6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java
@@ -16,26 +16,26 @@
 
 import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroupInfo;
 import static com.google.gerrit.acceptance.rest.group.GroupAssert.assertGroups;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gson.Gson;
+import com.google.gerrit.server.group.CreateGroup;
+import com.google.gerrit.server.group.GroupJson.GroupInfo;
 import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
 import com.jcraft.jsch.JSchException;
 
-import org.junit.Before;
+import org.apache.http.HttpStatus;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -45,21 +45,8 @@
 public class ListGroupsIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private GroupCache groupCache;
 
-  private TestAccount admin;
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void testListAllGroups() throws IOException, OrmException {
     Iterable<String> expectedGroups = Iterables.transform(groupCache.all(),
@@ -70,22 +57,38 @@
             return group.getName();
           }
         });
-    RestResponse r = session.get("/groups/");
+    RestResponse r = adminSession.get("/groups/");
     Map<String, GroupInfo> result =
-        (new Gson()).fromJson(r.getReader(), new TypeToken<Map<String, GroupInfo>>() {}.getType());
+        newGson().fromJson(r.getReader(),
+            new TypeToken<Map<String, GroupInfo>>() {}.getType());
     assertGroups(expectedGroups, result.keySet());
   }
 
   @Test
   public void testOnlyVisibleGroupsReturned() throws OrmException,
       JSchException, IOException {
-    Set<String> expectedGroups = Sets.newHashSet();
-    expectedGroups.add("Anonymous Users");
-    expectedGroups.add("Registered Users");
-    TestAccount user = accounts.create("user", "user@example.com", "User");
-    RestResponse r = new RestSession(server, user).get("/groups/");
+    String newGroupName = "newGroup";
+    CreateGroup.Input in = new CreateGroup.Input();
+    in.description = "a hidden group";
+    in.visibleToAll = false;
+    in.ownerId = groupCache.get(new AccountGroup.NameKey("Administrators"))
+        .getGroupUUID().get();
+    adminSession.put("/groups/" + newGroupName, in).consume();
+
+    Set<String> expectedGroups = Sets.newHashSet(newGroupName);
+    RestResponse r = userSession.get("/groups/");
     Map<String, GroupInfo> result =
-        (new Gson()).fromJson(r.getReader(), new TypeToken<Map<String, GroupInfo>>() {}.getType());
+        newGson().fromJson(r.getReader(),
+            new TypeToken<Map<String, GroupInfo>>() {}.getType());
+    assertTrue("no groups visible", result.isEmpty());
+
+    assertEquals(HttpStatus.SC_CREATED, adminSession.put(
+        String.format("/groups/%s/members/%s", newGroupName, user.username)
+      ).getStatusCode());
+
+    r = userSession.get("/groups/");
+    result = newGson().fromJson(r.getReader(),
+        new TypeToken<Map<String, GroupInfo>>() {}.getType());
     assertGroups(expectedGroups, result.keySet());
   }
 
@@ -93,9 +96,10 @@
   public void testAllGroupInfoFieldsSetCorrectly() throws IOException,
       OrmException {
     AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators"));
-    RestResponse r = session.get("/groups/?q=" + adminGroup.getName());
+    RestResponse r = adminSession.get("/groups/?q=" + adminGroup.getName());
     Map<String, GroupInfo> result =
-        (new Gson()).fromJson(r.getReader(), new TypeToken<Map<String, GroupInfo>>() {}.getType());
+        newGson().fromJson(r.getReader(),
+            new TypeToken<Map<String, GroupInfo>>() {}.getType());
     GroupInfo adminGroupInfo = result.get(adminGroup.getName());
     assertGroupInfo(adminGroup, adminGroupInfo);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/SystemGroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/SystemGroupsIT.java
deleted file mode 100644
index 764b7e8..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/SystemGroupsIT.java
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.group;
-
-import static org.junit.Assert.assertTrue;
-
-import com.google.common.collect.Sets;
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
-import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-
-import com.jcraft.jsch.JSchException;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * An example test that tests presence of system groups in a newly initialized
- * review site.
- *
- * The test shows how to perform these checks via SSH, REST or using Gerrit
- * internals.
- */
-public class SystemGroupsIT extends AbstractDaemonTest {
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  @Inject
-  private AccountCreator accounts;
-
-  protected TestAccount admin;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@sap.com", "Administrator",
-            "Administrators");
-  }
-
-  @Test
-  public void systemGroupsCreated_ssh() throws JSchException, IOException {
-    SshSession session = new SshSession(server, admin);
-    String result = session.exec("gerrit ls-groups");
-    assertTrue(result.contains("Administrators"));
-    assertTrue(result.contains("Anonymous Users"));
-    assertTrue(result.contains("Non-Interactive Users"));
-    assertTrue(result.contains("Project Owners"));
-    assertTrue(result.contains("Registered Users"));
-    session.close();
-  }
-
-  @Test
-  public void systemGroupsCreated_rest() throws IOException {
-    RestSession session = new RestSession(server, admin);
-    RestResponse r = session.get("/groups/");
-    Gson gson = new Gson();
-    Map<String, GroupInfo> result =
-        gson.fromJson(r.getReader(), new TypeToken<Map<String, GroupInfo>>() {}.getType());
-    Set<String> names = result.keySet();
-    assertTrue(names.contains("Administrators"));
-    assertTrue(names.contains("Anonymous Users"));
-    assertTrue(names.contains("Non-Interactive Users"));
-    assertTrue(names.contains("Project Owners"));
-    assertTrue(names.contains("Registered Users"));
-  }
-
-  @Test
-  public void systemGroupsCreated_internals() throws OrmException {
-    ReviewDb db = reviewDbProvider.open();
-    try {
-      Set<String> names = Sets.newHashSet();
-      for (AccountGroup g : db.accountGroups().all()) {
-        names.add(g.getName());
-      }
-      assertTrue(names.contains("Administrators"));
-      assertTrue(names.contains("Anonymous Users"));
-      assertTrue(names.contains("Non-Interactive Users"));
-      assertTrue(names.contains("Project Owners"));
-      assertTrue(names.contains("Registered Users"));
-    } finally {
-      db.close();
-    }
-  }
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
index bb3bb30..91511be 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BUCK
@@ -5,7 +5,7 @@
   deps = [
     ':branch',
     ':project',
-    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
+    '//gerrit-acceptance-tests:lib',
   ],
 )
 
@@ -13,11 +13,11 @@
   name = 'branch',
   srcs = [
     'BranchAssert.java',
-    'BranchInfo.java',
   ],
   deps = [
     '//lib:guava',
     '//lib:junit',
+    '//gerrit-server:server',
   ],
 )
 
@@ -25,7 +25,6 @@
   name = 'project',
   srcs = [
     'ProjectAssert.java',
-    'ProjectInfo.java',
   ],
   deps = [
     '//gerrit-extension-api:api',
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java
index 654ef65..5b0dfca 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchAssert.java
@@ -21,6 +21,7 @@
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.gerrit.server.project.ListBranches.BranchInfo;
 
 import java.util.List;
 
@@ -50,7 +51,7 @@
     if (expected.revision != null) {
       assertEquals(expected.revision, actual.revision);
     }
-    assertEquals(expected.can_delete, toBoolean(actual.can_delete));
+    assertEquals(expected.canDelete, toBoolean(actual.canDelete));
   }
 
   private static boolean toBoolean(Boolean b) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchInfo.java
deleted file mode 100644
index 2b7933e..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BranchInfo.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.rest.project;
-
-public class BranchInfo {
-  public String ref;
-  public String revision;
-  public Boolean can_delete;
-
-  public BranchInfo() {
-  }
-
-  public BranchInfo(String ref, String revision, boolean canDelete) {
-    this.ref = ref;
-    this.revision = revision;
-    this.can_delete = canDelete;
-  }
-
-  @Override
-  public String toString() {
-    return ref;
-  }
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
index 086cfa4..a4bcdf2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateBranchIT.java
@@ -14,25 +14,18 @@
 
 package com.google.gerrit.acceptance.rest.project;
 
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
 
@@ -44,10 +37,6 @@
 import java.io.IOException;
 
 public class CreateBranchIT extends AbstractDaemonTest {
-
-  @Inject
-  private AccountCreator accounts;
-
   @Inject
   private MetaDataUpdate.Server metaDataUpdateFactory;
 
@@ -55,34 +44,13 @@
   private ProjectCache projectCache;
 
   @Inject
-  private GroupCache groupCache;
-
-  @Inject
   private AllProjectsNameProvider allProjects;
 
-  private RestSession adminSession;
-  private RestSession userSession;
-
-  private Project.NameKey project;
   private Branch.NameKey branch;
 
   @Before
   public void setUp() throws Exception {
-    TestAccount admin = accounts.admin();
-    adminSession = new RestSession(server, admin);
-
-    TestAccount user = accounts.create("user", "user@example.com", "User");
-    userSession = new RestSession(server, user);
-
-    project = new Project.NameKey("p");
     branch = new Branch.NameKey(project, "test");
-
-    SshSession sshSession = new SshSession(server, admin);
-    try {
-      createProject(sshSession, project.get(), null, true);
-    } finally {
-      sshSession.close();
-    }
   }
 
   @Test
@@ -168,7 +136,7 @@
     AccessSection s = config.getAccessSection("refs/*", true);
     Permission p = s.getPermission(Permission.CREATE, true);
     PermissionRule rule = new PermissionRule(config.resolve(
-        groupCache.get(AccountGroup.ANONYMOUS_USERS)));
+        SystemGroupBackend.getGroup(SystemGroupBackend.ANONYMOUS_USERS)));
     rule.setBlock();
     p.add(rule);
     config.commit(md);
@@ -182,7 +150,7 @@
     AccessSection s = config.getAccessSection("refs/*", true);
     Permission p = s.getPermission(Permission.OWNER, true);
     PermissionRule rule = new PermissionRule(config.resolve(
-        groupCache.get(AccountGroup.REGISTERED_USERS)));
+        SystemGroupBackend.getGroup(SystemGroupBackend.REGISTERED_USERS)));
     p.add(rule);
     config.commit(md);
     projectCache.evict(config.getProject());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index 44a40d3..b593840 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo;
 import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectOwners;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -24,20 +23,19 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.CreateProject;
 import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.gerrit.server.project.ProjectState;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
@@ -50,20 +48,15 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
 import java.util.Collections;
-import java.util.List;
 import java.util.Set;
 
 public class CreateProjectIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private ProjectCache projectCache;
 
   @Inject
@@ -72,22 +65,12 @@
   @Inject
   private GitRepositoryManager git;
 
-  private TestAccount admin;
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void testCreateProject() throws IOException {
     final String newProjectName = "newProject";
-    RestResponse r = session.put("/projects/" + newProjectName);
+    RestResponse r = adminSession.put("/projects/" + newProjectName);
     assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
-    ProjectInfo p = (new Gson()).fromJson(r.getReader(), new TypeToken<ProjectInfo>() {}.getType());
+    ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
     assertEquals(newProjectName, p.name);
     ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
     assertNotNull(projectState);
@@ -97,110 +80,110 @@
 
   @Test
   public void testCreateProjectWithNameMismatch_BadRequest() throws IOException {
-    ProjectInput in = new ProjectInput();
+    CreateProject.Input in = new CreateProject.Input();
     in.name = "otherName";
-    RestResponse r = session.put("/projects/someName", in);
+    RestResponse r = adminSession.put("/projects/someName", in);
     assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
   }
 
   @Test
   public void testCreateProjectWithProperties() throws IOException {
     final String newProjectName = "newProject";
-    ProjectInput in = new ProjectInput();
+    CreateProject.Input in = new CreateProject.Input();
     in.description = "Test description";
-    in.submit_type = SubmitType.CHERRY_PICK;
-    in.use_contributor_agreements = InheritableBoolean.TRUE;
-    in.use_signed_off_by = InheritableBoolean.TRUE;
-    in.use_content_merge = InheritableBoolean.TRUE;
-    in.require_change_id = InheritableBoolean.TRUE;
-    RestResponse r = session.put("/projects/" + newProjectName, in);
-    ProjectInfo p = (new Gson()).fromJson(r.getReader(), new TypeToken<ProjectInfo>() {}.getType());
+    in.submitType = SubmitType.CHERRY_PICK;
+    in.useContributorAgreements = InheritableBoolean.TRUE;
+    in.useSignedOffBy = InheritableBoolean.TRUE;
+    in.useContentMerge = InheritableBoolean.TRUE;
+    in.requireChangeId = InheritableBoolean.TRUE;
+    RestResponse r = adminSession.put("/projects/" + newProjectName, in);
+    ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
     assertEquals(newProjectName, p.name);
     Project project = projectCache.get(new Project.NameKey(newProjectName)).getProject();
     assertProjectInfo(project, p);
     assertEquals(in.description, project.getDescription());
-    assertEquals(in.submit_type, project.getSubmitType());
-    assertEquals(in.use_contributor_agreements, project.getUseContributorAgreements());
-    assertEquals(in.use_signed_off_by, project.getUseSignedOffBy());
-    assertEquals(in.use_content_merge, project.getUseContentMerge());
-    assertEquals(in.require_change_id, project.getRequireChangeID());
+    assertEquals(in.submitType, project.getSubmitType());
+    assertEquals(in.useContributorAgreements, project.getUseContributorAgreements());
+    assertEquals(in.useSignedOffBy, project.getUseSignedOffBy());
+    assertEquals(in.useContentMerge, project.getUseContentMerge());
+    assertEquals(in.requireChangeId, project.getRequireChangeID());
   }
 
   @Test
   public void testCreateChildProject() throws IOException {
     final String parentName = "parent";
-    RestResponse r = session.put("/projects/" + parentName);
+    RestResponse r = adminSession.put("/projects/" + parentName);
     r.consume();
     final String childName = "child";
-    ProjectInput in = new ProjectInput();
+    CreateProject.Input in = new CreateProject.Input();
     in.parent = parentName;
-    r = session.put("/projects/" + childName, in);
+    r = adminSession.put("/projects/" + childName, in);
     Project project = projectCache.get(new Project.NameKey(childName)).getProject();
     assertEquals(in.parent, project.getParentName());
   }
 
   public void testCreateChildProjectUnderNonExistingParent_UnprocessableEntity()
       throws IOException {
-    ProjectInput in = new ProjectInput();
+    CreateProject.Input in = new CreateProject.Input();
     in.parent = "non-existing-project";
-    RestResponse r = session.put("/projects/child", in);
+    RestResponse r = adminSession.put("/projects/child", in);
     assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
   }
 
   @Test
   public void testCreateProjectWithOwner() throws IOException {
     final String newProjectName = "newProject";
-    ProjectInput in = new ProjectInput();
+    CreateProject.Input in = new CreateProject.Input();
     in.owners = Lists.newArrayListWithCapacity(3);
-    in.owners.add("Administrators"); // by name
-    in.owners.add(groupUuid("Registered Users").get()); // by group UUID
-    in.owners.add(Integer.toString(groupCache.get(new AccountGroup.NameKey("Anonymous Users"))
-        .getId().get())); // by legacy group ID
-    session.put("/projects/" + newProjectName, in);
+    in.owners.add("Anonymous Users"); // by name
+    in.owners.add(SystemGroupBackend.REGISTERED_USERS.get()); // by UUID
+    in.owners.add(Integer.toString(groupCache.get(
+        new AccountGroup.NameKey("Administrators")).getId().get())); // by ID
+    adminSession.put("/projects/" + newProjectName, in);
     ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
     Set<AccountGroup.UUID> expectedOwnerIds = Sets.newHashSetWithExpectedSize(3);
+    expectedOwnerIds.add(SystemGroupBackend.ANONYMOUS_USERS);
+    expectedOwnerIds.add(SystemGroupBackend.REGISTERED_USERS);
     expectedOwnerIds.add(groupUuid("Administrators"));
-    expectedOwnerIds.add(groupUuid("Registered Users"));
-    expectedOwnerIds.add(groupUuid("Anonymous Users"));
     assertProjectOwners(expectedOwnerIds, projectState);
   }
 
   public void testCreateProjectWithNonExistingOwner_UnprocessableEntity()
       throws IOException {
-    ProjectInput in = new ProjectInput();
+    CreateProject.Input in = new CreateProject.Input();
     in.owners = Collections.singletonList("non-existing-group");
-    RestResponse r = session.put("/projects/newProject", in);
+    RestResponse r = adminSession.put("/projects/newProject", in);
     assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
   }
 
   @Test
   public void testCreatePermissionOnlyProject() throws IOException {
     final String newProjectName = "newProject";
-    ProjectInput in = new ProjectInput();
-    in.permissions_only = true;
-    session.put("/projects/" + newProjectName, in);
-    assertHead(newProjectName, GitRepositoryManager.REF_CONFIG);
+    CreateProject.Input in = new CreateProject.Input();
+    in.permissionsOnly = true;
+    adminSession.put("/projects/" + newProjectName, in);
+    assertHead(newProjectName, RefNames.REFS_CONFIG);
   }
 
   @Test
   public void testCreateProjectWithEmptyCommit() throws IOException {
     final String newProjectName = "newProject";
-    ProjectInput in = new ProjectInput();
-    in.create_empty_commit = true;
-    session.put("/projects/" + newProjectName, in);
+    CreateProject.Input in = new CreateProject.Input();
+    in.createEmptyCommit = true;
+    adminSession.put("/projects/" + newProjectName, in);
     assertEmptyCommit(newProjectName, "refs/heads/master");
   }
 
   @Test
   public void testCreateProjectWithBranches() throws IOException {
     final String newProjectName = "newProject";
-    ProjectInput in = new ProjectInput();
-    in.create_empty_commit = true;
+    CreateProject.Input in = new CreateProject.Input();
+    in.createEmptyCommit = true;
     in.branches = Lists.newArrayListWithCapacity(3);
     in.branches.add("refs/heads/test");
     in.branches.add("refs/heads/master");
     in.branches.add("release"); // without 'refs/heads' prefix
-    session.put("/projects/" + newProjectName, in);
+    adminSession.put("/projects/" + newProjectName, in);
     assertHead(newProjectName, "refs/heads/test");
     assertEmptyCommit(newProjectName, "refs/heads/test", "refs/heads/master",
         "refs/heads/release");
@@ -209,15 +192,14 @@
   @Test
   public void testCreateProjectWithoutCapability_Forbidden() throws OrmException,
       JSchException, IOException {
-    TestAccount user = accounts.create("user", "user@example.com", "User");
-    RestResponse r = new RestSession(server, user).put("/projects/newProject");
+    RestResponse r = userSession.put("/projects/newProject");
     assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
   }
 
   @Test
   public void testCreateProjectWhenProjectAlreadyExists_Conflict()
       throws OrmException, JSchException, IOException {
-    RestResponse r = session.put("/projects/All-Projects");
+    RestResponse r = adminSession.put("/projects/All-Projects");
     assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
   }
 
@@ -255,20 +237,4 @@
       repo.close();
     }
   }
-
-  @SuppressWarnings("unused")
-  private static class ProjectInput {
-    String name;
-    String parent;
-    String description;
-    boolean permissions_only;
-    boolean create_empty_commit;
-    SubmitType submit_type;
-    List<String> branches;
-    List<String> owners;
-    InheritableBoolean use_contributor_agreements;
-    InheritableBoolean use_signed_off_by;
-    InheritableBoolean use_content_merge;
-    InheritableBoolean require_change_id;
-  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
index d23ad6d..020fd43 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchIT.java
@@ -14,26 +14,18 @@
 
 package com.google.gerrit.acceptance.rest.project;
 
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
 
@@ -47,45 +39,19 @@
 public class DeleteBranchIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private MetaDataUpdate.Server metaDataUpdateFactory;
 
   @Inject
   private ProjectCache projectCache;
 
   @Inject
-  private GroupCache groupCache;
-
-  @Inject
   private AllProjectsNameProvider allProjects;
 
-  private RestSession adminSession;
-  private RestSession userSession;
-
-  private Project.NameKey project;
   private Branch.NameKey branch;
 
   @Before
   public void setUp() throws Exception {
-    TestAccount admin = accounts.admin();
-    adminSession = new RestSession(server, admin);
-
-    TestAccount user = accounts.create("user", "user@example.com", "User");
-    userSession = new RestSession(server, user);
-
-    project = new Project.NameKey("p");
     branch = new Branch.NameKey(project, "test");
-
-    initSsh(admin);
-    SshSession sshSession = new SshSession(server, admin);
-    try {
-      createProject(sshSession, project.get(), null, true);
-    } finally {
-      sshSession.close();
-    }
-
     adminSession.put("/projects/" + project.get()
         + "/branches/" + branch.getShortName()).consume();
   }
@@ -164,8 +130,8 @@
     ProjectConfig config = ProjectConfig.read(md);
     AccessSection s = config.getAccessSection("refs/heads/*", true);
     Permission p = s.getPermission(Permission.PUSH, true);
-    AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Anonymous Users"));
-    PermissionRule rule = new PermissionRule(config.resolve(adminGroup));
+    PermissionRule rule = new PermissionRule(config.resolve(
+        SystemGroupBackend.getGroup(SystemGroupBackend.ANONYMOUS_USERS)));
     rule.setForce(true);
     rule.setBlock();
     p.add(rule);
@@ -179,8 +145,8 @@
     ProjectConfig config = ProjectConfig.read(md);
     AccessSection s = config.getAccessSection("refs/*", true);
     Permission p = s.getPermission(Permission.OWNER, true);
-    AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Registered Users"));
-    PermissionRule rule = new PermissionRule(config.resolve(adminGroup));
+    PermissionRule rule = new PermissionRule(config.resolve(
+        SystemGroupBackend.getGroup(SystemGroupBackend.REGISTERED_USERS)));
     p.add(rule);
     config.commit(md);
     projectCache.evict(config.getProject());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
index 7057a4f..26585ba 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GarbageCollectionIT.java
@@ -14,16 +14,12 @@
 
 package com.google.gerrit.acceptance.rest.project;
 
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.GcAssert;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.UseLocalDisk;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsName;
@@ -41,34 +37,21 @@
 public class GarbageCollectionIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private AllProjectsName allProjects;
 
   @Inject
   private GcAssert gcAssert;
 
-  private TestAccount admin;
-  private RestSession session;
   private Project.NameKey project1;
   private Project.NameKey project2;
 
   @Before
   public void setUp() throws Exception {
-    admin =
-        accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-
-    SshSession sshSession = new SshSession(server, admin);
-
     project1 = new Project.NameKey("p1");
     createProject(sshSession, project1.get());
 
     project2 = new Project.NameKey("p2");
     createProject(sshSession, project2.get());
-
-    session = new RestSession(server, admin);
   }
 
   @Test
@@ -77,10 +60,11 @@
   }
 
   @Test
-  public void testGcNotAllowed_Forbidden() throws IOException, OrmException, JSchException {
+  public void testGcNotAllowed_Forbidden() throws IOException, OrmException,
+      JSchException {
     assertEquals(HttpStatus.SC_FORBIDDEN,
-        new RestSession(server, accounts.create("user", "user@example.com", "User"))
-            .post("/projects/" + allProjects.get() + "/gc").getStatusCode());
+        userSession.post("/projects/" + allProjects.get() + "/gc")
+            .getStatusCode());
   }
 
   @Test
@@ -92,7 +76,7 @@
   }
 
   private int POST(String endPoint) throws IOException {
-    RestResponse r = session.post(endPoint);
+    RestResponse r = adminSession.post(endPoint);
     r.consume();
     return r.getStatusCode();
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
index bf8aac1b..b037ff9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetChildProjectIT.java
@@ -14,27 +14,22 @@
 
 package com.google.gerrit.acceptance.rest.project;
 
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
 import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo;
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
 import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.inject.Inject;
 
 import com.jcraft.jsch.JSchException;
 
 import org.apache.http.HttpStatus;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -42,25 +37,11 @@
 public class GetChildProjectIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private AllProjectsName allProjects;
 
   @Inject
   private ProjectCache projectCache;
 
-  private TestAccount admin;
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    admin =
-        accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void getNonExistingChildProject_NotFound() throws IOException {
     assertEquals(HttpStatus.SC_NOT_FOUND,
@@ -74,6 +55,7 @@
     createProject(sshSession, p1.get());
     Project.NameKey p2 = new Project.NameKey("p2");
     createProject(sshSession, p2.get());
+    sshSession.close();
     assertEquals(HttpStatus.SC_NOT_FOUND,
         GET("/projects/" + p1.get() + "/children/" + p2.get()).getStatusCode());
   }
@@ -83,11 +65,11 @@
     SshSession sshSession = new SshSession(server, admin);
     Project.NameKey child = new Project.NameKey("p1");
     createProject(sshSession, child.get());
+    sshSession.close();
     RestResponse r = GET("/projects/" + allProjects.get() + "/children/" + child.get());
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     ProjectInfo childInfo =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<ProjectInfo>() {}.getType());
+        newGson().fromJson(r.getReader(), ProjectInfo.class);
     assertProjectInfo(projectCache.get(child).getProject(), childInfo);
   }
 
@@ -98,6 +80,7 @@
     createProject(sshSession, child.get());
     Project.NameKey grandChild = new Project.NameKey("p1.1");
     createProject(sshSession, grandChild.get(), child);
+    sshSession.close();
     assertEquals(HttpStatus.SC_NOT_FOUND,
         GET("/projects/" + allProjects.get() + "/children/" + grandChild.get())
             .getStatusCode());
@@ -111,16 +94,17 @@
     createProject(sshSession, child.get());
     Project.NameKey grandChild = new Project.NameKey("p1.1");
     createProject(sshSession, grandChild.get(), child);
+    sshSession.close();
     RestResponse r =
         GET("/projects/" + allProjects.get() + "/children/" + grandChild.get()
             + "?recursive");
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     ProjectInfo grandChildInfo =
-        (new Gson()).fromJson(r.getReader(), new TypeToken<ProjectInfo>() {}.getType());
+        newGson().fromJson(r.getReader(), ProjectInfo.class);
     assertProjectInfo(projectCache.get(grandChild).getProject(), grandChildInfo);
   }
 
   private RestResponse GET(String endpoint) throws IOException {
-    return session.get(endpoint);
+    return adminSession.get(endpoint);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
index 4778662..52bb623 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
@@ -14,45 +14,33 @@
 
 package com.google.gerrit.acceptance.rest.project;
 
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
 import static com.google.gerrit.acceptance.rest.project.BranchAssert.assertBranches;
 import static org.junit.Assert.assertEquals;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.PushOneCommit;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ListBranches.BranchInfo;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 
 import com.jcraft.jsch.JSchException;
 
 import org.apache.http.HttpStatus;
-import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -62,46 +50,11 @@
 public class ListBranchesIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private MetaDataUpdate.Server metaDataUpdateFactory;
 
   @Inject
   private ProjectCache projectCache;
 
-  @Inject
-  private GroupCache groupCache;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  private TestAccount admin;
-  private RestSession session;
-  private SshSession sshSession;
-  private Project.NameKey project;
-  private Git git;
-  private ReviewDb db;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-
-    project = new Project.NameKey("p");
-    initSsh(admin);
-    sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
-    git = cloneProject(sshSession.getUrl() + "/" + project.get());
-    db = reviewDbProvider.open();
-  }
-
-  @After
-  public void cleanup() {
-    db.close();
-  }
-
   @Test
   public void listBranchesOfNonExistingProject_NotFound() throws IOException {
     assertEquals(HttpStatus.SC_NOT_FOUND,
@@ -112,26 +65,21 @@
   public void listBranchesOfNonVisibleProject_NotFound() throws IOException,
       OrmException, JSchException, ConfigInvalidException {
     blockRead(project, "refs/*");
-    RestSession session =
-        new RestSession(server, accounts.create("user", "user@example.com", "User"));
     assertEquals(HttpStatus.SC_NOT_FOUND,
-        session.get("/projects/" + project.get() + "/branches").getStatusCode());
+        userSession.get("/projects/" + project.get() + "/branches").getStatusCode());
   }
 
   @Test
   public void listBranchesOfEmptyProject() throws IOException, JSchException {
     Project.NameKey emptyProject = new Project.NameKey("empty");
     createProject(sshSession, emptyProject.get(), null, false);
-    RestResponse r = session.get("/projects/" + emptyProject.get() + "/branches");
-    List<BranchInfo> result =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<List<BranchInfo>>() {}.getType());
+    RestResponse r = adminSession.get("/projects/" + emptyProject.get() + "/branches");
     List<BranchInfo> expected = Lists.asList(
         new BranchInfo("refs/meta/config",  null, false),
         new BranchInfo[] {
           new BranchInfo("HEAD", null, false)
         });
-    assertBranches(expected, result);
+    assertBranches(expected, toBranchInfoList(r));
   }
 
   @Test
@@ -140,10 +88,7 @@
     String masterCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
     pushTo("refs/heads/dev");
     String devCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
-    RestResponse r = session.get("/projects/" + project.get() + "/branches");
-    List<BranchInfo> result =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<List<BranchInfo>>() {}.getType());
+    RestResponse r = adminSession.get("/projects/" + project.get() + "/branches");
     List<BranchInfo> expected = Lists.asList(
         new BranchInfo("refs/meta/config",  null, false),
         new BranchInfo[] {
@@ -151,6 +96,7 @@
           new BranchInfo("refs/heads/master", masterCommit, false),
           new BranchInfo("refs/heads/dev", devCommit, true)
         });
+    List<BranchInfo> result = toBranchInfoList(r);
     assertBranches(expected, result);
 
     // verify correct sorting
@@ -164,44 +110,34 @@
   public void listBranchesSomeHidden() throws IOException, GitAPIException,
       ConfigInvalidException, OrmException, JSchException {
     blockRead(project, "refs/heads/dev");
-    RestSession session =
-        new RestSession(server, accounts.create("user", "user@example.com", "User"));
     pushTo("refs/heads/master");
     String masterCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
     pushTo("refs/heads/dev");
-    RestResponse r = session.get("/projects/" + project.get() + "/branches");
-    List<BranchInfo> result =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<List<BranchInfo>>() {}.getType());
+    RestResponse r = userSession.get("/projects/" + project.get() + "/branches");
     // refs/meta/config is hidden since user is no project owner
     List<BranchInfo> expected = Lists.asList(
         new BranchInfo("HEAD", "master", false),
         new BranchInfo[] {
           new BranchInfo("refs/heads/master", masterCommit, false),
         });
-    assertBranches(expected, result);
+    assertBranches(expected, toBranchInfoList(r));
   }
 
   @Test
   public void listBranchesHeadHidden() throws IOException, GitAPIException,
       ConfigInvalidException, OrmException, JSchException {
     blockRead(project, "refs/heads/master");
-    RestSession session =
-        new RestSession(server, accounts.create("user", "user@example.com", "User"));
     pushTo("refs/heads/master");
     pushTo("refs/heads/dev");
     String devCommit = git.getRepository().getRef("master").getTarget().getObjectId().getName();
-    RestResponse r = session.get("/projects/" + project.get() + "/branches");
-    List<BranchInfo> result =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<List<BranchInfo>>() {}.getType());
+    RestResponse r = userSession.get("/projects/" + project.get() + "/branches");
     // refs/meta/config is hidden since user is no project owner
     assertBranches(Collections.singletonList(new BranchInfo("refs/heads/dev",
-        devCommit, false)), result);
+        devCommit, false)), toBranchInfoList(r));
   }
 
   private RestResponse GET(String endpoint) throws IOException {
-    return session.get(endpoint);
+    return adminSession.get(endpoint);
   }
 
   private void blockRead(Project.NameKey project, String ref)
@@ -211,17 +147,25 @@
     ProjectConfig config = ProjectConfig.read(md);
     AccessSection s = config.getAccessSection(ref, true);
     Permission p = s.getPermission(Permission.READ, true);
-    AccountGroup adminGroup = groupCache.get(AccountGroup.REGISTERED_USERS);
-    PermissionRule rule = new PermissionRule(config.resolve(adminGroup));
+    PermissionRule rule = new PermissionRule(config.resolve(
+        SystemGroupBackend.getGroup(SystemGroupBackend.REGISTERED_USERS)));
     rule.setBlock();
     p.add(rule);
     config.commit(md);
     projectCache.evict(config.getProject());
   }
 
+  private static List<BranchInfo> toBranchInfoList(RestResponse r)
+      throws IOException {
+    List<BranchInfo> result =
+        newGson().fromJson(r.getReader(),
+            new TypeToken<List<BranchInfo>>() {}.getType());
+    return result;
+  }
+
   private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
       IOException {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
     return push.to(git, ref);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
index c03ecd3..c61764b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListChildProjectsIT.java
@@ -14,27 +14,22 @@
 
 package com.google.gerrit.acceptance.rest.project;
 
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
+import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjects;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjects;
 
-import com.google.gson.reflect.TypeToken;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gson.Gson;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
+import com.google.gson.reflect.TypeToken;
 import com.google.inject.Inject;
 
 import com.jcraft.jsch.JSchException;
 
 import org.apache.http.HttpStatus;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -44,22 +39,8 @@
 public class ListChildProjectsIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private AllProjectsName allProjects;
 
-  private TestAccount admin;
-  private RestSession session;
-
-  @Before
-  public void setUp() throws Exception {
-    admin =
-        accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-    session = new RestSession(server, admin);
-  }
-
   @Test
   public void listChildrenOfNonExistingProject_NotFound() throws IOException {
     assertEquals(HttpStatus.SC_NOT_FOUND,
@@ -70,15 +51,14 @@
   public void listNoChildren() throws IOException {
     RestResponse r = GET("/projects/" + allProjects.get() + "/children/");
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    List<ProjectInfo> children =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<List<ProjectInfo>>() {}.getType());
-    assertTrue(children.isEmpty());
+    List<ProjectInfo> projectInfoList = toProjectInfoList(r);
+    // Project 'p' was already created in the base class
+    assertTrue(projectInfoList.size() == 1);
   }
 
   @Test
   public void listChildren() throws IOException, JSchException {
-    SshSession sshSession = new SshSession(server, admin);
+    Project.NameKey existingProject = new Project.NameKey("p");
     Project.NameKey child1 = new Project.NameKey("p1");
     createProject(sshSession, child1.get());
     Project.NameKey child2 = new Project.NameKey("p2");
@@ -87,15 +67,11 @@
 
     RestResponse r = GET("/projects/" + allProjects.get() + "/children/");
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    List<ProjectInfo> children =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<List<ProjectInfo>>() {}.getType());
-    assertProjects(Arrays.asList(child1, child2), children);
+    assertProjects(Arrays.asList(existingProject, child1, child2), toProjectInfoList(r));
   }
 
   @Test
   public void listChildrenRecursively() throws IOException, JSchException {
-    SshSession sshSession = new SshSession(server, admin);
     Project.NameKey child1 = new Project.NameKey("p1");
     createProject(sshSession, child1.get());
     createProject(sshSession, "p2");
@@ -110,13 +86,17 @@
 
     RestResponse r = GET("/projects/" + child1.get() + "/children/?recursive");
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
-    List<ProjectInfo> children =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<List<ProjectInfo>>() {}.getType());
-    assertProjects(Arrays.asList(child1_1, child1_2, child1_1_1, child1_1_1_1), children);
+    assertProjects(Arrays.asList(child1_1, child1_2,
+        child1_1_1, child1_1_1_1), toProjectInfoList(r));
+  }
+
+  private static List<ProjectInfo> toProjectInfoList(RestResponse r)
+      throws IOException {
+    return newGson().fromJson(r.getReader(),
+        new TypeToken<List<ProjectInfo>>() {}.getType());
   }
 
   private RestResponse GET(String endpoint) throws IOException {
-    return session.get(endpoint);
+    return adminSession.get(endpoint);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
index 224d59d..788a54b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.gerrit.server.project.ProjectState;
 
 import java.util.List;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
new file mode 100644
index 0000000..fb830e4
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectLevelConfigIT.java
@@ -0,0 +1,141 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.project;
+
+import static com.google.gerrit.acceptance.GitUtil.checkout;
+import static com.google.gerrit.acceptance.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
+import static com.google.gerrit.acceptance.GitUtil.fetch;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class ProjectLevelConfigIT extends AbstractDaemonTest {
+
+  @Inject
+  private SchemaFactory<ReviewDb> reviewDbProvider;
+
+  @Inject
+  private ProjectCache projectCache;
+
+  @Inject
+  private AllProjectsNameProvider allProjects;
+
+  @Inject
+  private PushOneCommit.Factory pushFactory;
+
+  private ReviewDb db;
+  private SshSession sshSession;
+  private String project;
+  private Git git;
+
+  @Before
+  public void setUp() throws Exception {
+    sshSession = new SshSession(server, admin);
+
+    project = "p";
+    createProject(sshSession, project, null, true);
+    git = cloneProject(sshSession.getUrl() + "/" + project);
+    fetch(git, RefNames.REFS_CONFIG + ":refs/heads/config");
+    checkout(git, "refs/heads/config");
+
+    db = reviewDbProvider.open();
+  }
+
+  @After
+  public void cleanup() {
+    db.close();
+  }
+
+  @Test
+  public void accessProjectSpecificConfig() throws GitAPIException, IOException {
+    String configName = "test.config";
+    Config cfg = new Config();
+    cfg.setString("s1", null, "k1", "v1");
+    cfg.setString("s2", "ss", "k2", "v2");
+    PushOneCommit push =
+        pushFactory.create(db, admin.getIdent(), "Create Project Level Config",
+            configName, cfg.toText());
+    push.to(git, RefNames.REFS_CONFIG);
+
+    ProjectState state = projectCache.get(new Project.NameKey(project));
+    assertEquals(cfg.toText(), state.getConfig(configName).get().toText());
+  }
+
+  @Test
+  public void nonExistingConfig() {
+    ProjectState state = projectCache.get(new Project.NameKey(project));
+    assertEquals("", state.getConfig("test.config").get().toText());
+  }
+
+  @Test
+  public void withInheritance() throws GitAPIException, IOException {
+    String configName = "test.config";
+
+    Config parentCfg = new Config();
+    parentCfg.setString("s1", null, "k1", "parentValue1");
+    parentCfg.setString("s1", null, "k2", "parentValue2");
+    parentCfg.setString("s2", "ss", "k3", "parentValue3");
+    parentCfg.setString("s2", "ss", "k4", "parentValue4");
+
+    Git parentGit =
+        cloneProject(sshSession.getUrl() + "/" + allProjects.get().get(), false);
+    fetch(parentGit, RefNames.REFS_CONFIG + ":refs/heads/config");
+    checkout(parentGit, "refs/heads/config");
+    PushOneCommit push =
+        pushFactory.create(db, admin.getIdent(), "Create Project Level Config",
+            configName, parentCfg.toText());
+    push.to(parentGit, RefNames.REFS_CONFIG);
+
+    Config cfg = new Config();
+    cfg.setString("s1", null, "k1", "childValue1");
+    cfg.setString("s2", "ss", "k3", "childValue2");
+    push = pushFactory.create(db, admin.getIdent(), "Create Project Level Config",
+        configName, cfg.toText());
+    push.to(git, RefNames.REFS_CONFIG);
+
+    ProjectState state = projectCache.get(new Project.NameKey(project));
+
+    Config expectedCfg = new Config();
+    expectedCfg.setString("s1", null, "k1", "childValue1");
+    expectedCfg.setString("s1", null, "k2", "parentValue2");
+    expectedCfg.setString("s2", "ss", "k3", "childValue2");
+    expectedCfg.setString("s2", "ss", "k4", "parentValue4");
+
+    assertEquals(expectedCfg.toText(), state.getConfig(configName)
+        .getWithInheritance().toText());
+
+    assertEquals(cfg.toText(), state.getConfig(configName).get().toText());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
index abc3bb6..7c6f280 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
@@ -14,27 +14,19 @@
 
 package com.google.gerrit.acceptance.rest.project;
 
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
 import static org.junit.Assert.assertEquals;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
+import com.google.gerrit.server.project.SetParent;
 import com.google.inject.Inject;
 
 import com.jcraft.jsch.JSchException;
 
 import org.apache.http.HttpStatus;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -42,44 +34,15 @@
 public class SetParentIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private AllProjectsNameProvider allProjects;
 
-  private RestSession adminSession;
-  private RestSession userSession;
-  private SshSession sshSession;
-
-  private String project;
-
-  @Before
-  public void setUp() throws Exception {
-    TestAccount admin = accounts.admin();
-    adminSession = new RestSession(server, admin);
-
-    TestAccount user = accounts.create("user", "user@example.com", "User");
-    userSession = new RestSession(server, user);
-
-
-    initSsh(admin);
-    sshSession = new SshSession(server, admin);
-    project = "p";
-    createProject(sshSession, project, null, true);
-  }
-
-  @After
-  public void cleanup() {
-    sshSession.close();
-  }
-
   @Test
   public void setParent_Forbidden() throws IOException, JSchException {
     String parent = "parent";
     createProject(sshSession, parent, null, true);
     RestResponse r =
-        userSession.put("/projects/" + project + "/parent",
-            new ParentInput(parent));
+        userSession.put("/projects/" + project.get() + "/parent",
+            newParentInput(parent));
     assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
     r.consume();
   }
@@ -89,16 +52,15 @@
     String parent = "parent";
     createProject(sshSession, parent, null, true);
     RestResponse r =
-        adminSession.put("/projects/" + project + "/parent",
-            new ParentInput(parent));
+        adminSession.put("/projects/" + project.get() + "/parent",
+            newParentInput(parent));
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     r.consume();
 
-    r = adminSession.get("/projects/" + project + "/parent");
+    r = adminSession.get("/projects/" + project.get() + "/parent");
     assertEquals(HttpStatus.SC_OK, r.getStatusCode());
     String newParent =
-        (new Gson()).fromJson(r.getReader(),
-            new TypeToken<String>() {}.getType());
+        newGson().fromJson(r.getReader(), String.class);
     assertEquals(parent, newParent);
     r.consume();
   }
@@ -107,7 +69,7 @@
   public void setParentForAllProjects_Conflict() throws IOException {
     RestResponse r =
         adminSession.put("/projects/" + allProjects.get() + "/parent",
-            new ParentInput(project));
+            newParentInput(project.get()));
     assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
     r.consume();
   }
@@ -115,22 +77,22 @@
   @Test
   public void setInvalidParent_Conflict() throws IOException, JSchException {
     RestResponse r =
-        adminSession.put("/projects/" + project + "/parent",
-            new ParentInput(project));
+        adminSession.put("/projects/" + project.get() + "/parent",
+            newParentInput(project.get()));
     assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
     r.consume();
 
     String child = "child";
-    createProject(sshSession, child, new Project.NameKey(project), true);
-    r = adminSession.put("/projects/" + project + "/parent",
-           new ParentInput(child));
+    createProject(sshSession, child, project, true);
+    r = adminSession.put("/projects/" + project.get() + "/parent",
+           newParentInput(child));
     assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
     r.consume();
 
     String grandchild = "grandchild";
     createProject(sshSession, grandchild, new Project.NameKey(child), true);
-    r = adminSession.put("/projects/" + project + "/parent",
-           new ParentInput(grandchild));
+    r = adminSession.put("/projects/" + project.get() + "/parent",
+           newParentInput(grandchild));
     assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
     r.consume();
   }
@@ -138,18 +100,15 @@
   @Test
   public void setNonExistingParent_UnprocessibleEntity() throws IOException {
     RestResponse r =
-        adminSession.put("/projects/" + project + "/parent",
-            new ParentInput("non-existing"));
+        adminSession.put("/projects/" + project.get() + "/parent",
+            newParentInput("non-existing"));
     assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
     r.consume();
   }
 
-  @SuppressWarnings("unused")
-  private static class ParentInput {
-    String parent;
-
-    ParentInput(String parent) {
-      this.parent = parent;
-    }
+  SetParent.Input newParentInput(String project) {
+    SetParent.Input in = new SetParent.Input();
+    in.parent = project;
+    return in;
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK
index 0157bd6..688e649 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/BUCK
@@ -3,19 +3,6 @@
 acceptance_tests(
   srcs = glob(['*IT.java']),
   deps = [
-    ':util',
-    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
-    '//lib:gwtjsonrpc',
-  ],
-)
-
-java_library(
-  name = 'util',
-  srcs = [
-    'ChangeAndCommit.java',
-    'RelatedInfo.java',
-  ],
-  deps = [
-    '//gerrit-server:server',
+    '//gerrit-acceptance-tests:lib',
   ],
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ChangeAndCommit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ChangeAndCommit.java
deleted file mode 100644
index d9959b2..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ChangeAndCommit.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.acceptance.server.change;
-
-import com.google.gerrit.server.change.ChangeJson.CommitInfo;
-
-public class ChangeAndCommit {
-  public String changeId;
-  public CommitInfo commit;
-  public Integer _changeNumber;
-  public Integer _revisionNumber;
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
index 3ae31b4..1456086 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -14,38 +14,24 @@
 
 package com.google.gerrit.acceptance.server.change;
 
-import static com.google.gerrit.acceptance.git.GitUtil.add;
-import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
-import static com.google.gerrit.acceptance.git.GitUtil.createCommit;
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
-import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
-import static com.google.gerrit.acceptance.git.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.GitUtil.add;
+import static com.google.gerrit.acceptance.GitUtil.createCommit;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
 import static org.junit.Assert.assertEquals;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
-import com.google.gerrit.acceptance.RestSession;
-import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.git.PushOneCommit;
-import com.google.gerrit.acceptance.git.GitUtil.Commit;
+import com.google.gerrit.acceptance.GitUtil.Commit;
+import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.OutputFormat;
-import com.google.gson.reflect.TypeToken;
+import com.google.gerrit.server.change.GetRelated.ChangeAndCommit;
+import com.google.gerrit.server.change.GetRelated.RelatedInfo;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
 
-import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.ResetCommand.ResetType;
 import org.eclipse.jgit.api.errors.GitAPIException;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -53,40 +39,10 @@
 
 public class GetRelatedIT extends AbstractDaemonTest {
 
-  @Inject
-  private AccountCreator accounts;
-
-  @Inject
-  private SchemaFactory<ReviewDb> reviewDbProvider;
-
-  private TestAccount admin;
-  private RestSession session;
-  private Git git;
-  private ReviewDb db;
-
-  @Before
-  public void setUp() throws Exception {
-    admin = accounts.admin();
-    session = new RestSession(server, admin);
-
-    initSsh(admin);
-    Project.NameKey project = new Project.NameKey("p");
-    SshSession sshSession = new SshSession(server, admin);
-    createProject(sshSession, project.get());
-    git = cloneProject(sshSession.getUrl() + "/" + project.get());
-    sshSession.close();
-    db = reviewDbProvider.open();
-  }
-
-  @After
-  public void cleanup() {
-    db.close();
-  }
-
   @Test
   public void getRelatedNoResult() throws GitAPIException,
       IOException, Exception {
-    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
     PatchSet.Id ps = push.to(git, "refs/for/master").getPatchSetId();
     List<ChangeAndCommit> related = getRelated(ps);
     assertEquals(0, related.size());
@@ -186,10 +142,8 @@
   private List<ChangeAndCommit> getRelated(PatchSet.Id ps) throws IOException {
     String url = String.format("/changes/%d/revisions/%d/related",
         ps.getParentKey().get(), ps.get());
-    RelatedInfo related = OutputFormat.JSON_COMPACT.newGson().fromJson(
-        session.get(url).getReader(),
-        new TypeToken<RelatedInfo>() {}.getType());
-    return related.changes;
+    return newGson().fromJson(adminSession.get(url).getReader(),
+        RelatedInfo.class).changes;
   }
 
   private PatchSet.Id getPatchSetId(Commit c) throws OrmException {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/BUCK
new file mode 100644
index 0000000..688e649
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/BUCK
@@ -0,0 +1,8 @@
+include_defs('//gerrit-acceptance-tests/tests.defs')
+
+acceptance_tests(
+  srcs = glob(['*IT.java']),
+  deps = [
+    '//gerrit-acceptance-tests:lib',
+  ],
+)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
new file mode 100644
index 0000000..f38ac89
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -0,0 +1,153 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.server.project;
+
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.project.Util.category;
+import static com.google.gerrit.server.project.Util.grant;
+import static com.google.gerrit.server.project.Util.value;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@NoHttpd
+public class CustomLabelIT extends AbstractDaemonTest {
+
+  @Inject
+  private ProjectCache projectCache;
+
+  @Inject
+  private AllProjectsName allProjects;
+
+  @Inject
+  private MetaDataUpdate.Server metaDataUpdateFactory;
+
+  private final LabelType Q = category("CustomLabel",
+      value(1, "Positive"),
+      value(0, "No score"),
+      value(-1, "Negative"));
+
+  @Before
+  public void setUp() throws Exception {
+    ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+    AccountGroup.UUID anonymousUsers =
+        SystemGroupBackend.getGroup(ANONYMOUS_USERS).getUUID();
+    grant(cfg, Permission.forLabel(Q.getName()), -1, 1, anonymousUsers,
+        "refs/heads/*");
+    saveProjectConfig(cfg);
+  }
+
+  @Test
+  public void customLabelNoOp_NegativeVoteNotBlock() throws Exception {
+    Q.setFunctionName("NoOp");
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNotNull(q.rejected);
+    assertNull(q.blocking);
+  }
+
+  @Test
+  public void customLabelNoBlock_NegativeVoteNotBlock() throws Exception {
+    Q.setFunctionName("NoBlock");
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNotNull(q.rejected);
+    assertNull(q.blocking);
+  }
+
+  @Test
+  public void customLabelMaxNoBlock_NegativeVoteNotBlock() throws Exception {
+    Q.setFunctionName("MaxNoBlock");
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNotNull(q.rejected);
+    assertNull(q.blocking);
+  }
+
+  @Test
+  public void customLabelAnyWithBlock_NegativeVoteBlock() throws Exception {
+    Q.setFunctionName("AnyWithBlock");
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNull(q.disliked);
+    assertNotNull(q.rejected);
+    assertTrue(q.blocking);
+  }
+
+  @Test
+  public void customLabelMaxWithBlock_NegativeVoteBlock() throws Exception {
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(new ReviewInput().label(Q.getName(), -1));
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo q = c.labels.get(Q.getName());
+    assertEquals(1, q.all.size());
+    assertNull(q.disliked);
+    assertNotNull(q.rejected);
+    assertTrue(q.blocking);
+  }
+
+  private void saveLabelConfig() throws Exception {
+    ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+    cfg.getLabelSections().put(Q.getName(), Q);
+    saveProjectConfig(cfg);
+  }
+
+  private void saveProjectConfig(ProjectConfig cfg) throws Exception {
+    MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
+    try {
+      cfg.commit(md);
+    } finally {
+      md.close();
+    }
+    projectCache.evict(allProjects);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
new file mode 100644
index 0000000..a2dd8ec
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/LabelTypeIT.java
@@ -0,0 +1,262 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.server.project;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
+import org.junit.Test;
+
+@NoHttpd
+public class LabelTypeIT extends AbstractDaemonTest {
+
+  @Inject
+  private GitRepositoryManager repoManager;
+
+  @Inject
+  private ProjectCache projectCache;
+
+  @Inject
+  private AllProjectsName allProjects;
+
+  @Inject
+  private MetaDataUpdate.Server metaDataUpdateFactory;
+
+  private LabelType codeReview;
+
+  @Before
+  public void setUp() throws Exception {
+    ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+    codeReview = checkNotNull(cfg.getLabelSections().get("Code-Review"));
+    codeReview.setCopyMinScore(false);
+    codeReview.setCopyMaxScore(false);
+    codeReview.setCopyAllScoresOnTrivialRebase(false);
+    codeReview.setCopyAllScoresIfNoCodeChange(false);
+    saveProjectConfig(cfg);
+  }
+
+  @Test
+  public void noCopyMinScoreOnRework() throws Exception {
+    PushOneCommit.Result r = createChange();
+    revision(r).review(ReviewInput.reject());
+    assertApproval(r, -2);
+    r = amendChange(r.getChangeId());
+    assertApproval(r, 0);
+  }
+
+  @Test
+  public void copyMinScoreOnRework() throws Exception {
+    codeReview.setCopyMinScore(true);
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(ReviewInput.reject());
+    assertApproval(r, -2);
+    r = amendChange(r.getChangeId());
+    assertApproval(r, -2);
+  }
+
+  @Test
+  public void noCopyMaxScoreOnRework() throws Exception {
+    PushOneCommit.Result r = createChange();
+    revision(r).review(ReviewInput.approve());
+    assertApproval(r, 2);
+    r = amendChange(r.getChangeId());
+    assertApproval(r, 0);
+  }
+
+  @Test
+  public void copyMaxScoreOnRework() throws Exception {
+    codeReview.setCopyMaxScore(true);
+    saveLabelConfig();
+    PushOneCommit.Result r = createChange();
+    revision(r).review(ReviewInput.approve());
+    assertApproval(r, 2);
+    r = amendChange(r.getChangeId());
+    assertApproval(r, 2);
+  }
+
+  @Test
+  public void noCopyNonMaxScoreOnRework() throws Exception {
+    codeReview.setCopyMinScore(true);
+    codeReview.setCopyMaxScore(true);
+    saveLabelConfig();
+
+    PushOneCommit.Result r = createChange();
+    revision(r).review(ReviewInput.recommend());
+    assertApproval(r, 1);
+    r = amendChange(r.getChangeId());
+    assertApproval(r, 0);
+  }
+
+  @Test
+  public void noCopyNonMinScoreOnRework() throws Exception {
+    codeReview.setCopyMinScore(true);
+    codeReview.setCopyMaxScore(true);
+    saveLabelConfig();
+
+    PushOneCommit.Result r = createChange();
+    revision(r).review(ReviewInput.dislike());
+    assertApproval(r, -1);
+    r = amendChange(r.getChangeId());
+    assertApproval(r, 0);
+  }
+
+  @Test
+  public void noCopyAllScoresIfNoCodeChange() throws Exception {
+    String file = "a.txt";
+    String contents = "contents";
+
+    PushOneCommit push = pushFactory.create(db, admin.getIdent(),
+        "first subject", file, contents);
+    PushOneCommit.Result r = push.to(git, "refs/for/master");
+    revision(r).review(ReviewInput.recommend());
+    assertApproval(r, 1);
+
+    push = pushFactory.create(db, admin.getIdent(),
+        "second subject", file, contents, r.getChangeId());
+    r = push.to(git, "refs/for/master");
+    assertApproval(r, 0);
+  }
+
+  @Test
+  public void copyAllScoresIfNoCodeChange() throws Exception {
+    String file = "a.txt";
+    String contents = "contents";
+    codeReview.setCopyAllScoresIfNoCodeChange(true);
+    saveLabelConfig();
+
+    PushOneCommit push = pushFactory.create(db, admin.getIdent(),
+        "first subject", file, contents);
+    PushOneCommit.Result r = push.to(git, "refs/for/master");
+    revision(r).review(ReviewInput.recommend());
+    assertApproval(r, 1);
+
+    push = pushFactory.create(db, admin.getIdent(),
+        "second subject", file, contents, r.getChangeId());
+    r = push.to(git, "refs/for/master");
+    assertApproval(r, 1);
+  }
+
+  @Test
+  public void noCopyAllScoresOnTrivialRebase() throws Exception {
+    String subject = "test commit";
+    String file = "a.txt";
+    String contents = "contents";
+
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
+    PushOneCommit.Result r1 = push.to(git, "refs/for/master");
+    merge(r1);
+
+    push = pushFactory.create(db, admin.getIdent(),
+        "non-conflicting", "b.txt", "other contents");
+    PushOneCommit.Result r2 = push.to(git, "refs/for/master");
+    merge(r2);
+
+    git.checkout().setName(r1.getCommit().name()).call();
+    push = pushFactory.create(db, admin.getIdent(), subject, file, contents);
+    PushOneCommit.Result r3 = push.to(git, "refs/for/master");
+    revision(r3).review(ReviewInput.recommend());
+    assertApproval(r3, 1);
+
+    rebase(r3);
+    assertApproval(r3, 0);
+  }
+
+  @Test
+  public void copyAllScoresOnTrivialRebase() throws Exception {
+    String subject = "test commit";
+    String file = "a.txt";
+    String contents = "contents";
+    codeReview.setCopyAllScoresOnTrivialRebase(true);
+    saveLabelConfig();
+
+    PushOneCommit push = pushFactory.create(db, admin.getIdent());
+    PushOneCommit.Result r1 = push.to(git, "refs/for/master");
+    merge(r1);
+
+    push = pushFactory.create(db, admin.getIdent(),
+        "non-conflicting", "b.txt", "other contents");
+    PushOneCommit.Result r2 = push.to(git, "refs/for/master");
+    merge(r2);
+
+    git.checkout().setName(r1.getCommit().name()).call();
+    push = pushFactory.create(db, admin.getIdent(), subject, file, contents);
+    PushOneCommit.Result r3 = push.to(git, "refs/for/master");
+    revision(r3).review(ReviewInput.recommend());
+    assertApproval(r3, 1);
+
+    rebase(r3);
+    assertApproval(r3, 1);
+  }
+
+  private void saveLabelConfig() throws Exception {
+    ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
+    cfg.getLabelSections().clear();
+    cfg.getLabelSections().put(codeReview.getName(), codeReview);
+    saveProjectConfig(cfg);
+  }
+
+  private void saveProjectConfig(ProjectConfig cfg) throws Exception {
+    MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
+    try {
+      cfg.commit(md);
+    } finally {
+      md.close();
+    }
+  }
+
+  private void merge(PushOneCommit.Result r) throws Exception {
+    revision(r).review(ReviewInput.approve());
+    revision(r).submit();
+    Repository repo = repoManager.openRepository(project);
+    try {
+      assertEquals(r.getCommitId(),
+          repo.getRef("refs/heads/master").getObjectId());
+    } finally {
+      repo.close();
+    }
+  }
+
+  private void rebase(PushOneCommit.Result r) throws Exception {
+    revision(r).rebase();
+  }
+
+  private void assertApproval(PushOneCommit.Result r, int expected)
+      throws Exception {
+    // Don't use asserts from PushOneCommit so we can test the round-trip
+    // through JSON instead of querying the DB directly.
+    ChangeInfo c = get(r.getChangeId());
+    LabelInfo cr = c.labels.get("Code-Review");
+    assertEquals(1, cr.all.size());
+    assertEquals("Administrator", cr.all.get(0).name);
+    assertEquals(expected, cr.all.get(0).value.intValue());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
index 94e6f6a..aa9703c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUCK
@@ -2,5 +2,5 @@
 
 acceptance_tests(
   srcs = glob(['*IT.java']),
-  deps = ['//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util'],
+  deps = ['//gerrit-acceptance-tests:lib'],
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
index f10258c..9f859dc7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
@@ -14,16 +14,14 @@
 
 package com.google.gerrit.acceptance.ssh;
 
-import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.GitUtil.createProject;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.AccountCreator;
 import com.google.gerrit.acceptance.GcAssert;
 import com.google.gerrit.acceptance.SshSession;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.UseLocalDisk;
 import com.google.gerrit.common.data.GarbageCollectionResult;
 import com.google.gerrit.reviewdb.client.Project;
@@ -45,9 +43,6 @@
 public class GarbageCollectionIT extends AbstractDaemonTest {
 
   @Inject
-  private AccountCreator accounts;
-
-  @Inject
   private AllProjectsName allProjects;
 
   @Inject
@@ -59,20 +54,12 @@
   @Inject
   private GcAssert gcAssert;
 
-  private TestAccount admin;
-  private SshSession sshSession;
   private Project.NameKey project1;
   private Project.NameKey project2;
   private Project.NameKey project3;
 
   @Before
   public void setUp() throws Exception {
-    admin =
-        accounts.create("admin", "admin@example.com", "Administrator",
-            "Administrators");
-
-    sshSession = new SshSession(server, admin);
-
     project1 = new Project.NameKey("p1");
     createProject(sshSession, project1.get());
 
@@ -107,9 +94,10 @@
   @Test
   public void testGcWithoutCapability_Error() throws IOException, OrmException,
       JSchException {
-    SshSession s = new SshSession(server, accounts.create("user", "user@example.com", "User"));
+    SshSession s = new SshSession(server, user);
     s.exec("gerrit gc --all");
     assertError("Capability runGC is required to access this resource", s.getError());
+    s.close();
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/tests.defs b/gerrit-acceptance-tests/tests.defs
index f125a39..3c8b4d5 100644
--- a/gerrit-acceptance-tests/tests.defs
+++ b/gerrit-acceptance-tests/tests.defs
@@ -1,7 +1,7 @@
 def acceptance_tests(
     srcs,
     deps = [],
-    vm_args = ['-Xmx128m']):
+    vm_args = ['-Xmx256m']):
   for j in srcs:
     java_test(
       name = j[:-len('.java')],
diff --git a/gerrit-antlr/.gitignore b/gerrit-antlr/.gitignore
deleted file mode 100644
index fb047af..0000000
--- a/gerrit-antlr/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-antlr.iml
\ No newline at end of file
diff --git a/gerrit-antlr/.settings/org.eclipse.core.resources.prefs b/gerrit-antlr/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index e9441bb..0000000
--- a/gerrit-antlr/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-cache-h2/.gitignore b/gerrit-cache-h2/.gitignore
deleted file mode 100644
index cb430b8..0000000
--- a/gerrit-cache-h2/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-cache-h2.iml
diff --git a/gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs b/gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index f9fe345..0000000
--- a/gerrit-cache-h2/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-cache-h2/.settings/org.eclipse.core.runtime.prefs b/gerrit-cache-h2/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-cache-h2/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-cache-h2/.settings/org.eclipse.jdt.core.prefs b/gerrit-cache-h2/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 470942d..0000000
--- a/gerrit-cache-h2/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-cache-h2/.settings/org.eclipse.jdt.ui.prefs b/gerrit-cache-h2/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-cache-h2/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
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 1c850d1..ae999f6 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
@@ -27,7 +27,6 @@
 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;
 
@@ -39,13 +38,7 @@
   public static class Module extends LifecycleModule {
     @Override
     protected void configure() {
-      install(new FactoryModule() {
-        @Override
-        protected void configure() {
-          factory(ForwardingRemovalListener.Factory.class);
-        }
-      });
-
+      factory(ForwardingRemovalListener.Factory.class);
       bind(DefaultCacheFactory.class);
       bind(MemoryCacheFactory.class).to(DefaultCacheFactory.class);
       bind(PersistentCacheFactory.class).to(H2CacheFactory.class);
@@ -118,10 +111,9 @@
     return !Strings.isNullOrEmpty(cfg.getString("cache", name, var));
   }
 
-  @SuppressWarnings({"rawtypes", "unchecked"})
+  @SuppressWarnings("unchecked")
   private static <K, V> CacheBuilder<K, V> newCacheBuilder() {
-    CacheBuilder builder = CacheBuilder.newBuilder();
-    return builder;
+    return (CacheBuilder<K, V>) CacheBuilder.newBuilder();
   }
 
   private static <K, V> Weigher<K, V> unitWeight() {
diff --git a/gerrit-common/.gitignore b/gerrit-common/.gitignore
deleted file mode 100644
index 759f12c..0000000
--- a/gerrit-common/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-common.iml
\ No newline at end of file
diff --git a/gerrit-common/.settings/org.eclipse.core.resources.prefs b/gerrit-common/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index f9fe345..0000000
--- a/gerrit-common/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-common/.settings/org.eclipse.core.runtime.prefs b/gerrit-common/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-common/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-common/.settings/org.eclipse.jdt.core.prefs b/gerrit-common/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 470942d..0000000
--- a/gerrit-common/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-common/.settings/org.eclipse.jdt.ui.prefs b/gerrit-common/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-common/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-common/BUCK b/gerrit-common/BUCK
index 94b0a01..9ed1624 100644
--- a/gerrit-common/BUCK
+++ b/gerrit-common/BUCK
@@ -1,5 +1,17 @@
 SRC = 'src/main/java/com/google/gerrit/'
 
+ANNOTATIONS = [
+  SRC + 'common/Nullable.java',
+  SRC + 'common/audit/Audit.java',
+  SRC + 'common/auth/SignInRequired.java',
+]
+
+java_library(
+  name = 'annotations',
+  srcs = ANNOTATIONS,
+  visibility = ['PUBLIC'],
+)
+
 gwt_module(
   name = 'client',
   srcs = glob([SRC + 'common/**/*.java']),
@@ -8,6 +20,9 @@
     '//gerrit-patch-jgit:client',
     '//gerrit-prettify:client',
     '//gerrit-reviewdb:client',
+  ],
+  compile_deps = [
+    ':annotations',
     '//lib:gwtjsonrpc',
     '//lib:gwtorm',
     '//lib/jgit:jgit',
@@ -17,8 +32,9 @@
 
 java_library(
   name = 'server',
-  srcs = glob([SRC + 'common/**/*.java']),
+  srcs = glob([SRC + 'common/**/*.java'], excludes = ANNOTATIONS),
   deps = [
+    ':annotations',
     '//gerrit-patch-jgit:server',
     '//gerrit-prettify:server',
     '//gerrit-reviewdb:server',
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 e72c3d0..e4199c2 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
@@ -35,9 +35,8 @@
   public static final String SETTINGS_NEW_AGREEMENT = "/settings/new-agreement";
   public static final String REGISTER = "/register";
 
-  public static final String TOP = "n,z";
-
   public static final String MINE = "/";
+  public static final String QUERY = "/q/";
   public static final String PROJECTS = "/projects/";
   public static final String DASHBOARDS = ",dashboards/";
   public static final String ADMIN_GROUPS = "/admin/groups/";
@@ -79,8 +78,12 @@
     return "/admin/projects/" + p.get() + ",access";
   }
 
+  public static String toProjectBranches(Project.NameKey p) {
+    return "/admin/projects/" + p.get() + ",branches";
+  }
+
   public static String toAccountQuery(String fullname, Status status) {
-    return toChangeQuery(op("owner", fullname) + " " + status(status), TOP);
+    return toChangeQuery(op("owner", fullname) + " " + status(status));
   }
 
   public static String toCustomDashboard(final String params) {
@@ -91,12 +94,13 @@
     return ADMIN_PROJECTS + proj.get() + ",dashboards";
   }
 
-  public static String toChangeQuery(final String query) {
-    return toChangeQuery(query, TOP);
+  public static String toChangeQuery(String query) {
+    return QUERY + KeyUtil.encode(query);
   }
 
-  public static String toChangeQuery(String query, String page) {
-    return "/q/" + KeyUtil.encode(query) + "," + page;
+  public static String toChangeQuery(String query, String start) {
+    int s = Integer.parseInt(start);
+    return QUERY + KeyUtil.encode(query) + (s > 0 ? "," + s : "");
   }
 
   public static String toProjectDashboard(Project.NameKey name, String id) {
@@ -132,6 +136,10 @@
     }
   }
 
+  public static String op(String op, int value) {
+    return op + ":" + value;
+  }
+
   public static String op(String op, String value) {
     if (isSingleWord(value)) {
       return op + ":" + value;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/ProjectAccessUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/ProjectAccessUtil.java
index 12e47b1..5e297d4 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/ProjectAccessUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/ProjectAccessUtil.java
@@ -25,9 +25,8 @@
 import java.util.Set;
 
 public class ProjectAccessUtil {
-  public static List<AccessSection> mergeSections(final List<AccessSection> src) {
-    final Map<String, AccessSection> map =
-        new LinkedHashMap<String, AccessSection>();
+  public static List<AccessSection> mergeSections(List<AccessSection> src) {
+    Map<String, AccessSection> map = new LinkedHashMap<>();
     for (final AccessSection section : src) {
       if (section.getPermissions().isEmpty()) {
         continue;
@@ -40,14 +39,14 @@
         map.put(section.getName(), section);
       }
     }
-    return new ArrayList<AccessSection>(map.values());
+    return new ArrayList<>(map.values());
   }
 
   public static List<AccessSection> removeEmptyPermissionsAndSections(
       final List<AccessSection> src) {
-    final Set<AccessSection> sectionsToRemove = new HashSet<AccessSection>();
+    final Set<AccessSection> sectionsToRemove = new HashSet<>();
     for (final AccessSection section : src) {
-      final Set<Permission> permissionsToRemove = new HashSet<Permission>();
+      final Set<Permission> permissionsToRemove = new HashSet<>();
       for (final Permission permission : section.getPermissions()) {
         if (permission.getRules().isEmpty()) {
           permissionsToRemove.add(permission);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/auth/SignInRequired.java b/gerrit-common/src/main/java/com/google/gerrit/common/auth/SignInRequired.java
index cd9fa25..1b9011a 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/auth/SignInRequired.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/auth/SignInRequired.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.common.auth;
 
-import com.google.gerrit.common.errors.NotSignedInException;
-
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -24,8 +22,9 @@
 /**
  * Annotation indicating a service method requires a current user.
  * <p>
- * If there is no current user then {@link NotSignedInException} will be given
- * to the callback's onFailure method.
+ * If there is no current user then
+ * {@code com.google.gerrit.common.errors.NotSignedInException} will be given to
+ * the callback's onFailure method.
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java b/gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java
index 777467d..4a9ddf8 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/changes/Side.java
@@ -16,5 +16,5 @@
 
 /** The side on which a comment was added. */
 public enum Side {
-  PARENT, REVISION;
+  PARENT, REVISION
 }
\ No newline at end of file
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
index 5ddf1ae..9a222ed 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
@@ -39,13 +39,13 @@
 
   public List<Permission> getPermissions() {
     if (permissions == null) {
-      permissions = new ArrayList<Permission>();
+      permissions = new ArrayList<>();
     }
     return permissions;
   }
 
   public void setPermissions(List<Permission> list) {
-    Set<String> names = new HashSet<String>();
+    Set<String> names = new HashSet<>();
     for (Permission p : list) {
       if (!names.add(p.getName().toLowerCase())) {
         throw new IllegalArgumentException();
@@ -124,7 +124,7 @@
     if (!super.equals(obj) || !(obj instanceof AccessSection)) {
       return false;
     }
-    return new HashSet<Permission>(getPermissions()).equals(new HashSet<Permission>(
+    return new HashSet<Permission>(getPermissions()).equals(new HashSet<>(
         ((AccessSection) obj).getPermissions()));
   }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java
index 92c2d6c..6b8c075 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfo.java
@@ -52,6 +52,10 @@
     return id;
   }
 
+  public void setFullName(String n) {
+    fullName = n;
+  }
+
   /** @return the full name of the account holder; null if not supplied */
   public String getFullName() {
     return fullName;
@@ -87,10 +91,10 @@
    * <p>
    * Example output:
    * <ul>
-   * <li><code>A U. Thor &lt;author@example.com&gt;</code>: full populated</li>
-   * <li><code>A U. Thor (12)</code>: missing email address</li>
-   * <li><code>Anonymous Coward &lt;author@example.com&gt;</code>: missing name</li>
-   * <li><code>Anonymous Coward (12)</code>: missing name and email address</li>
+   * <li>{@code A U. Thor &lt;author@example.com&gt;}: full populated</li>
+   * <li>{@code A U. Thor (12)}: missing email address</li>
+   * <li>{@code Anonymous Coward &lt;author@example.com&gt;}: missing name</li>
+   * <li>{@code Anonymous Coward (12)}: missing name and email address</li>
    * </ul>
    */
   public String getNameEmail(String anonymousCowardName) {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfoCache.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfoCache.java
index bc028e8..d7803c1 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfoCache.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountInfoCache.java
@@ -20,7 +20,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
-/** In-memory table of {@link AccountInfo}, indexed by {@link Account.Id}. */
+/** In-memory table of {@link AccountInfo}, indexed by {@code Account.Id}. */
 public class AccountInfoCache {
   private static final AccountInfoCache EMPTY;
   static {
@@ -39,7 +39,7 @@
   }
 
   public AccountInfoCache(final Iterable<AccountInfo> list) {
-    accounts = new HashMap<Account.Id, AccountInfo>();
+    accounts = new HashMap<>();
     for (final AccountInfo ai : list) {
       accounts.put(ai.getId(), ai);
     }
@@ -50,9 +50,9 @@
    * <p>
    * The return value can take on one of three forms:
    * <ul>
-   * <li><code>null</code>, if <code>id == null</code>.</li>
-   * <li>a valid info block, if <code>id</code> was loaded.</li>
-   * <li>an anonymous info block, if <code>id</code> was not loaded.</li>
+   * <li>{@code null}, if {@code id == null}.</li>
+   * <li>a valid info block, if {@code id} was loaded.</li>
+   * <li>an anonymous info block, if {@code id} was not loaded.</li>
    * </ul>
    *
    * @param id the id desired.
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 18cf657..5a7559d 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
@@ -34,9 +34,6 @@
   @SignInRequired
   void myAccount(AsyncCallback<Account> callback);
 
-  @SignInRequired
-  void myDiffPreferences(AsyncCallback<AccountDiffPreference> callback);
-
   @Audit
   @SignInRequired
   void changePreferences(AccountGeneralPreferences pref,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
index 36a9814..f09241d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ApprovalDetail.java
@@ -30,7 +30,7 @@
 public class ApprovalDetail {
   public static List<ApprovalDetail> sort(Collection<ApprovalDetail> ads,
       final int owner) {
-    List<ApprovalDetail> sorted = new ArrayList<ApprovalDetail>(ads);
+    List<ApprovalDetail> sorted = new ArrayList<>(ads);
     Collections.sort(sorted, new Comparator<ApprovalDetail>() {
       public int compare(ApprovalDetail o1, ApprovalDetail o2) {
         int byOwner = (o2.account.get() == owner ? 1 : 0)
@@ -56,7 +56,7 @@
 
   public ApprovalDetail(final Account.Id id) {
     account = id;
-    approvals = new ArrayList<PatchSetApproval>();
+    approvals = new ArrayList<>();
   }
 
   public Account.Id getAccount() {
@@ -73,7 +73,7 @@
 
   public void approved(String label) {
     if (approved == null) {
-      approved = new HashSet<String>();
+      approved = new HashSet<>();
     }
     approved.add(label);
     hasNonZero = 1;
@@ -81,7 +81,7 @@
 
   public void rejected(String label) {
     if (rejected == null) {
-      rejected = new HashSet<String>();
+      rejected = new HashSet<>();
     }
     rejected.add(label);
     hasNonZero = 1;
@@ -89,14 +89,14 @@
 
   public void votable(String label) {
     if (votable == null) {
-      votable = new HashSet<String>();
+      votable = new HashSet<>();
     }
     votable.add(label);
   }
 
   public void value(String label, int value) {
     if (values == null) {
-      values = new HashMap<String, Integer>();
+      values = new HashMap<>();
     }
     values.put(label, value);
     if (value != 0) {
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 c50d2e3..5824415 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
@@ -15,9 +15,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;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
@@ -27,19 +25,9 @@
 @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);
-
-  @SignInRequired
-  void patchSetPublishDetail(PatchSet.Id key,
-      AsyncCallback<PatchSetPublishDetail> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeInfo.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeInfo.java
index f2e833f..31fd827 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeInfo.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeInfo.java
@@ -35,10 +35,14 @@
   protected PatchSet.Id patchSetId;
   protected boolean latest;
 
-  protected ChangeInfo() {
+  public ChangeInfo() {
   }
 
   public ChangeInfo(final Change c, final PatchSet.Id patchId) {
+    set(c, patchId);
+  }
+
+  public void set(final Change c, final PatchSet.Id patchId) {
     id = c.getId();
     key = c.getKey();
     owner = c.getOwner();
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
deleted file mode 100644
index d0e5154..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.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;
-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;
-
-@RpcImpl(version = Version.V2_0)
-public interface ChangeManageService extends RemoteJsonService {
-  @Audit
-  @SignInRequired
-  void createNewPatchSet(final PatchSet.Id patchSetId, final String newCommitMessage,
-      final 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/CommentDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/CommentDetail.java
index 3cb3f05..db78c4d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/CommentDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/CommentDetail.java
@@ -33,9 +33,9 @@
   private transient Map<Integer, List<PatchLineComment>> forA;
   private transient Map<Integer, List<PatchLineComment>> forB;
 
-  public CommentDetail(final PatchSet.Id idA, final PatchSet.Id idB) {
-    this.a = new ArrayList<PatchLineComment>();
-    this.b = new ArrayList<PatchLineComment>();
+  public CommentDetail(PatchSet.Id idA, PatchSet.Id idB) {
+    this.a = new ArrayList<>();
+    this.b = new ArrayList<>();
     this.idA = idA;
     this.idB = idB;
   }
@@ -121,18 +121,18 @@
     // possible for several comments to have the same parent (this can happen if two reviewers
     // click Reply on the same comment at the same time). Such comments will be displayed under
     // their correct parent in chronological order.
-    Map<String, List<PatchLineComment>> parentMap = new HashMap<String, List<PatchLineComment>>();
+    Map<String, List<PatchLineComment>> parentMap = new HashMap<>();
 
     // It's possible to have more than one root comment if two reviewers create a comment on the
     // same line at the same time
-    List<PatchLineComment> rootComments = new ArrayList<PatchLineComment>();
+    List<PatchLineComment> rootComments = new ArrayList<>();
 
     // Store all the comments in parentMap, keyed by their parent
     for (PatchLineComment c : comments) {
       String parentUuid = c.getParentUuid();
       List<PatchLineComment> l = parentMap.get(parentUuid);
       if (l == null) {
-        l = new ArrayList<PatchLineComment>();
+        l = new ArrayList<>();
         parentMap.put(parentUuid, l);
       }
       l.add(c);
@@ -141,14 +141,14 @@
 
     // Add the comments in the list, starting with the head and then going through all the
     // comments that have it as a parent, and so on
-    List<PatchLineComment> result = new ArrayList<PatchLineComment>();
+    List<PatchLineComment> result = new ArrayList<>();
     addChildren(parentMap, rootComments, result);
 
     return result;
   }
 
   /**
-   * Add the comments to <code>outResult</code>, depth first
+   * Add the comments to {@code outResult}, depth first
    */
   private static void addChildren(Map<String, List<PatchLineComment>> parentMap,
       List<PatchLineComment> children, List<PatchLineComment> outResult) {
@@ -161,14 +161,12 @@
   }
 
   private Map<Integer, List<PatchLineComment>> index(
-      final List<PatchLineComment> in) {
-    final HashMap<Integer, List<PatchLineComment>> r;
-
-    r = new HashMap<Integer, List<PatchLineComment>>();
+      List<PatchLineComment> in) {
+    HashMap<Integer, List<PatchLineComment>> r = new HashMap<>();
     for (final PatchLineComment p : in) {
       List<PatchLineComment> l = r.get(p.getLine());
       if (l == null) {
-        l = new ArrayList<PatchLineComment>();
+        l = new ArrayList<>();
         r.put(p.getLine(), l);
       }
       l.add(p);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ContributorAgreement.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ContributorAgreement.java
index e02d9d3..d475980 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ContributorAgreement.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ContributorAgreement.java
@@ -54,7 +54,7 @@
 
   public List<PermissionRule> getAccepted() {
     if (accepted == null) {
-      accepted = new ArrayList<PermissionRule>();
+      accepted = new ArrayList<>();
     }
     return accepted;
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GarbageCollectionResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GarbageCollectionResult.java
index e4d7b80..a052f4f 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GarbageCollectionResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GarbageCollectionResult.java
@@ -23,7 +23,7 @@
   protected List<Error> errors;
 
   public GarbageCollectionResult() {
-    errors = new ArrayList<Error>();
+    errors = new ArrayList<>();
   }
 
   public void addError(Error e) {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
index c4c388f..38afaab 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GerritConfig.java
@@ -32,6 +32,7 @@
   protected String switchAccountUrl;
   protected String httpPasswordUrl;
   protected String reportBugUrl;
+  protected String reportBugText;
   protected boolean gitBasicAuth;
 
   protected GitwebConfig gitweb;
@@ -48,11 +49,12 @@
   protected Project.NameKey wildProject;
   protected Set<Account.FieldName> editableAccountFields;
   protected boolean documentationAvailable;
-  protected boolean testChangeMerge;
   protected String anonymousCowardName;
   protected int suggestFrom;
   protected int changeUpdateDelay;
   protected AccountGeneralPreferences.ChangeScreen changeScreen;
+  protected int largeChangeSize;
+  protected boolean newFeatures;
 
   public String getLoginUrl() {
     return loginUrl;
@@ -102,6 +104,14 @@
     reportBugUrl = u;
   }
 
+  public String getReportBugText() {
+    return reportBugText;
+  }
+
+  public void setReportBugText(String t) {
+    reportBugText = t;
+  }
+
   public boolean isGitBasicAuth() {
     return gitBasicAuth;
   }
@@ -232,14 +242,6 @@
     documentationAvailable = available;
   }
 
-  public boolean testChangeMerge() {
-    return testChangeMerge;
-  }
-
-  public void setTestChangeMerge(final boolean test) {
-    testChangeMerge = test;
-  }
-
   public String getAnonymousCowardName() {
     return anonymousCowardName;
   }
@@ -280,4 +282,20 @@
   public void setChangeScreen(AccountGeneralPreferences.ChangeScreen ui) {
     this.changeScreen = ui;
   }
+
+  public int getLargeChangeSize() {
+    return largeChangeSize;
+  }
+
+  public void setLargeChangeSize(int largeChangeSize) {
+    this.largeChangeSize = largeChangeSize;
+  }
+
+  public boolean getNewFeatures() {
+    return newFeatures;
+  }
+
+  public void setNewFeatures(boolean n) {
+    newFeatures = n;
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GitWebType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GitWebType.java
index 5ef6d6a..8219d27 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GitWebType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GitWebType.java
@@ -31,16 +31,18 @@
       type.setProject("?p=${project}.git;a=summary");
       type.setRevision("?p=${project}.git;a=commit;h=${commit}");
       type.setBranch("?p=${project}.git;a=shortlog;h=${branch}");
+      type.setRootTree("?p=${project}.git;a=tree;hb=${commit}");
+      type.setFile("?p=${project}.git;hb=${commit};f=${file}");
       type.setFileHistory("?p=${project}.git;a=history;hb=${branch};f=${file}");
-
     } else if (name.equalsIgnoreCase("cgit")) {
       type = new GitWebType();
       type.setLinkName("cgit");
       type.setProject("${project}.git/summary");
       type.setRevision("${project}.git/commit/?id=${commit}");
       type.setBranch("${project}.git/log/?h=${branch}");
+      type.setRootTree("${project}.git/tree/?h=${commit}");
+      type.setFile("${project}.git/tree/${file}?h=${commit}");
       type.setFileHistory("${project}.git/log/${file}?h=${branch}");
-
     } else if (name.equalsIgnoreCase("custom")) {
       type = new GitWebType();
       // The custom name is not defined, let's keep the old style of using GitWeb
@@ -62,13 +64,19 @@
   /** String for revision view url. */
   private String revision;
 
-  /** ParamertizedString for project view url. */
+  /** ParameterizedString for project view url. */
   private String project;
 
-  /** ParamertizedString for branch view url. */
+  /** ParameterizedString for branch view url. */
   private String branch;
 
-  /** ParamertizedString for file history view url. */
+  /** ParameterizedString for root tree view url. */
+  private String rootTree;
+
+  /** ParameterizedString for file view url. */
+  private String file;
+
+  /** ParameterizedString for file history view url. */
   private String fileHistory;
 
   /** Character to substitute the standard path separator '/' in branch and
@@ -122,6 +130,24 @@
   }
 
   /**
+   * Get the String for root tree view.
+   *
+   * @return The String for root tree view
+   */
+  public String getRootTree() {
+    return rootTree;
+  }
+
+  /**
+   * Get the String for file view.
+   *
+   * @return The String for file view
+   */
+  public String getFile() {
+    return file;
+  }
+
+  /**
    * Get the String for file history view.
    *
    * @return The String for file history view
@@ -184,6 +210,28 @@
   }
 
   /**
+   * Set the pattern for root tree view.
+   *
+   * @param pattern The pattern for root tree view
+   */
+  public void setRootTree(final String pattern) {
+    if (pattern != null && !pattern.isEmpty()) {
+      rootTree = pattern;
+    }
+  }
+
+  /**
+   * Set the pattern for file view.
+   *
+   * @param pattern The pattern for file view
+   */
+  public void setFile(final String pattern) {
+    if (pattern != null && !pattern.isEmpty()) {
+      file = pattern;
+    }
+  }
+
+  /**
    * Set the pattern for file history view.
    *
    * @param pattern The pattern for file history view
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index 1fd98b6..80a04fa 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -79,6 +79,9 @@
   /** Can perform streaming of Gerrit events. */
   public static final String STREAM_EVENTS = "streamEvents";
 
+  /** Can view all accounts, regardless of {@code accounts.visibility}. */
+  public static final String VIEW_ALL_ACCOUNTS = "viewAllAccounts";
+
   /** Can view the server's current cache states. */
   public static final String VIEW_CACHES = "viewCaches";
 
@@ -92,7 +95,7 @@
   private static final List<String> NAMES_LC;
 
   static {
-    NAMES_ALL = new ArrayList<String>();
+    NAMES_ALL = new ArrayList<>();
     NAMES_ALL.add(ACCESS_DATABASE);
     NAMES_ALL.add(ADMINISTRATE_SERVER);
     NAMES_ALL.add(CREATE_ACCOUNT);
@@ -106,11 +109,12 @@
     NAMES_ALL.add(RUN_AS);
     NAMES_ALL.add(RUN_GC);
     NAMES_ALL.add(STREAM_EVENTS);
+    NAMES_ALL.add(VIEW_ALL_ACCOUNTS);
     NAMES_ALL.add(VIEW_CACHES);
     NAMES_ALL.add(VIEW_CONNECTIONS);
     NAMES_ALL.add(VIEW_QUEUE);
 
-    NAMES_LC = new ArrayList<String>(NAMES_ALL.size());
+    NAMES_LC = new ArrayList<>(NAMES_ALL.size());
     for (String name : NAMES_ALL) {
       NAMES_LC.add(name.toLowerCase());
     }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupInfoCache.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupInfoCache.java
index 5a0561d..b700853 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupInfoCache.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupInfoCache.java
@@ -20,7 +20,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
-/** In-memory table of {@link GroupInfo}, indexed by {@link AccountGroup.Id}. */
+/** In-memory table of {@link GroupInfo}, indexed by {@code AccountGroup.Id}. */
 public class GroupInfoCache {
   private static final GroupInfoCache EMPTY;
   static {
@@ -39,7 +39,7 @@
   }
 
   public GroupInfoCache(final Iterable<GroupInfo> list) {
-    groups = new HashMap<AccountGroup.UUID, GroupInfo>();
+    groups = new HashMap<>();
     for (final GroupInfo gi : list) {
       groups.put(gi.getId(), gi);
     }
@@ -50,9 +50,9 @@
    * <p>
    * The return value can take on one of three forms:
    * <ul>
-   * <li><code>null</code>, if <code>id == null</code>.</li>
-   * <li>a valid info block, if <code>id</code> was loaded.</li>
-   * <li>an anonymous info block, if <code>id</code> was not loaded.</li>
+   * <li>{@code null}, if {@code id == null}.</li>
+   * <li>a valid info block, if {@code id} was loaded.</li>
+   * <li>an anonymous info block, if {@code id} was not loaded.</li>
    * </ul>
    *
    * @param uuid the id desired.
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
index f143405..323af23 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 
+import java.util.Date;
 import java.util.List;
 
 /** Data sent as part of the host page, to bootstrap the UI. */
@@ -28,6 +29,7 @@
   public GerritConfig config;
   public Theme theme;
   public List<String> plugins;
+  public List<Message> messages;
 
   public static class Theme {
     public String backgroundColor;
@@ -39,4 +41,10 @@
     public String tableOddRowColor;
     public String tableEvenRowColor;
   }
+
+  public static class Message {
+    public String id;
+    public Date redisplay;
+    public String html;
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
index 7fd8864..c596c1e 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
@@ -27,20 +27,29 @@
 public class LabelType {
   public static LabelType withDefaultValues(String name) {
     checkName(name);
-    List<LabelValue> values = new ArrayList<LabelValue>(2);
+    List<LabelValue> values = new ArrayList<>(2);
     values.add(new LabelValue((short) 0, "Rejected"));
     values.add(new LabelValue((short) 1, "Approved"));
     return new LabelType(name, values);
   }
 
-  private static String checkName(String name) {
+  public static String checkName(String name) {
+    checkNameInternal(name);
     if ("SUBM".equals(name)) {
       throw new IllegalArgumentException(
           "Reserved label name \"" + name + "\"");
     }
+    return name;
+  }
+
+  public static String checkNameInternal(String name) {
+    if (name == null || name.isEmpty()) {
+      throw new IllegalArgumentException("Empty label name");
+    }
     for (int i = 0; i < name.length(); i++) {
       char c = name.charAt(i);
-      if (!((c >= 'a' && c <= 'z') ||
+      if ((i == 0 && c == '-') ||
+          !((c >= 'a' && c <= 'z') ||
             (c >= 'A' && c <= 'Z') ||
             (c >= '0' && c <= '9') ||
             c == '-')) {
@@ -66,7 +75,7 @@
   }
 
   private static List<LabelValue> sortValues(List<LabelValue> values) {
-    values = new ArrayList<LabelValue>(values);
+    values = new ArrayList<>(values);
     if (values.size() <= 1) {
       return Collections.unmodifiableList(values);
     }
@@ -79,7 +88,7 @@
     short max = values.get(values.size() - 1).getValue();
     short v = min;
     short i = 0;
-    List<LabelValue> result = new ArrayList<LabelValue>(max - min + 1);
+    List<LabelValue> result = new ArrayList<>(max - min + 1);
     // Fill in any missing values with empty text.
     while (i < values.size()) {
       while (v < values.get(i).getValue()) {
@@ -243,7 +252,7 @@
 
   private void initByValue() {
     if (byValue == null) {
-      byValue = new HashMap<Short, LabelValue>();
+      byValue = new HashMap<>();
       for (final LabelValue v : values) {
         byValue.put(v.getValue(), v);
       }
@@ -252,7 +261,7 @@
 
   public List<Integer> getValuesAsList() {
     if (intList == null) {
-      intList = new ArrayList<Integer>(values.size());
+      intList = new ArrayList<>(values.size());
       for (LabelValue v : values) {
         intList.add(Integer.valueOf(v.getValue()));
       }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelTypes.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelTypes.java
index 34a45a4..b47445e 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelTypes.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelTypes.java
@@ -25,8 +25,8 @@
 
 public class LabelTypes {
   protected List<LabelType> labelTypes;
-  private transient Map<String, LabelType> byLabel;
-  private transient Map<String, Integer> positions;
+  private transient volatile Map<String, LabelType> byLabel;
+  private transient volatile Map<String, Integer> positions;
 
   protected LabelTypes() {
   }
@@ -50,10 +50,15 @@
 
   private Map<String, LabelType> byLabel() {
     if (byLabel == null) {
-      byLabel = new HashMap<String, LabelType>();
-      if (labelTypes != null) {
-        for (LabelType t : labelTypes) {
-          byLabel.put(t.getName().toLowerCase(), t);
+      synchronized (this) {
+        if (byLabel == null) {
+          Map<String, LabelType> l = new HashMap<>();
+          if (labelTypes != null) {
+            for (LabelType t : labelTypes) {
+              l.put(t.getName().toLowerCase(), t);
+            }
+          }
+          byLabel = l;
         }
       }
     }
@@ -88,11 +93,16 @@
 
   private Map<String, Integer> positions() {
     if (positions == null) {
-      positions = new HashMap<String, Integer>();
-      if (labelTypes != null) {
-        int i = 0;
-        for (LabelType t : labelTypes) {
-          positions.put(t.getName(), i++);
+      synchronized (this) {
+        if (positions == null) {
+          Map<String, Integer> p = new HashMap<>();
+          if (labelTypes != null) {
+            int i = 0;
+            for (LabelType t : labelTypes) {
+              p.put(t.getName(), i++);
+            }
+          }
+          positions = p;
         }
       }
     }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java
index 2a70d6c..454324b 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ParameterizedString.java
@@ -21,7 +21,7 @@
 import java.util.List;
 import java.util.Map;
 
-/** Performs replacements on strings such as <code>Hello ${user}</code>. */
+/** Performs replacements on strings such as {@code Hello ${user}}. */
 public class ParameterizedString {
   /** Obtain a string which has no parameters and always produces the value. */
   public static ParameterizedString asis(final String constant) {
@@ -46,8 +46,8 @@
 
   public ParameterizedString(final String pattern) {
     final StringBuilder raw = new StringBuilder();
-    final List<Parameter> prs = new ArrayList<Parameter>(4);
-    final List<Format> ops = new ArrayList<Format>(4);
+    final List<Parameter> prs = new ArrayList<>(4);
+    final List<Format> ops = new ArrayList<>(4);
 
     int i = 0;
     while (i < pattern.length()) {
@@ -66,7 +66,7 @@
       // "${parameter[.functions...]}" -> "parameter[.functions...]"
       final Parameter p = new Parameter(pattern.substring(b + 2, e));
 
-      raw.append("{" + prs.size() + "}");
+      raw.append("{").append(prs.size()).append("}");
       prs.add(p);
       ops.add(p);
 
@@ -95,7 +95,7 @@
 
   /** Get the list of parameter names, ordered by appearance in the pattern. */
   public List<String> getParameterNames() {
-    final ArrayList<String> r = new ArrayList<String>(parameters.size());
+    final ArrayList<String> r = new ArrayList<>(parameters.size());
     for (Parameter p : parameters) {
       r.add(p.name);
     }
@@ -132,7 +132,7 @@
   }
 
   public final class Builder {
-    private final Map<String, String> params = new HashMap<String, String>();
+    private final Map<String, String> params = new HashMap<>();
 
     public Builder replace(final String name, final String value) {
       params.put(name, value);
@@ -169,7 +169,7 @@
     Parameter(final String parameter) {
       // "parameter[.functions...]" -> (parameter, functions...)
       final List<String> names = Arrays.asList(parameter.split("\\."));
-      final List<Function> functs = new ArrayList<Function>(names.size());
+      final List<Function> functs = new ArrayList<>(names.size());
 
       if (names.isEmpty()) {
         name = "";
@@ -207,7 +207,7 @@
   private static final Map<String, Function> FUNCTIONS = initFunctions();
 
   private static Map<String, Function> initFunctions() {
-    final HashMap<String, Function> m = new HashMap<String, Function>();
+    HashMap<String, Function> m = new HashMap<>();
     m.put("toLowerCase", new Function() {
       @Override
       String apply(String a) {
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 5b234c71..19fcbeb 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
@@ -15,46 +15,17 @@
 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.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
 import com.google.gwtjsonrpc.common.RpcImpl;
 import com.google.gwtjsonrpc.common.RpcImpl.Version;
-import com.google.gwtjsonrpc.common.VoidResult;
 
 @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);
-
-  /**
-   * Deletes the specified draft patch set. If the draft patch set is the only
-   * patch set of the change, then also the change gets deleted.
-   *
-   * @param psid ID of the draft patch set that should be deleted
-   * @param callback callback to report the result of the draft patch set
-   *        deletion operation; if the draft patch set was successfully deleted
-   *        {@link AsyncCallback#onSuccess(Object)} is invoked and the change
-   *        details are passed as parameter; if the change gets deleted because
-   *        the draft patch set that was deleted was the only patch set in the
-   *        change, then <code>null</code> is passed as result to
-   *        {@link AsyncCallback#onSuccess(Object)}
-   */
-  @Audit
-  @SignInRequired
-  void deleteDraftPatchSet(PatchSet.Id psid, AsyncCallback<ChangeDetail> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index 69263d9..0326e02 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -40,6 +40,7 @@
   public static final String REBASE = "rebase";
   public static final String REMOVE_REVIEWER = "removeReviewer";
   public static final String SUBMIT = "submit";
+  public static final String SUBMIT_AS = "submitAs";
   public static final String VIEW_DRAFTS = "viewDrafts";
 
   private static final List<String> NAMES_LC;
@@ -64,6 +65,7 @@
     NAMES_LC.add(REBASE.toLowerCase());
     NAMES_LC.add(REMOVE_REVIEWER.toLowerCase());
     NAMES_LC.add(SUBMIT.toLowerCase());
+    NAMES_LC.add(SUBMIT_AS.toLowerCase());
     NAMES_LC.add(VIEW_DRAFTS.toLowerCase());
     NAMES_LC.add(EDIT_TOPIC_NAME.toLowerCase());
     NAMES_LC.add(DELETE_DRAFTS.toLowerCase());
@@ -269,4 +271,24 @@
   public int hashCode() {
     return name.hashCode();
   }
+
+  @Override
+  public String toString() {
+    StringBuilder bldr = new StringBuilder();
+    bldr.append(name)
+        .append(" ");
+    if (exclusiveGroup) {
+      bldr.append("[exclusive] ");
+    }
+    bldr.append("[");
+    Iterator<PermissionRule> it = getRules().iterator();
+    while (it.hasNext()) {
+      bldr.append(it.next());
+      if (it.hasNext()) {
+        bldr.append(", ");
+      }
+    }
+    bldr.append("]");
+    return bldr.toString();
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
index 0363fd6..7be8e4e 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
@@ -45,7 +45,7 @@
 
     /** @return all values between {@link #getMin()} and {@link #getMax()} */
     public List<Integer> getValuesAsList() {
-      ArrayList<Integer> r = new ArrayList<Integer>(getRangeSize());
+      ArrayList<Integer> r = new ArrayList<>(getRangeSize());
       for (int i = min; i <= max; i++) {
         r.add(i);
       }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
index a7a66b1..bd05baf 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
@@ -20,7 +20,7 @@
   public static enum Action {
     ALLOW, DENY, BLOCK,
 
-    INTERACTIVE, BATCH;
+    INTERACTIVE, BATCH
   }
 
   protected Action action = Action.ALLOW;
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
index ee6cc95..ea0c8d2 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common.data;
 
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 
 import java.util.List;
@@ -28,8 +29,10 @@
   protected Set<String> ownerOf;
   protected boolean isConfigVisible;
   protected boolean canUpload;
+  protected boolean canChangeParent;
   protected LabelTypes labelTypes;
   protected Map<String, String> capabilities;
+  protected Map<AccountGroup.UUID, GroupInfo> groupInfo;
 
   public ProjectAccess() {
   }
@@ -107,6 +110,14 @@
     this.canUpload = canUpload;
   }
 
+  public boolean canChangeParent() {
+    return canChangeParent;
+  }
+
+  public void setCanChangeParent(boolean canChangeParent) {
+    this.canChangeParent = canChangeParent;
+  }
+
   public LabelTypes getLabelTypes() {
     return labelTypes;
   }
@@ -122,4 +133,12 @@
   public void setCapabilities(Map<String, String> capabilities) {
     this.capabilities = capabilities;
   }
+
+  public Map<AccountGroup.UUID, GroupInfo> getGroupInfo() {
+    return groupInfo;
+  }
+
+  public void setGroupInfo(Map<AccountGroup.UUID, GroupInfo> m) {
+    groupInfo = m;
+  }
 }
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 44f60861..652acac 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
@@ -33,11 +33,11 @@
   @Audit
   @SignInRequired
   void changeProjectAccess(Project.NameKey projectName, String baseRevision,
-      String message, List<AccessSection> sections,
+      String message, List<AccessSection> sections, Project.NameKey parentProjectName,
       AsyncCallback<ProjectAccess> callback);
 
   @SignInRequired
   void reviewProjectAccess(Project.NameKey projectName, String baseRevision,
-      String message, List<AccessSection> sections,
+      String message, List<AccessSection> sections, Project.NameKey parentProjectName,
       AsyncCallback<Change.Id> callback);
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
index 1c87c71..f82f434 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
@@ -27,7 +27,7 @@
   protected Change.Id changeId;
 
   public ReviewResult() {
-    errors = new ArrayList<Error>();
+    errors = new ArrayList<>();
   }
 
   public void addError(final Error e) {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java
index 063f13b..28a8340 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerInfo.java
@@ -51,7 +51,7 @@
       if (accountInfo.getPreferredEmail() != null) {
         return accountInfo.getPreferredEmail();
       }
-      return accountInfo.getFullName().toString();
+      return accountInfo.getFullName();
     }
     return groupReference.getName();
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
index d85095a..d696137 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewerResult.java
@@ -28,7 +28,7 @@
   protected boolean askForConfirmation;
 
   public ReviewerResult() {
-    errors = new ArrayList<Error>();
+    errors = new ArrayList<>();
   }
 
   public void addError(final Error e) {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitRecord.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitRecord.java
index 365f6a9..f4fe963 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitRecord.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SubmitRecord.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.reviewdb.client.Account;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Describes the state required to submit a change.
@@ -37,7 +38,7 @@
      * <p>
      * Additional detail may be available in {@link SubmitRecord#errorMessage}.
      */
-    RULE_ERROR;
+    RULE_ERROR
   }
 
   public Status status;
@@ -78,7 +79,7 @@
        * The likely cause is access has not been granted correctly by the
        * project owner or site administrator.
        */
-      IMPOSSIBLE;
+      IMPOSSIBLE
     }
 
     public String label;
@@ -94,6 +95,22 @@
       }
       return sb.toString();
     }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof Label) {
+        Label l = (Label) o;
+        return Objects.equals(label, l.label)
+            && Objects.equals(status, l.status)
+            && Objects.equals(appliedBy, l.appliedBy);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(label, status, appliedBy);
+    }
   }
 
   @Override
@@ -114,4 +131,20 @@
     sb.append(']');
     return sb.toString();
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o instanceof SubmitRecord) {
+      SubmitRecord r = (SubmitRecord) o;
+      return Objects.equals(status, r.status)
+          && Objects.equals(labels, r.labels)
+          && Objects.equals(errorMessage, r.errorMessage);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(status, labels, errorMessage);
+  }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
index 2423757..d05dfc2 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/SuggestService.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.common.data;
 
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwtjsonrpc.common.AsyncCallback;
@@ -48,9 +47,9 @@
 
   /**
    * Suggests reviewers. A reviewer can be a user or a group. Inactive users,
-   * the system groups {@link AccountGroup#ANONYMOUS_USERS} and
-   * {@link AccountGroup#REGISTERED_USERS} and groups that have more than the
-   * configured <code>addReviewer.maxAllowed</code> members are not suggested as
+   * the system groups {@code SystemGroupBackend#ANONYMOUS_USERS} and
+   * {@code SystemGroupBackend#REGISTERED_USERS} and groups that have more than
+   * the configured {@code addReviewer.maxAllowed} members are not suggested as
    * reviewers.
    * @param changeId the change for which reviewers should be suggested
    */
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ToggleStarRequest.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ToggleStarRequest.java
deleted file mode 100644
index c9ba72f..0000000
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ToggleStarRequest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.common.data;
-
-import com.google.gerrit.reviewdb.client.Change;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/** Request parameters to update the changes the user has toggled. */
-public class ToggleStarRequest {
-  protected Set<Change.Id> add;
-  protected Set<Change.Id> remove;
-
-  /**
-   * Request an update to the change's star status.
-   *
-   * @param id unique id of the change, must not be null.
-   * @param on true if the change should now be starred; false if it should now
-   *        be not starred.
-   */
-  public void toggle(final Change.Id id, final boolean on) {
-    if (on) {
-      if (add == null) {
-        add = new HashSet<Change.Id>();
-      }
-      add.add(id);
-      if (remove != null) {
-        remove.remove(id);
-      }
-    } else {
-      if (remove == null) {
-        remove = new HashSet<Change.Id>();
-      }
-      remove.add(id);
-      if (add != null) {
-        add.remove(id);
-      }
-    }
-  }
-
-  /** Get the set of changes which should have stars added; may be null. */
-  public Set<Change.Id> getAddSet() {
-    return add;
-  }
-
-  /** Get the set of changes which should have stars removed; may be null. */
-  public Set<Change.Id> getRemoveSet() {
-    return remove;
-  }
-}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/errors/UpdateParentFailedException.java b/gerrit-common/src/main/java/com/google/gerrit/common/errors/UpdateParentFailedException.java
new file mode 100644
index 0000000..320d055
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/errors/UpdateParentFailedException.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.errors;
+
+/** Error indicating that updating a parent project failed. */
+public class UpdateParentFailedException extends Exception {
+  private static final long serialVersionUID = 1L;
+
+  public static final String MESSAGE = "Update Parent Project Failed: ";
+
+  public UpdateParentFailedException(final String message,
+      final Throwable why) {
+    super(MESSAGE + ": " + message, why);
+  }
+}
diff --git a/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java b/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java
index e00fa48..7c662ae 100644
--- a/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java
+++ b/gerrit-common/src/test/java/com/google/gerrit/common/data/EncodePathSeparatorTest.java
@@ -14,10 +14,12 @@
 
 package com.google.gerrit.common.data;
 
-import junit.framework.TestCase;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
 
-public class EncodePathSeparatorTest extends TestCase {
+public class EncodePathSeparatorTest {
 
+  @Test
   public void testDefaultBehaviour() {
 
     GitWebType gitWebType = GitWebType.fromName(null);
@@ -25,6 +27,7 @@
     assertEquals("a/b", gitWebType.replacePathSeparator("a/b"));
   }
 
+  @Test
   public void testExclamationMark() {
 
     GitWebType gitWebType = GitWebType.fromName(null);
diff --git a/gerrit-common/src/test/java/com/google/gerrit/common/data/ParameterizedStringTest.java b/gerrit-common/src/test/java/com/google/gerrit/common/data/ParameterizedStringTest.java
index a2d1279..615f49f 100644
--- a/gerrit-common/src/test/java/com/google/gerrit/common/data/ParameterizedStringTest.java
+++ b/gerrit-common/src/test/java/com/google/gerrit/common/data/ParameterizedStringTest.java
@@ -14,37 +14,44 @@
 
 package com.google.gerrit.common.data;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
 import java.util.HashMap;
 import java.util.Map;
 
-public class ParameterizedStringTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ParameterizedStringTest {
+  @Test
   public void testEmptyString() {
     final ParameterizedString p = new ParameterizedString("");
     assertEquals("", p.getPattern());
     assertEquals("", p.getRawPattern());
     assertTrue(p.getParameterNames().isEmpty());
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
     assertNotNull(p.bind(a));
     assertEquals(0, p.bind(a).length);
     assertEquals("", p.replace(a));
   }
 
+  @Test
   public void testAsis1() {
     final ParameterizedString p = ParameterizedString.asis("${bar}c");
     assertEquals("${bar}c", p.getPattern());
     assertEquals("${bar}c", p.getRawPattern());
     assertTrue(p.getParameterNames().isEmpty());
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
     a.put("bar", "frobinator");
     assertNotNull(p.bind(a));
     assertEquals(0, p.bind(a).length);
     assertEquals("${bar}c", p.replace(a));
   }
 
+  @Test
   public void testReplace1() {
     final ParameterizedString p = new ParameterizedString("${bar}c");
     assertEquals("${bar}c", p.getPattern());
@@ -52,7 +59,7 @@
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("bar"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
     a.put("bar", "frobinator");
     assertNotNull(p.bind(a));
     assertEquals(1, p.bind(a).length);
@@ -60,6 +67,7 @@
     assertEquals("frobinatorc", p.replace(a));
   }
 
+  @Test
   public void testReplace2() {
     final ParameterizedString p = new ParameterizedString("a${bar}c");
     assertEquals("a${bar}c", p.getPattern());
@@ -67,7 +75,7 @@
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("bar"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
     a.put("bar", "frobinator");
     assertNotNull(p.bind(a));
     assertEquals(1, p.bind(a).length);
@@ -75,6 +83,7 @@
     assertEquals("afrobinatorc", p.replace(a));
   }
 
+  @Test
   public void testReplace3() {
     final ParameterizedString p = new ParameterizedString("a${bar}");
     assertEquals("a${bar}", p.getPattern());
@@ -82,7 +91,7 @@
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("bar"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
     a.put("bar", "frobinator");
     assertNotNull(p.bind(a));
     assertEquals(1, p.bind(a).length);
@@ -90,6 +99,7 @@
     assertEquals("afrobinator", p.replace(a));
   }
 
+  @Test
   public void testReplace4() {
     final ParameterizedString p = new ParameterizedString("a${bar}c");
     assertEquals("a${bar}c", p.getPattern());
@@ -97,19 +107,20 @@
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("bar"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
     assertNotNull(p.bind(a));
     assertEquals(1, p.bind(a).length);
     assertEquals("", p.bind(a)[0]);
     assertEquals("ac", p.replace(a));
   }
 
+  @Test
   public void testReplaceToLowerCase() {
     final ParameterizedString p = new ParameterizedString("${a.toLowerCase}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "foo");
     assertNotNull(p.bind(a));
@@ -124,12 +135,13 @@
     assertEquals("foo", p.replace(a));
   }
 
+  @Test
   public void testReplaceToUpperCase() {
     final ParameterizedString p = new ParameterizedString("${a.toUpperCase}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "foo");
     assertNotNull(p.bind(a));
@@ -144,12 +156,13 @@
     assertEquals("FOO", p.replace(a));
   }
 
+  @Test
   public void testReplaceLocalName() {
     final ParameterizedString p = new ParameterizedString("${a.localPart}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "foo@example.com");
     assertNotNull(p.bind(a));
@@ -164,6 +177,7 @@
     assertEquals("foo", p.replace(a));
   }
 
+  @Test
   public void testUndefinedFunctionName() {
     ParameterizedString p =
         new ParameterizedString(
@@ -172,7 +186,7 @@
     assertTrue(p.getParameterNames().contains("userName"));
     assertTrue(p.getParameterNames().contains("email"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
     a.put("userName", "firstName lastName");
     a.put("email", "FIRSTNAME.LASTNAME@EXAMPLE.COM");
     assertNotNull(p.bind(a));
@@ -183,13 +197,14 @@
     assertEquals("hi, FIRSTNAME LASTNAME,your eamil address is 'firstname.lastname'.right?", p.replace(a));
   }
 
+  @Test
   public void testReplaceToUpperCaseToLowerCase() {
     final ParameterizedString p =
         new ParameterizedString("${a.toUpperCase.toLowerCase}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "FOO@EXAMPLE.COM");
     assertNotNull(p.bind(a));
@@ -204,13 +219,14 @@
     assertEquals("foo@example.com", p.replace(a));
   }
 
+  @Test
   public void testReplaceToUpperCaseLocalName() {
     final ParameterizedString p =
         new ParameterizedString("${a.toUpperCase.localPart}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "foo@example.com");
     assertNotNull(p.bind(a));
@@ -225,13 +241,14 @@
     assertEquals("FOO", p.replace(a));
   }
 
+  @Test
   public void testReplaceToUpperCaseAnUndefinedMethod() {
     final ParameterizedString p =
         new ParameterizedString("${a.toUpperCase.anUndefinedMethod}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "foo@example.com");
     assertNotNull(p.bind(a));
@@ -246,13 +263,14 @@
     assertEquals("FOO@EXAMPLE.COM", p.replace(a));
   }
 
+  @Test
   public void testReplaceLocalNameToUpperCase() {
     final ParameterizedString p =
         new ParameterizedString("${a.localPart.toUpperCase}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "foo@example.com");
     assertNotNull(p.bind(a));
@@ -267,13 +285,14 @@
     assertEquals("FOO", p.replace(a));
   }
 
+  @Test
   public void testReplaceLocalNameToLowerCase() {
     final ParameterizedString p =
         new ParameterizedString("${a.localPart.toLowerCase}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "FOO@EXAMPLE.COM");
     assertNotNull(p.bind(a));
@@ -288,13 +307,14 @@
     assertEquals("foo", p.replace(a));
   }
 
+  @Test
   public void testReplaceLocalNameAnUndefinedMethod() {
     final ParameterizedString p =
         new ParameterizedString("${a.localPart.anUndefinedMethod}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "FOO@EXAMPLE.COM");
     assertNotNull(p.bind(a));
@@ -309,13 +329,14 @@
     assertEquals("foo", p.replace(a));
   }
 
+  @Test
   public void testReplaceToLowerCaseToUpperCase() {
     final ParameterizedString p =
         new ParameterizedString("${a.toLowerCase.toUpperCase}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "FOO@EXAMPLE.COM");
     assertNotNull(p.bind(a));
@@ -330,13 +351,14 @@
     assertEquals("FOO@EXAMPLE.COM", p.replace(a));
   }
 
+  @Test
   public void testReplaceToLowerCaseLocalName() {
     final ParameterizedString p =
         new ParameterizedString("${a.toLowerCase.localPart}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "FOO@EXAMPLE.COM");
     assertNotNull(p.bind(a));
@@ -351,13 +373,14 @@
     assertEquals("foo", p.replace(a));
   }
 
+  @Test
   public void testReplaceToLowerCaseAnUndefinedMethod() {
     final ParameterizedString p =
         new ParameterizedString("${a.toLowerCase.anUndefinedMethod}");
     assertEquals(1, p.getParameterNames().size());
     assertTrue(p.getParameterNames().contains("a"));
 
-    final Map<String, String> a = new HashMap<String, String>();
+    final Map<String, String> a = new HashMap<>();
 
     a.put("a", "foo@example.com");
     assertNotNull(p.bind(a));
diff --git a/gerrit-extension-api/.gitignore b/gerrit-extension-api/.gitignore
deleted file mode 100644
index 4e1ec9c..0000000
--- a/gerrit-extension-api/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-extension-api.iml
diff --git a/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs b/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index f9fe345..0000000
--- a/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-extension-api/.settings/org.eclipse.core.runtime.prefs b/gerrit-extension-api/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-extension-api/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-extension-api/.settings/org.eclipse.jdt.core.prefs b/gerrit-extension-api/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 941fb31..0000000
--- a/gerrit-extension-api/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,270 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-extension-api/.settings/org.eclipse.jdt.ui.prefs b/gerrit-extension-api/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-extension-api/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK
index 5aa57dd..0302afd 100644
--- a/gerrit-extension-api/BUCK
+++ b/gerrit-extension-api/BUCK
@@ -1,12 +1,33 @@
 SRC = 'src/main/java/com/google/gerrit/extensions/'
+SRCS = glob([SRC + '**/*.java'])
 
 gwt_module(
   name = 'client',
-  srcs = glob([SRC + 'webui/GerritTopMenu.java']),
+  srcs = glob([
+    SRC + 'webui/GerritTopMenu.java',
+    SRC + 'common/ListChangesOption.java'
+  ]),
   gwtxml = SRC + 'Extensions.gwt.xml',
   visibility = ['PUBLIC'],
 )
 
+java_binary(
+  name = 'extension-api',
+  deps = [':lib'],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'lib',
+  exported_deps = [
+    ':api',
+    '//lib/guice:guice',
+    '//lib/guice:guice-servlet',
+    '//lib:servlet-api-3_1',
+  ],
+  visibility = ['PUBLIC'],
+)
+
 java_library2(
   name = 'api',
   srcs = glob([SRC + '**/*.java']),
@@ -15,7 +36,20 @@
 )
 
 java_sources(
-  name = 'api-src',
-  srcs = glob([SRC + '**/*.java']),
+  name = 'extension-api-src',
+  srcs = SRCS,
+  visibility = ['PUBLIC'],
+)
+
+java_doc(
+  name = 'extension-api-javadoc',
+  title = 'Gerrit Review Extension API Documentation',
+  pkg = 'com.google.gerrit.extensions',
+  paths = ['$SRCDIR/src/main/java'],
+  srcs = SRCS,
+  deps = [
+    '//lib/guice:javax-inject',
+    '//lib/guice:guice_library'
+  ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml
index ef6a827..df53ef1 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/Extensions.gwt.xml
@@ -15,4 +15,5 @@
 -->
 <module>
   <source path='webui' />
+  <source path='common' />
 </module>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/CapabilityScope.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/CapabilityScope.java
index ede8b8c..ee5a6d5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/CapabilityScope.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/CapabilityScope.java
@@ -24,7 +24,7 @@
    *
    * If {@code @RequiresCapability} is used within the core Gerrit Code Review
    * server (and thus is outside of a plugin) the scope is the core server and
-   * will use {@link com.google.gerrit.common.data.GlobalCapability}.
+   * will use {@code com.google.gerrit.common.data.GlobalCapability}.
    */
   CONTEXT,
 
@@ -32,5 +32,5 @@
   PLUGIN,
 
   /** Scope is the core server. */
-  CORE;
+  CORE
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Export.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Export.java
index 4811e407..8cf743a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Export.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Export.java
@@ -28,19 +28,19 @@
  * Plugins or extensions using auto-registration should apply this annotation to
  * any non-abstract class they want exported for access.
  * <p>
- * For SSH commands the @Export annotation names the subcommand:
+ * For SSH commands the {@literal @Export} annotation names the subcommand:
  *
  * <pre>
- *   @Export("print")
+ *   {@literal @Export("print")}
  *   class MyCommand extends SshCommand {
  * </pre>
  *
- * For HTTP servlets, the @Export annotation names the URL the servlet is bound
- * to, relative to the plugin or extension's namespace within the Gerrit
- * container.
+ * For HTTP servlets, the {@literal @Export} annotation names the URL the
+ * servlet is bound to, relative to the plugin or extension's namespace within
+ * the Gerrit container.
  *
  * <pre>
- *  @Export("/index.html")
+ *  {@literal @Export("/index.html")}
  *  class ShowIndexHtml extends HttpServlet {
  * </pre>
  */
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginCanonicalWebUrl.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginCanonicalWebUrl.java
new file mode 100644
index 0000000..90295c8
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginCanonicalWebUrl.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.annotations;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation applied to a String containing the plugin canonical web URL.
+ * <p>
+ * A plugin or extension may receive this string by Guice injection to discover
+ * the canonical web URL under which the plugin is available:
+ *
+ * <pre>
+ *  {@literal @Inject}
+ *  MyType(@PluginCanonicalWebUrl String myUrl) {
+ *  ...
+ *  }
+ * </pre>
+ */
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface PluginCanonicalWebUrl {
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginData.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginData.java
index bf2b09a..75238a8 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginData.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginData.java
@@ -29,7 +29,7 @@
  * a directory where it can store configuration or other data that is private:
  *
  * <pre>
- * @Inject
+ * {@literal @Inject}
  * MyType(@PluginData java.io.File myDir) {
  *   new FileInputStream(new File(myDir, &quot;my.config&quot;));
  * }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginName.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginName.java
index 672bab2..efdd3c6c 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginName.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginName.java
@@ -29,7 +29,7 @@
  * the name that an administrator has installed the plugin or extension under:
  *
  * <pre>
- *  @Inject
+ *  {@literal @Inject}
  *  MyType(@PluginName String myName) {
  *  ...
  *  }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java
index ec0b361..a14779a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/RequiresCapability.java
@@ -21,8 +21,8 @@
 import java.lang.annotation.Target;
 
 /**
- * Annotation on {@link com.google.gerrit.sshd.SshCommand} or
- * {@link com.google.gerrit.httpd.restapi.RestApiServlet} declaring a
+ * Annotation on {@code com.google.gerrit.sshd.SshCommand} or
+ * {@code com.google.gerrit.httpd.restapi.RestApiServlet} declaring a
  * capability must be granted.
  */
 @Target({ElementType.TYPE})
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/GerritApi.java
similarity index 70%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/GerritApi.java
index c48f968..844807b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/GerritApi.java
@@ -12,14 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api;
 
-public class GroupInfo {
-  public String id;
-  public String name;
-  public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+import com.google.gerrit.extensions.api.changes.Changes;
+import com.google.gerrit.extensions.api.projects.Projects;
+
+public interface GerritApi {
+  public Changes changes();
+  public Projects projects();
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/AbandonInput.java
similarity index 77%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/AbandonInput.java
index 11779f4..04a7bc7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/AbandonInput.java
@@ -12,10 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.changes;
 
-import java.util.List;
+import com.google.gerrit.extensions.restapi.DefaultInput;
 
-public class MembersInput {
-  List<String> members;
+public class AbandonInput {
+  @DefaultInput
+  public String message;
 }
+
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/AddReviewerInput.java
similarity index 67%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/AddReviewerInput.java
index 8e1b340..30a23bf 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/AddReviewerInput.java
@@ -12,14 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.change;
+package com.google.gerrit.extensions.api.changes;
 
-public class SubmitInput {
-  boolean wait_for_merge;
+import com.google.gerrit.extensions.restapi.DefaultInput;
 
-  public static SubmitInput waitForMerge() {
-    SubmitInput in = new SubmitInput();
-    in.wait_for_merge = true;
-    return in;
+public class AddReviewerInput {
+  @DefaultInput
+  public String reviewer;
+  public Boolean confirmed;
+
+  public boolean confirmed() {
+    return (confirmed != null) ? confirmed : false;
   }
 }
+
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
new file mode 100644
index 0000000..f0a9e4d
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+import java.util.EnumSet;
+
+public interface ChangeApi {
+  String id();
+
+  RevisionApi current() throws RestApiException;
+  RevisionApi revision(int id) throws RestApiException;
+  RevisionApi revision(String id) throws RestApiException;
+
+  void abandon() throws RestApiException;
+  void abandon(AbandonInput in) throws RestApiException;
+
+  void restore() throws RestApiException;
+  void restore(RestoreInput in) throws RestApiException;
+
+  ChangeApi revert() throws RestApiException;
+  ChangeApi revert(RevertInput in) throws RestApiException;
+
+  void addReviewer(AddReviewerInput in) throws RestApiException;
+  void addReviewer(String in) throws RestApiException;
+
+  ChangeInfo get(EnumSet<ListChangesOption> options) throws RestApiException;
+
+  /** {@code get} with {@link ListChangesOption} set to ALL. */
+  ChangeInfo get() throws RestApiException;
+  /** {@code get} with {@link ListChangesOption} set to NONE. */
+  ChangeInfo info() throws RestApiException;
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
similarity index 64%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
index c48f968..48e9fd3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
@@ -12,14 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.changes;
 
-public class GroupInfo {
-  public String id;
-  public String name;
-  public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface Changes {
+  ChangeApi id(int id) throws RestApiException;
+  ChangeApi id(String triplet) throws RestApiException;
+  ChangeApi id(String project, String branch, String id)
+      throws RestApiException;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
similarity index 81%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
index 4457fb6..7ae7ef1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/CherryPickInput.java
@@ -12,8 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.changes;
 
-public class GroupOptionsInfo {
-  public Boolean visible_to_all;
+public class CherryPickInput {
+  public String message;
+  public String destination;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RestoreInput.java
similarity index 77%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RestoreInput.java
index 11779f4..a116dde 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RestoreInput.java
@@ -12,10 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.changes;
 
-import java.util.List;
+import com.google.gerrit.extensions.restapi.DefaultInput;
 
-public class MembersInput {
-  List<String> members;
+public class RestoreInput {
+  @DefaultInput
+  public String message;
 }
+
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevertInput.java
similarity index 77%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevertInput.java
index 11779f4..2c1c688 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevertInput.java
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.changes;
 
-import java.util.List;
+import com.google.gerrit.extensions.restapi.DefaultInput;
 
-public class MembersInput {
-  List<String> members;
+public class RevertInput {
+  @DefaultInput
+  public String message;
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
new file mode 100644
index 0000000..993fa14
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
@@ -0,0 +1,132 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Input passed to {@code POST /changes/{id}/revisions/{id}/review}. */
+public class ReviewInput {
+  @DefaultInput
+  public String message;
+
+  public Map<String, Short> labels;
+  public Map<String, List<Comment>> comments;
+
+  /**
+   * If true require all labels to be within the user's permitted ranges based
+   * on access controls, attempting to use a label not granted to the user
+   * will fail the entire modify operation early. If false the operation will
+   * execute anyway, but the proposed labels given by the user will be
+   * modified to be the "best" value allowed by the access controls, or
+   * ignored if the label does not exist.
+   */
+  public boolean strictLabels = true;
+
+  /**
+   * How to process draft comments already in the database that were not also
+   * described in this input request.
+   */
+  public DraftHandling drafts = DraftHandling.DELETE;
+
+  /** Who to send email notifications to after review is stored. */
+  public NotifyHandling notify = NotifyHandling.ALL;
+
+  /**
+   * Account ID, name, email address or username of another user. The review
+   * will be posted/updated on behalf of this named user instead of the
+   * caller. Caller must have the labelAs-$NAME permission granted for each
+   * label that appears in {@link #labels}. This is in addition to the named
+   * user also needing to have permission to use the labels.
+   * <p>
+   * {@link #strictLabels} impacts how labels is processed for the named user,
+   * not the caller.
+   */
+  public String onBehalfOf;
+
+  public static enum DraftHandling {
+    DELETE, PUBLISH, KEEP
+  }
+
+  public static enum NotifyHandling {
+    NONE, OWNER, OWNER_REVIEWERS, ALL
+  }
+
+  public static enum Side {
+    PARENT, REVISION
+  }
+
+  public static class Comment {
+    public String id;
+    public Side side;
+    public int line;
+    public String inReplyTo;
+    public String message;
+    public Range range;
+
+    public static class Range {
+      public int startLine;
+      public int startCharacter;
+      public int endLine;
+      public int endCharacter;
+    }
+  }
+
+  public ReviewInput message(String msg) {
+    message = msg != null && !msg.isEmpty() ? msg : null;
+    return this;
+  }
+
+  public ReviewInput label(String name, short value) {
+    if (name == null || name.isEmpty()) {
+      throw new IllegalArgumentException();
+    }
+    if (labels == null) {
+      labels = new LinkedHashMap<String, Short>(4);
+    }
+    labels.put(name, value);
+    return this;
+  }
+
+  public ReviewInput label(String name, int value) {
+    if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
+      throw new IllegalArgumentException();
+    }
+    return label(name, (short) value);
+  }
+
+  public ReviewInput label(String name) {
+    return label(name, (short) 1);
+  }
+
+  public static ReviewInput recommend() {
+    return new ReviewInput().label("Code-Review", 1);
+  }
+
+  public static ReviewInput dislike() {
+    return new ReviewInput().label("Code-Review", -1);
+  }
+
+  public static ReviewInput approve() {
+    return new ReviewInput().label("Code-Review", 2);
+  }
+
+  public static ReviewInput reject() {
+    return new ReviewInput().label("Code-Review", -2);
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
new file mode 100644
index 0000000..4f90be2
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface RevisionApi {
+  void delete() throws RestApiException;
+  void review(ReviewInput in) throws RestApiException;
+
+  /** {@code submit} with {@link SubmitInput#waitForMerge} set to true. */
+  void submit() throws RestApiException;
+  void submit(SubmitInput in) throws RestApiException;
+  void publish() throws RestApiException;
+  ChangeApi cherryPick(CherryPickInput in) throws RestApiException;
+  ChangeApi rebase() throws RestApiException;
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmitInput.java
similarity index 81%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmitInput.java
index 4457fb6..742748e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmitInput.java
@@ -12,8 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.changes;
 
-public class GroupOptionsInfo {
-  public Boolean visible_to_all;
+public class SubmitInput {
+  public boolean waitForMerge;
+  public String onBehalfOf;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchApi.java
similarity index 74%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchApi.java
index 4457fb6..2f1533f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchApi.java
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.projects;
 
-public class GroupOptionsInfo {
-  public Boolean visible_to_all;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface BranchApi {
+  BranchApi create(BranchInput in) throws RestApiException;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchInput.java
similarity index 77%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchInput.java
index 11779f4..cfe9a99 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BranchInput.java
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.projects;
 
-import java.util.List;
+import com.google.gerrit.extensions.restapi.DefaultInput;
 
-public class MembersInput {
-  List<String> members;
+public class BranchInput {
+  @DefaultInput
+  public String revision;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
similarity index 83%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 4457fb6..1c7209d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.projects;
 
-public class GroupOptionsInfo {
-  public Boolean visible_to_all;
+public interface ProjectApi {
+  BranchApi branch(String ref);
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
similarity index 75%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
index 11779f4..a3a4137 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.api.projects;
 
-import java.util.List;
+import com.google.gerrit.extensions.restapi.RestApiException;
 
-public class MembersInput {
-  List<String> members;
+public interface Projects {
+  ProjectApi name(String name) throws RestApiException;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AccountInfo.java
similarity index 80%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountInfo.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AccountInfo.java
index cf88bc6..5130f9f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/AccountInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.account;
+package com.google.gerrit.extensions.common;
 
 public class AccountInfo {
-  public Integer _account_id;
+  public Integer _accountId;
   public String name;
   public String email;
+  public String username;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/actions/ActionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ActionInfo.java
similarity index 85%
rename from gerrit-server/src/main/java/com/google/gerrit/server/actions/ActionInfo.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ActionInfo.java
index 91574a2..0953ee9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/actions/ActionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ActionInfo.java
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.actions;
+package com.google.gerrit.extensions.common;
 
 import com.google.gerrit.extensions.webui.UiAction;
 
 public class ActionInfo {
-  String method;
-  String label;
-  String title;
-  Boolean enabled;
+  public String method;
+  public String label;
+  public String title;
+  public Boolean enabled;
 
   public ActionInfo(UiAction.Description d) {
     method = d.getMethod();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/RelatedInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ApprovalInfo.java
similarity index 71%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/RelatedInfo.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ApprovalInfo.java
index 4089910..2176e8f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/RelatedInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ApprovalInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.server.change;
+package com.google.gerrit.extensions.common;
 
-import java.util.List;
+import java.sql.Timestamp;
 
-public class RelatedInfo {
-  public List<ChangeAndCommit> changes;
+public class ApprovalInfo extends AccountInfo {
+  public Integer value;
+  public Timestamp date;
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
new file mode 100644
index 0000000..f85684e
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.Map;
+
+public class ChangeInfo {
+  public String id;
+  public String project;
+  public String branch;
+  public String topic;
+  public String changeId;
+  public String subject;
+  public ChangeStatus status;
+  public Timestamp created;
+  public Timestamp updated;
+  public Boolean starred;
+  public Boolean reviewed;
+  public Boolean mergeable;
+  public Integer insertions;
+  public Integer deletions;
+  public AccountInfo owner;
+  public String currentRevision;
+  public Map<String, ActionInfo> actions;
+  public Map<String, LabelInfo> labels;
+  public Collection<ChangeMessageInfo> messages;
+  public Map<String, RevisionInfo> revisions;
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessageInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java
similarity index 69%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessageInfo.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java
index b3c584a..7ad40b5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessageInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.change;
+package com.google.gerrit.extensions.common;
+
+import java.sql.Timestamp;
 
 public class ChangeMessageInfo {
-  String message;
+  public String id;
+  public AccountInfo author;
+  public Timestamp date;
+  public String message;
+  public Integer _revisionNumber;
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeStatus.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeStatus.java
new file mode 100644
index 0000000..9af66f2
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeStatus.java
@@ -0,0 +1,101 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+/* Current state within the basic workflow of the change **/
+public enum ChangeStatus {
+
+  /**
+   * Change is open and pending review, or review is in progress.
+   *
+   * <p>
+   * This is the default state assigned to a change when it is first created
+   * in the database. A change stays in the NEW state throughout its review
+   * cycle, until the change is submitted or abandoned.
+   *
+   * <p>
+   * Changes in the NEW state can be moved to:
+   * <ul>
+   * <li>{@link #SUBMITTED} - when the Submit Patch Set action is used;
+   * <li>{@link #ABANDONED} - when the Abandon action is used.
+   * </ul>
+   */
+  NEW,
+
+  /**
+   * Change is open, but has been submitted to the merge queue.
+   *
+   * <p>
+   * A change enters the SUBMITTED state when an authorized user presses the
+   * "submit" action through the web UI, requesting that Gerrit merge the
+   * change's current patch set into the destination branch.
+   *
+   * <p>
+   * Typically a change resides in the SUBMITTED for only a brief sub-second
+   * period while the merge queue fires and the destination branch is updated.
+   * However, if a dependency commit (directly or transitively) is not yet
+   * merged into the branch, the change will hang in the SUBMITTED state
+   * indefinitely.
+   *
+   * <p>
+   * Changes in the SUBMITTED state can be moved to:
+   * <ul>
+   * <li>{@link #NEW} - when a replacement patch set is supplied, OR when a
+   * merge conflict is detected;
+   * <li>{@link #MERGED} - when the change has been successfully merged into
+   * the destination branch;
+   * <li>{@link #ABANDONED} - when the Abandon action is used.
+   * </ul>
+   */
+  SUBMITTED,
+
+  /**
+   * Change is a draft change that only consists of draft patchsets.
+   *
+   * <p>
+   * This is a change that is not meant to be submitted or reviewed yet. If
+   * the uploader publishes the change, it becomes a NEW change.
+   * Publishing is a one-way action, a change cannot return to DRAFT status.
+   * Draft changes are only visible to the uploader and those explicitly
+   * added as reviewers.
+   *
+   * <p>
+   * Changes in the DRAFT state can be moved to:
+   * <ul>
+   * <li>{@link #NEW} - when the change is published, it becomes a new change;
+   * </ul>
+   */
+  DRAFT,
+
+  /**
+   * Change is closed, and submitted to its destination branch.
+   *
+   * <p>
+   * Once a change has been merged, it cannot be further modified by adding a
+   * replacement patch set. Draft comments however may be published,
+   * supporting a post-submit review.
+   */
+  MERGED,
+
+  /**
+   * Change is closed, but was not submitted to its destination branch.
+   *
+   * <p>
+   * Once a change has been abandoned, it cannot be further modified by adding
+   * a replacement patch set, and it cannot be merged. Draft comments however
+   * may be published, permitting reviewers to send constructive feedback.
+   */
+  ABANDONED
+}
\ No newline at end of file
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommitInfo.java
similarity index 66%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommitInfo.java
index 11779f4..7313ab3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/CommitInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,10 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.common;
 
 import java.util.List;
 
-public class MembersInput {
-  List<String> members;
+public class CommitInfo {
+  public String commit;
+  public List<CommitInfo> parents;
+  public GitPerson author;
+  public GitPerson committer;
+  public String subject;
+  public String message;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FetchInfo.java
similarity index 65%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FetchInfo.java
index c48f968..eda84b1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FetchInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,14 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.common;
 
-public class GroupInfo {
-  public String id;
-  public String name;
+import java.util.Map;
+
+public class FetchInfo {
   public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+  public String ref;
+  public Map<String, String> commands;
+
+  public FetchInfo(String url, String ref) {
+    this.url = url;
+    this.ref = ref;
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FileInfo.java
similarity index 67%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FileInfo.java
index 4457fb6..58f5494 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/FileInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.common;
 
-public class GroupOptionsInfo {
-  public Boolean visible_to_all;
+public class FileInfo {
+  public Character status;
+  public Boolean binary;
+  public String oldPath;
+  public Integer linesInserted;
+  public Integer linesDeleted;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GitPerson.java
similarity index 75%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GitPerson.java
index cf88bc6..9853417 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/AccountInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GitPerson.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,10 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.account;
+package com.google.gerrit.extensions.common;
 
-public class AccountInfo {
-  public Integer _account_id;
+import java.sql.Timestamp;
+
+public class GitPerson {
   public String name;
   public String email;
+  public Timestamp date;
+  public int tz;
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/LabelInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/LabelInfo.java
new file mode 100644
index 0000000..fd6008f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/LabelInfo.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+import java.util.List;
+import java.util.Map;
+
+public class LabelInfo {
+  public AccountInfo approved;
+  public AccountInfo rejected;
+  public AccountInfo recommended;
+  public AccountInfo disliked;
+  public List<ApprovalInfo> all;
+  public Map<String, String> values;
+  public Short value;
+  public Boolean optional;
+  public Boolean blocking;
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java
similarity index 94%
rename from gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java
index 276c332..c23e312 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ListChangesOption.java
@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.common.changes;
+package com.google.gerrit.extensions.common;
 
 import java.util.EnumSet;
 
-/** Output options available when using {@code /changes/} RPCs. */
+/** Output options available for retrieval change details. */
 public enum ListChangesOption {
   LABELS(0),
   DETAILED_LABELS(8),
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
new file mode 100644
index 0000000..ea5b068
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/RevisionInfo.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+import java.util.Map;
+
+public class RevisionInfo {
+  public transient boolean isCurrent;
+  public Boolean draft;
+  public Boolean hasDraftComments;
+  public int _number;
+  public Map<String, FetchInfo> fetch;
+  public CommitInfo commit;
+  public Map<String, FileInfo> files;
+  public Map<String, ActionInfo> actions;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/DownloadScheme.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/DownloadScheme.java
index 20eda97..d81657a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/DownloadScheme.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/DownloadScheme.java
@@ -30,9 +30,7 @@
   public abstract boolean isAuthRequired();
 
   /** @return whether this scheme supports authentication */
-  public boolean isAuthSupported() {
-    return isAuthRequired();
-  }
+  public abstract boolean isAuthSupported();
 
   /** @return whether the download scheme is enabled */
   public abstract boolean isEnabled();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/HeadUpdatedListener.java
similarity index 61%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/HeadUpdatedListener.java
index c48f968..5961d6f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/HeadUpdatedListener.java
@@ -12,14 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.events;
 
-public class GroupInfo {
-  public String id;
-  public String name;
-  public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/** Notified whenever the HEAD of a project is updated. */
+@ExtensionPoint
+public interface HeadUpdatedListener {
+  public interface Event {
+    String getProjectName();
+    String getOldHeadName();
+    String getNewHeadName();
+  }
+
+  void onHeadUpdated(Event event);
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/persistence/DataSourceInterceptor.java
similarity index 69%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
copy to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/persistence/DataSourceInterceptor.java
index 4457fb6..0a83eea 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/persistence/DataSourceInterceptor.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.extensions.persistence;
 
-public class GroupOptionsInfo {
-  public Boolean visible_to_all;
+import javax.sql.DataSource;
+
+public interface DataSourceInterceptor {
+  DataSource intercept(String name, DataSource dataSource);
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java
index 72f1bc5..3aa1781 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/CacheControl.java
@@ -19,7 +19,7 @@
 public class CacheControl {
 
   public enum Type {
-    NONE, PUBLIC, PRIVATE;
+    NONE, PUBLIC, PRIVATE
   }
 
   public static final CacheControl NONE = new CacheControl(Type.NONE, 0, null);
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestApiException.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestApiException.java
index 3fae128..b6ba730 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestApiException.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/RestApiException.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.extensions.restapi;
 
 /** Root exception type for JSON API failures. */
-public abstract class RestApiException extends Exception {
+public class RestApiException extends Exception {
   private static final long serialVersionUID = 1L;
   private CacheControl caching = CacheControl.NONE;
 
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/systemstatus/MessageOfTheDay.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/systemstatus/MessageOfTheDay.java
new file mode 100644
index 0000000..033e7b4
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/systemstatus/MessageOfTheDay.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.systemstatus;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Supplies a message of the day when the page is first loaded.
+ *
+ * <pre>
+ * DynamicSet.bind(binder(), MessageOfTheDay.class).to(MyMessage.class);
+ * </pre>
+ */
+@ExtensionPoint
+public abstract class MessageOfTheDay {
+  /**
+   * Retrieve the message of the day as an HTML fragment.
+   *
+   * @return message as an HTML fragment; null if no message is available.
+   */
+  public abstract String getHtmlMessage();
+
+  /**
+   * Unique identifier for this message.
+   * <p>
+   * Messages with the same identifier will be hidden from the user until
+   * redisplay has occurred.
+   * </p>
+   *
+   * @return unique message identifier. This identifier should be unique within
+   *         the server.
+   */
+  public abstract String getMessageId();
+
+  /**
+   * When should the message be displayed?
+   *
+   * <p>
+   * Default implementation returns {@code tomorrow at 00:00:00 GMT}.
+   * </p>
+   *
+   * @return a future date after which the message should be redisplayed.
+   */
+  public Date getRedisplay() {
+    Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+    cal.set(Calendar.HOUR_OF_DAY, 0);
+    cal.set(Calendar.MINUTE, 0);
+    cal.set(Calendar.SECOND, 0);
+    cal.set(Calendar.MILLISECOND, 0);
+    cal.add(Calendar.DAY_OF_MONTH, 1);
+    return cal.getTime();
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/systemstatus/ServerInformation.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/systemstatus/ServerInformation.java
index 3d2df21..c7deedb 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/systemstatus/ServerInformation.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/systemstatus/ServerInformation.java
@@ -35,7 +35,7 @@
      * The server is attempting a graceful halt of operations and will exit (or
      * be killed by the operating system) soon.
      */
-    SHUTDOWN;
+    SHUTDOWN
   }
 
   State getState();
diff --git a/gerrit-gwtdebug/.gitignore b/gerrit-gwtdebug/.gitignore
deleted file mode 100644
index 4207862..0000000
--- a/gerrit-gwtdebug/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-gwtdebug.iml
\ No newline at end of file
diff --git a/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs b/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index e9441bb..0000000
--- a/gerrit-gwtdebug/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-gwtdebug/.settings/org.eclipse.core.runtime.prefs b/gerrit-gwtdebug/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-gwtdebug/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-gwtdebug/.settings/org.eclipse.jdt.core.prefs b/gerrit-gwtdebug/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index d2b5901..0000000
--- a/gerrit-gwtdebug/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:38 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-gwtdebug/.settings/org.eclipse.jdt.ui.prefs b/gerrit-gwtdebug/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-gwtdebug/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-gwtdebug/BUCK b/gerrit-gwtdebug/BUCK
index 308c2f1..a926773 100644
--- a/gerrit-gwtdebug/BUCK
+++ b/gerrit-gwtdebug/BUCK
@@ -1,6 +1,11 @@
 java_library(
   name = 'gwtdebug',
   srcs = ['src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java'],
-  deps = ['//lib/gwt:dev'],
+  deps = [
+    '//lib/gwt:dev',
+    '//lib/jetty:server',
+    '//lib/jetty:servlet',
+    '//lib/jetty:webapp',
+  ],
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java b/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
index a2a770e..09a7fb6 100644
--- a/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
+++ b/gerrit-gwtdebug/src/main/java/com/google/gerrit/gwtdebug/GerritDebugLauncher.java
@@ -19,29 +19,31 @@
 import com.google.gwt.core.ext.ServletContainerLauncher;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.shell.jetty.JettyNullLogger;
 
-import org.mortbay.component.AbstractLifeCycle;
-import org.mortbay.jetty.AbstractConnector;
-import org.mortbay.jetty.HttpFields.Field;
-import org.mortbay.jetty.Request;
-import org.mortbay.jetty.RequestLog;
-import org.mortbay.jetty.Response;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.handler.RequestLogHandler;
-import org.mortbay.jetty.nio.SelectChannelConnector;
-import org.mortbay.jetty.webapp.WebAppClassLoader;
-import org.mortbay.jetty.webapp.WebAppContext;
-import org.mortbay.log.Log;
-import org.mortbay.log.Logger;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.webapp.WebAppClassLoader;
+import org.eclipse.jetty.webapp.WebAppContext;
 
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.util.Iterator;
 
 public class GerritDebugLauncher extends ServletContainerLauncher {
+
+  private final static boolean __escape = true;
+
   /**
    * Log jetty requests/responses to TreeLogger.
    */
@@ -57,7 +59,6 @@
     /**
      * Log an HTTP request/response to TreeLogger.
      */
-    @SuppressWarnings("unchecked")
     public void log(Request request, Response response) {
       int status = response.getStatus();
       if (status < 0) {
@@ -93,20 +94,13 @@
         if (branch.isLoggable(logHeaders)) {
           // Request headers
           TreeLogger headers = branch.branch(logHeaders, "Request headers");
-          Iterator<Field> headerFields =
-              request.getConnection().getRequestFields().getFields();
-          while (headerFields.hasNext()) {
-            Field headerField = headerFields.next();
-            headers.log(logHeaders, headerField.getName() + ": "
-                + headerField.getValue());
+          for (HttpField f : request.getHttpFields()) {
+            headers.log(logHeaders, f.getName() + ": " + f.getValue());
           }
           // Response headers
           headers = branch.branch(logHeaders, "Response headers");
-          headerFields = response.getHttpFields().getFields();
-          while (headerFields.hasNext()) {
-            Field headerField = headerFields.next();
-            headers.log(logHeaders, headerField.getName() + ": "
-                + headerField.getValue());
+          for (HttpField f : response.getHttpFields()) {
+            headers.log(logHeaders, f.getName() + ": " + f.getValue());
           }
         }
       }
@@ -163,6 +157,54 @@
       logger.log(TreeLogger.WARN, msg, th);
     }
 
+    public void debug(String msg, long value) {
+      // ignored
+    }
+
+    @Override
+    public void debug(String msg, Object... args) {
+      // ignored
+    }
+
+    @Override
+    public void debug(Throwable thrown) {
+      // ignored
+    }
+
+    @Override
+    public void warn(String msg, Object... args) {
+      logger.log(TreeLogger.WARN, format(msg, args));
+    }
+
+    @Override
+    public void warn(Throwable thrown) {
+      logger.log(TreeLogger.WARN, thrown.getMessage(), thrown);
+    }
+
+    @Override
+    public void info(String msg, Object... args) {
+      logger.log(TreeLogger.INFO, format(msg, args));
+    }
+
+    @Override
+    public void info(Throwable thrown) {
+      logger.log(TreeLogger.INFO, thrown.getMessage(), thrown);
+    }
+
+    @Override
+    public void info(String msg, Throwable thrown) {
+      logger.log(TreeLogger.INFO, msg, thrown);
+    }
+
+    @Override
+    public void ignore(Throwable ignored) {
+    }
+
+    @Override
+    public String getName() {
+      return this.getClass().getName();
+    }
+
     /**
      * Copied from org.mortbay.log.StdErrLog.
      */
@@ -178,6 +220,54 @@
       }
       return msg;
     }
+
+    private String format(String msg, Object... args) {
+      StringBuilder builder = new StringBuilder();
+      if (msg == null) {
+          msg = "";
+          for (int i = 0; i < args.length; i++) {
+              msg += "{} ";
+          }
+      }
+      String braces = "{}";
+      int start = 0;
+      for (Object arg : args) {
+          int bracesIndex = msg.indexOf(braces,start);
+          if (bracesIndex < 0) {
+              escape(builder, msg.substring(start));
+              builder.append(" ");
+              builder.append(arg);
+              start = msg.length();
+          } else {
+              escape(builder, msg.substring(start, bracesIndex));
+              builder.append(String.valueOf(arg));
+              start = bracesIndex + braces.length();
+          }
+      }
+      escape(builder, msg.substring(start));
+      return builder.toString();
+    }
+
+    private void escape(StringBuilder builder, String string) {
+      if (__escape) {
+        for (int i = 0; i < string.length(); ++i) {
+          char c = string.charAt(i);
+          if (Character.isISOControl(c)) {
+            if (c == '\n') {
+              builder.append('|');
+            } else if (c == '\r') {
+              builder.append('<');
+            } else {
+              builder.append('?');
+            }
+          } else {
+            builder.append(c);
+          }
+        }
+      } else {
+        builder.append(string);
+      }
+    }
   }
 
   /**
@@ -259,7 +349,7 @@
   protected final class MyWebAppContext extends WebAppContext {
     /**
      * Parent ClassLoader for the Jetty web app, which can only load JVM
-     * classes. We would just use <code>null</code> for the parent ClassLoader
+     * classes. We would just use {@code null} for the parent ClassLoader
      * except this makes Jetty unhappy.
      */
     private final ClassLoader bootStrapOnlyClassLoader =
@@ -268,7 +358,6 @@
     private final ClassLoader systemClassLoader =
         Thread.currentThread().getContextClassLoader();
 
-    @SuppressWarnings("unchecked")
     private MyWebAppContext(String webApp, String contextPath) {
       super(webApp, contextPath);
 
@@ -282,7 +371,7 @@
 
     @Override
     protected void doStart() throws Exception {
-      setClassLoader(new MyLoader());
+      setClassLoader(new MyLoader(this));
       super.doStart();
     }
 
@@ -293,9 +382,10 @@
     }
 
     private class MyLoader extends WebAppClassLoader {
-      MyLoader() throws IOException {
+      MyWebAppContext ctx;
+      MyLoader(MyWebAppContext ctx) throws IOException {
         super(bootStrapOnlyClassLoader, MyWebAppContext.this);
-
+        this.ctx = ctx;
         final URLClassLoader scl = (URLClassLoader) systemClassLoader;
         final URL[] urls = scl.getURLs();
         for (URL u : urls) {
@@ -306,16 +396,9 @@
       }
 
       @Override
-      public boolean isSystemPath(String name) {
-        name = name.replace('/', '.');
-        return super.isSystemPath(name) //
-            || name.startsWith("org.bouncycastle.");
-      }
-
-      @Override
       protected Class<?> findClass(String name) throws ClassNotFoundException {
         // For system path, always prefer the outside world.
-        if (isSystemPath(name)) {
+        if (ctx.isSystemClass(name.replace('/', '.'))) {
           try {
             return systemClassLoader.loadClass(name);
           } catch (ClassNotFoundException e) {
@@ -327,9 +410,6 @@
   }
 
   static {
-    // Suppress spammy Jetty log initialization.
-    System
-        .setProperty("org.mortbay.log.class", JettyNullLogger.class.getName());
     Log.getLog();
 
     /*
@@ -357,7 +437,6 @@
       throws Exception {
     TreeLogger branch =
         logger.branch(TreeLogger.INFO, "Starting Jetty on port " + port, null);
-
     checkStartParams(branch, port, warDir);
 
     // Setup our branch logger during startup.
@@ -366,9 +445,12 @@
     // Turn off XML validation.
     System.setProperty("org.mortbay.xml.XmlParser.Validating", "false");
 
-    AbstractConnector connector = getConnector();
+    Server server = new Server();
+    HttpConfiguration config = defaultConfig();
+    ServerConnector connector = new ServerConnector(server,
+        new HttpConnectionFactory(config));
     if (bindAddress != null) {
-      connector.setHost(bindAddress.toString());
+      connector.setHost(bindAddress);
     }
     connector.setPort(port);
 
@@ -378,7 +460,7 @@
     // Linux keeps the port blocked after shutdown if we don't disable this.
     connector.setSoLingerTime(0);
 
-    Server server = new Server();
+
     server.addConnector(connector);
 
     File top;
@@ -413,12 +495,16 @@
     // Now that we're started, log to the top level logger.
     Log.setLog(new JettyTreeLogger(logger));
 
-    return new JettyServletContainer(logger, server, wac, connector
-        .getLocalPort(), warDir);
+    return new JettyServletContainer(logger, server, wac,
+        connector.getLocalPort(), warDir);
   }
 
-  protected AbstractConnector getConnector() {
-    return new SelectChannelConnector();
+  protected HttpConfiguration defaultConfig() {
+    HttpConfiguration config = new HttpConfiguration();
+    config.setRequestHeaderSize(16386);
+    config.setSendServerVersion(false);
+    config.setSendDateHeader(true);
+    return config;
   }
 
   private void checkStartParams(TreeLogger logger, int port, File appRootDir) {
diff --git a/gerrit-gwtexpui/.gitignore b/gerrit-gwtexpui/.gitignore
deleted file mode 100644
index 406c4d5..0000000
--- a/gerrit-gwtexpui/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/generated_classes
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-.settings/org.eclipse.m2e.core.prefs
diff --git a/gerrit-gwtexpui/.settings/org.eclipse.core.resources.prefs b/gerrit-gwtexpui/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index f9fe345..0000000
--- a/gerrit-gwtexpui/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-gwtexpui/.settings/org.eclipse.core.runtime.prefs b/gerrit-gwtexpui/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-gwtexpui/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-gwtexpui/.settings/org.eclipse.jdt.core.prefs b/gerrit-gwtexpui/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 21aa7e7..0000000
--- a/gerrit-gwtexpui/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,285 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
-org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
-org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
-org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.join_lines_in_comments=true
-org.eclipse.jdt.core.formatter.join_wrapped_lines=true
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_on_off_tags=false
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
-org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
-org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/gerrit-gwtexpui/.settings/org.eclipse.jdt.ui.prefs b/gerrit-gwtexpui/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index 7d663fda..0000000
--- a/gerrit-gwtexpui/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-formatter_profile=_Google Format
-formatter_settings_version=12
diff --git a/gerrit-gwtexpui/BUCK b/gerrit-gwtexpui/BUCK
index 105ee7d..76d0741 100644
--- a/gerrit-gwtexpui/BUCK
+++ b/gerrit-gwtexpui/BUCK
@@ -11,16 +11,18 @@
   deps = [
     ':SafeHtml',
     ':UserAgent',
+  ],
+  compile_deps = [
     '//lib/gwt:user',
     '//lib:LICENSE-clippy',
   ],
   visibility = ['PUBLIC'],
 )
 
-gwt_module(
+java_library(
   name = 'CSS',
   srcs = glob([SRC + 'css/rebind/*.java']),
-  gwtxml = SRC + 'css/CSS.gwt.xml',
+  resources = [SRC + 'css/CSS.gwt.xml'],
   deps = ['//lib/gwt:dev'],
   visibility = ['PUBLIC'],
 )
@@ -36,23 +38,15 @@
   deps = [
     ':SafeHtml',
     ':UserAgent',
-    '//lib/gwt:user',
   ],
-  visibility = ['PUBLIC'],
-)
-
-gwt_module(
-  name = 'Linker',
-  srcs = glob([SRC + 'linker/rebind/*.java']),
-  gwtxml = SRC + 'linker/ServerPlannedIFrameLinker.gwt.xml',
-  deps = ['//lib/gwt:dev'],
+  compile_deps = ['//lib/gwt:user'],
   visibility = ['PUBLIC'],
 )
 
 java_library2(
   name = 'linker_server',
   srcs = glob([SRC + 'linker/server/*.java']),
-  compile_deps = ['//lib:servlet-api-3_0'],
+  compile_deps = ['//lib:servlet-api-3_1'],
   visibility = ['PUBLIC'],
 )
 
@@ -61,7 +55,7 @@
   srcs = glob([SRC + 'progress/client/*.java']),
   gwtxml = SRC + 'progress/Progress.gwt.xml',
   resources = [SRC + 'progress/client/progress.css'],
-  deps = ['//lib/gwt:user'],
+  compile_deps = ['//lib/gwt:user'],
   visibility = ['PUBLIC'],
 )
 
@@ -70,7 +64,7 @@
   srcs = glob([SRC + 'safehtml/client/*.java']),
   gwtxml = SRC + 'safehtml/SafeHtml.gwt.xml',
   resources = [SRC + 'safehtml/client/safehtml.css'],
-  deps = ['//lib/gwt:user'],
+  compile_deps = ['//lib/gwt:user'],
   visibility = ['PUBLIC'],
 )
 
@@ -80,25 +74,25 @@
     'src/test/java/com/google/gwtexpui/safehtml/client/**/*.java',
   ]),
   deps = [
-    ':SafeHtml',
+    ':SafeHtml_lib',
     '//lib:junit',
     '//lib/gwt:user',
     '//lib/gwt:dev',
   ],
-  source_under_test = [':SafeHtml'],
+  source_under_test = [':SafeHtml_lib'],
 )
 
 gwt_module(
   name = 'UserAgent',
   srcs = glob([SRC + 'user/client/*.java']),
   gwtxml = SRC + 'user/User.gwt.xml',
-  deps = ['//lib/gwt:user'],
+  compile_deps = ['//lib/gwt:user'],
   visibility = ['PUBLIC'],
 )
 
 java_library2(
   name = 'server',
   srcs = glob([SRC + 'server/*.java']),
-  compile_deps = ['//lib:servlet-api-3_0'],
+  compile_deps = ['//lib:servlet-api-3_1'],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
index c4f887e..1a8c275 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
@@ -15,6 +15,7 @@
 package com.google.gwtexpui.clippy.client;
 
 import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.BlurEvent;
 import com.google.gwt.event.dom.client.BlurHandler;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -24,7 +25,6 @@
 import com.google.gwt.http.client.URL;
 import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HasText;
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommand.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommand.java
index 032db65..15caa34 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommand.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommand.java
@@ -27,11 +27,14 @@
   public static final int M_SHIFT = 8 << 16;
 
   public static boolean same(final KeyCommand a, final KeyCommand b) {
-    return a.getClass() == b.getClass() && a.helpText.equals(b.helpText);
+    return a.getClass() == b.getClass()
+        && a.helpText.equals(b.helpText)
+        && a.sibling == b.sibling;
   }
 
   final int keyMask;
   private final String helpText;
+  KeyCommand sibling;
 
   public KeyCommand(final int mask, final int key, final String help) {
     this(mask, (char) key, help);
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java
index ad4c23e..6600a18 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java
@@ -52,6 +52,17 @@
     return map.isEmpty();
   }
 
+  public void add(KeyCommand a, KeyCommand b) {
+    add(a);
+    add(b);
+    pair(a, b);
+  }
+
+  public void pair(KeyCommand a, KeyCommand b) {
+    a.sibling = b;
+    b.sibling = a;
+  }
+
   public void add(final KeyCommand k) {
     assert !map.containsKey(k.keyMask)
          : "Key " + k.describeKeyStroke().asString()
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
index c81f871..67a5ef4 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
@@ -17,17 +17,17 @@
 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.event.dom.client.KeyDownEvent;
 import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.FocusPanel;
 import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.HasHorizontalAlignment;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 import com.google.gwtexpui.user.client.PluginSafePopupPanel;
@@ -36,8 +36,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 
 public class KeyHelpPopup extends PluginSafePopupPanel implements
@@ -164,13 +166,17 @@
   private int formatKeys(final Grid lists, int row, final int col,
       final KeyCommandSet set, final SafeHtml prefix) {
     final CellFormatter fmt = lists.getCellFormatter();
-    final int initialRow = row;
     final List<KeyCommand> keys = sort(set);
     if (lists.getRowCount() < row + keys.size()) {
       lists.resizeRows(row + keys.size());
     }
+
+    Map<KeyCommand, Integer> rows = new HashMap<KeyCommand, Integer>();
     FORMAT_KEYS: for (int i = 0; i < keys.size(); i++) {
       final KeyCommand k = keys.get(i);
+      if (rows.containsKey(k)) {
+        continue;
+      }
 
       if (k instanceof CompoundKeyCommand) {
         final SafeHtmlBuilder b = new SafeHtmlBuilder();
@@ -181,7 +187,7 @@
 
       for (int prior = 0; prior < i; prior++) {
         if (KeyCommand.same(keys.get(prior), k)) {
-          final int r = initialRow + prior;
+          final int r = rows.get(keys.get(prior));
           final SafeHtmlBuilder b = new SafeHtmlBuilder();
           b.append(SafeHtml.get(lists, r, col + 0));
           b.append(" ");
@@ -195,23 +201,29 @@
           }
           b.append(k.describeKeyStroke());
           SafeHtml.set(lists, r, col + 0, b);
+          rows.put(k, r);
           continue FORMAT_KEYS;
         }
       }
 
+      SafeHtmlBuilder b = new SafeHtmlBuilder();
+      String t = k.getHelpText();
       if (prefix != null) {
-        final SafeHtmlBuilder b = new SafeHtmlBuilder();
         b.append(prefix);
         b.append(" ");
         b.append(KeyConstants.I.thenOtherKey());
         b.append(" ");
-        b.append(k.describeKeyStroke());
-        SafeHtml.set(lists, row, col + 0, b);
-      } else {
-        SafeHtml.set(lists, row, col + 0, k.describeKeyStroke());
       }
+      b.append(k.describeKeyStroke());
+      if (k.sibling != null) {
+        b.append(" / ").append(k.sibling.describeKeyStroke());
+        t += " / " + k.sibling.getHelpText();
+        rows.put(k.sibling, row);
+      }
+      SafeHtml.set(lists, row, col + 0, b);
       lists.setText(row, col + 1, ":");
-      lists.setText(row, col + 2, k.getHelpText());
+      lists.setText(row, col + 2, t);
+      rows.put(k, row);
 
       fmt.addStyleName(row, col + 0, KeyResources.I.css().helpKeyStroke());
       fmt.addStyleName(row, col + 1, KeyResources.I.css().helpSeparator());
@@ -226,12 +238,7 @@
     Collections.sort(keys, new Comparator<KeyCommand>() {
       @Override
       public int compare(KeyCommand arg0, KeyCommand arg1) {
-        if (arg0.keyMask < arg1.keyMask) {
-          return -1;
-        } else if (arg0.keyMask > arg1.keyMask) {
-          return 1;
-        }
-        return 0;
+        return arg0.getHelpText().compareTo(arg1.getHelpText());
       }
     });
     return keys;
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/ShowHelpCommand.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/ShowHelpCommand.java
index 50a4a86..b7ea69f 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/ShowHelpCommand.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/ShowHelpCommand.java
@@ -14,18 +14,27 @@
 
 package com.google.gwtexpui.globalkey.client;
 
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.logical.shared.CloseEvent;
 import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.SimpleEventBus;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
 
-
 public class ShowHelpCommand extends KeyCommand {
   public static final ShowHelpCommand INSTANCE = new ShowHelpCommand();
+  private static final EventBus BUS = new SimpleEventBus();
   private static KeyHelpPopup current;
 
+  public static HandlerRegistration addFocusHandler(FocusHandler fh) {
+    return BUS.addHandler(FocusEvent.getType(), fh);
+  }
+
   public ShowHelpCommand() {
     super(0, '?', KeyConstants.I.showHelp());
   }
@@ -36,7 +45,6 @@
       // Already open? Close the dialog.
       //
       current.hide();
-      current = null;
       return;
     }
 
@@ -45,6 +53,7 @@
       @Override
       public void onClose(final CloseEvent<PopupPanel> event) {
         current = null;
+        BUS.fireEvent(new FocusEvent() {});
       }
     });
     current = help;
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/key.css b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/key.css
index 9372e45..755e686 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/key.css
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/key.css
@@ -26,6 +26,7 @@
   width: 92%;
   z-index: 1002;
   opacity: 0.85;
+  border-radius: 10px;
  }
 
 @if user.agent safari {
@@ -80,7 +81,6 @@
 
 .helpGroup {
   color: #dddd00;
-  padding-top: 0.8em;
   text-align: left;
 }
 
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/ServerPlannedIFrameLinker.gwt.xml b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/ServerPlannedIFrameLinker.gwt.xml
deleted file mode 100644
index a6978ab..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/ServerPlannedIFrameLinker.gwt.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<!--
- Copyright (C) 2009 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http:..www.apache.org.licenses.LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<module>
-  <define-linker name='serverplanned' class='com.google.gwtexpui.linker.rebind.ServerPlannedIFrameLinker'/>
-  <add-linker name='serverplanned'/>
-</module>
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/rebind/ServerPlannedIFrameLinker.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/rebind/ServerPlannedIFrameLinker.java
deleted file mode 100644
index 3e2361c..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/rebind/ServerPlannedIFrameLinker.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtexpui.linker.rebind;
-
-import com.google.gwt.core.ext.LinkerContext;
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.linker.AbstractLinker;
-import com.google.gwt.core.ext.linker.ArtifactSet;
-import com.google.gwt.core.ext.linker.CompilationResult;
-import com.google.gwt.core.ext.linker.LinkerOrder;
-import com.google.gwt.core.ext.linker.SelectionProperty;
-import com.google.gwt.core.ext.linker.StylesheetReference;
-
-import java.util.Map;
-import java.util.SortedMap;
-
-/** Saves data normally used by the {@code nocache.js} file. */
-@LinkerOrder(LinkerOrder.Order.POST)
-public class ServerPlannedIFrameLinker extends AbstractLinker {
-  @Override
-  public String getDescription() {
-    return "ServerPlannedIFrameLinker";
-  }
-
-  @Override
-  public ArtifactSet link(final TreeLogger logger, final LinkerContext context,
-      final ArtifactSet artifacts) throws UnableToCompleteException {
-    ArtifactSet toReturn = new ArtifactSet(artifacts);
-
-    StringBuilder table = new StringBuilder();
-    for (StylesheetReference r : artifacts.find(StylesheetReference.class)) {
-      table.append("css ");
-      table.append(r.getSrc());
-      table.append("\n");
-    }
-
-    for (CompilationResult r : artifacts.find(CompilationResult.class)) {
-      table.append(r.getStrongName() + "\n");
-      for (SortedMap<SelectionProperty, String> p : r.getPropertyMap()) {
-        for (Map.Entry<SelectionProperty, String> e : p.entrySet()) {
-          table.append("  ");
-          table.append(e.getKey().getName());
-          table.append("=");
-          table.append(e.getValue());
-          table.append('\n');
-        }
-      }
-      table.append("\n");
-    }
-
-    toReturn.add(emitString(logger, table.toString(), "permutations"));
-    return toReturn;
-  }
-}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/ClientSideRule.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/ClientSideRule.java
deleted file mode 100644
index 89da529..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/ClientSideRule.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtexpui.linker.server;
-
-import javax.servlet.http.HttpServletRequest;
-
-/** A rule that must execute on the client, as we don't know how to compute it. */
-final class ClientSideRule implements Rule {
-  private final String name;
-
-  ClientSideRule(String name) {
-    this.name = name;
-  }
-
-  @Override
-  public String getName() {
-    return name;
-  }
-
-  @Override
-  public String select(HttpServletRequest req) {
-    return null;
-  }
-}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/Permutation.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/Permutation.java
deleted file mode 100644
index b319db1..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/Permutation.java
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtexpui.linker.server;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Arrays;
-
-/** A single permutation of the compiled GWT application. */
-public class Permutation {
-  private final PermutationSelector selector;
-  private final String cacheHTML;
-  private final String[] values;
-
-  Permutation(PermutationSelector sel, String cacheHTML, String[] values) {
-    this.selector = sel;
-    this.cacheHTML = cacheHTML;
-    this.values = values;
-  }
-
-  boolean matches(String[] r) {
-    return Arrays.equals(values, r);
-  }
-
-  /**
-   * Append GWT bootstrap for this permutation onto the end of the body.
-   * <p>
-   * The GWT bootstrap for this particular permutation is appended onto the end
-   * of the {@code body} element of the passed host page.
-   * <p>
-   * To keep the bootstrap code small and simple, not all GWT features are
-   * actually supported. The {@code gwt:property}, {@code gwt:onPropertyErrorFn}
-   * and {@code gwt:onLoadErrorFn} meta tags are ignored and not handled.
-   * <p>
-   * Load order may differ from the standard GWT {@code nocache.js}. The browser
-   * is asked to load the iframe immediately, rather than after the body has
-   * finished loading.
-   *
-   * @param dom host page HTML document.
-   */
-  public void inject(Document dom) {
-    String moduleName = selector.getModuleName();
-    String moduleFunc = moduleName;
-
-    StringBuilder s = new StringBuilder();
-    s.append("\n");
-    s.append("function " + moduleFunc + "(){");
-    s.append("var s,l,t");
-    s.append(",w=window");
-    s.append(",d=document");
-    s.append(",n='" + moduleName + "'");
-    s.append(",f=d.createElement('iframe')");
-    s.append(";");
-
-    // Callback to execute the module once both s and l are true.
-    //
-    s.append("function m(){");
-    s.append("if(s&&l){");
-    // Base path needs to be absolute. There isn't an easy way to do this
-    // other than forcing an image to load and then pulling the URL back.
-    //
-    s.append("var b,i=d.createElement('img');");
-    s.append("i.src=n+'/clear.cache.gif';");
-    s.append("b=i.src;");
-    s.append("b=b.substring(0,b.lastIndexOf('/')+1);");
-    s.append(moduleFunc + "=null;"); // allow us to GC
-    s.append("f.contentWindow.gwtOnLoad(undefined,n,b);");
-    s.append("}");
-    s.append("}");
-
-    // Set s true when the module script has finished loading. The
-    // exact name here is known to the IFrameLinker and is called by
-    // the code in the iframe.
-    //
-    s.append(moduleFunc + ".onScriptLoad=function(){");
-    s.append("s=1;m();");
-    s.append("};");
-
-    // Set l true when the browser has finished processing the iframe
-    // tag, and everything else on the page.
-    //
-    s.append(moduleFunc + ".r=function(){");
-    s.append("l=1;m();");
-    s.append("};");
-
-    // Prevents mixed mode security in IE6/7.
-    s.append("f.src=\"javascript:''\";");
-    s.append("f.id=n;");
-    s.append("f.style.cssText"
-        + "='position:absolute;width:0;height:0;border:none';");
-    s.append("f.tabIndex=-1;");
-    s.append("d.body.appendChild(f);");
-
-    // The src has to be set after the iframe is attached to the DOM to avoid
-    // refresh quirks in Safari. We have to use the location.replace trick to
-    // avoid FF2 refresh quirks.
-    //
-    s.append("f.contentWindow.location.replace(n+'/" + cacheHTML + "');");
-
-    // defer attribute here is to workaround IE running immediately.
-    //
-    s.append("d.write('<script defer=\"defer\">" //
-        + moduleFunc + ".r()</'+'script>');");
-    s.append("}");
-    s.append(moduleFunc + "();");
-    s.append("\n//");
-
-    final Element html = dom.getDocumentElement();
-    final Element head = (Element) html.getElementsByTagName("head").item(0);
-    final Element body = (Element) html.getElementsByTagName("body").item(0);
-
-    for (String css : selector.getCSS()) {
-      if (isRelativeURL(css)) {
-        css = moduleName + '/' + css;
-      }
-
-      final Element link = dom.createElement("link");
-      link.setAttribute("rel", "stylesheet");
-      link.setAttribute("href", css);
-      head.appendChild(link);
-    }
-
-    final Element script = dom.createElement("script");
-    script.setAttribute("type", "text/javascript");
-    script.setAttribute("language", "javascript");
-    script.appendChild(dom.createComment(s.toString()));
-    body.appendChild(script);
-  }
-
-  private static boolean isRelativeURL(String src) {
-    if (src.startsWith("/")) {
-      return false;
-    }
-
-    try {
-      // If it parses as a URL, assume it is not relative.
-      //
-      new URL(src);
-      return false;
-    } catch (MalformedURLException e) {
-    }
-
-    return true;
-  }
-}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/PermutationSelector.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/PermutationSelector.java
deleted file mode 100644
index d3e5ae3..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/PermutationSelector.java
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtexpui.linker.server;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * Selects a permutation based on the HTTP request.
- * <p>
- * To use this class the application's GWT module must include our linker by
- * inheriting our module:
- *
- * <pre>
- *   &lt;inherits name='com.google.gwtexpui.linker.ServerPlannedIFrameLinker'/&gt;
- * </pre>
- */
-public class PermutationSelector {
-  private final String moduleName;
-  private final Map<String, Rule> rulesByName;
-  private final List<Rule> ruleOrder;
-  private final List<Permutation> permutations;
-  private final List<String> css;
-
-  /**
-   * Create an empty selector for a module.
-   * <p>
-   * {@link UserAgentRule} rule is automatically registered. Additional custom
-   * selector rules may be registered before {@link #init(ServletContext)} is
-   * called to finish the selector setup.
-   *
-   * @param moduleName the name of the module within the context.
-   */
-  public PermutationSelector(final String moduleName) {
-    this.moduleName = moduleName;
-
-    this.rulesByName = new HashMap<String, Rule>();
-    this.ruleOrder = new ArrayList<Rule>();
-    this.permutations = new ArrayList<Permutation>();
-    this.css = new ArrayList<String>();
-
-    register(new UserAgentRule());
-  }
-
-  private void notInitialized() {
-    if (!ruleOrder.isEmpty()) {
-      throw new IllegalStateException("Already initialized");
-    }
-  }
-
-  /**
-   * Register a property selection rule.
-   *
-   * @param r the rule implementation.
-   */
-  public void register(Rule r) {
-    notInitialized();
-    rulesByName.put(r.getName(), r);
-  }
-
-  /**
-   * Initialize the selector by reading the module's {@code permutations} file.
-   *
-   * @param ctx context to load the module data from.
-   * @throws ServletException
-   * @throws IOException
-   */
-  public void init(ServletContext ctx) throws ServletException, IOException {
-    notInitialized();
-
-    final String tableName = "/" + moduleName + "/permutations";
-    final InputStream in = ctx.getResourceAsStream(tableName);
-    if (in == null) {
-      throw new ServletException("No " + tableName + " in context");
-    }
-    try {
-      BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
-      for (;;) {
-        final String strongName = r.readLine();
-        if (strongName == null) {
-          break;
-        }
-
-        if (strongName.startsWith("css ")) {
-          css.add(strongName.substring("css ".length()));
-          continue;
-        }
-
-        Map<String, String> selections = new LinkedHashMap<String, String>();
-        for (;;) {
-          String permutation = r.readLine();
-          if (permutation == null || permutation.isEmpty()) {
-            break;
-          }
-
-          int eq = permutation.indexOf('=');
-          if (eq < 0) {
-            throw new ServletException(tableName + " has malformed content");
-          }
-
-          String k = permutation.substring(0, eq).trim();
-          String v = permutation.substring(eq + 1);
-
-          Rule rule = get(k);
-          if (!ruleOrder.contains(rule)) {
-            ruleOrder.add(rule);
-          }
-
-          if (selections.put(k, v) != null) {
-            throw new ServletException("Table " + tableName
-                + " has multiple values for " + k + " within permutation "
-                + strongName);
-          }
-        }
-
-        String cacheHtml = strongName + ".cache.html";
-        String[] values = new String[ruleOrder.size()];
-        for (int i = 0; i < values.length; i++) {
-          values[i] = selections.get(ruleOrder.get(i).getName());
-        }
-        permutations.add(new Permutation(this, cacheHtml, values));
-      }
-    } finally {
-      in.close();
-    }
-  }
-
-  private Rule get(final String name) {
-    Rule r = rulesByName.get(name);
-    if (r == null) {
-      r = new ClientSideRule(name);
-      register(r);
-    }
-    return r;
-  }
-
-  /** @return name of the module (within the application context). */
-  public String getModuleName() {
-    return moduleName;
-  }
-
-  /** @return all possible permutations */
-  public List<Permutation> getPermutations() {
-    return Collections.unmodifiableList(permutations);
-  }
-
-  /**
-   * Select the permutation that best matches the browser request.
-   *
-   * @param req current request.
-   * @return the selected permutation; null if no permutation can be fit to the
-   *         request and the standard {@code nocache.js} loader must be used.
-   */
-  public Permutation select(HttpServletRequest req) {
-    final String[] values = new String[ruleOrder.size()];
-    for (int i = 0; i < values.length; i++) {
-      final String value = ruleOrder.get(i).select(req);
-      if (value == null) {
-        // If the rule returned null it doesn't know how to compute
-        // the value for this HTTP request. Since we can't do that
-        // defer to JavaScript by not picking a permutation.
-        //
-        return null;
-      }
-      values[i] = value;
-    }
-
-    for (Permutation p : permutations) {
-      if (p.matches(values)) {
-        return p;
-      }
-    }
-
-    return null;
-  }
-
-  Collection<String> getCSS() {
-    return css;
-  }
-}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/Rule.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/Rule.java
deleted file mode 100644
index 76b9b51..0000000
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/Rule.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gwtexpui.linker.server;
-
-import javax.servlet.http.HttpServletRequest;
-
-/** A selection rule for a permutation property. */
-public interface Rule {
-  /** @return the property name, for example {@code "user.agent"}. */
-  public String getName();
-
-  /**
-   * Compute the value for this property, given the current request.
-   * <p>
-   * This rule method must compute the proper permutation value, matching what
-   * the GWT module XML files use for this property. The rule may use any state
-   * available in the current servlet request.
-   * <p>
-   * If this method returns {@code null} server side selection will be aborted
-   * and selection for all properties will be handled on the client side by the
-   * {@code nocache.js} file.
-   *
-   * @param req the request
-   * @return the value for the property; null if the value cannot be determined
-   *         on the server side.
-   */
-  public String select(HttpServletRequest req);
-}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
index 366b6c5..5bb3c1e 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/linker/server/UserAgentRule.java
@@ -28,7 +28,7 @@
  * <p>
  * Ported from JavaScript in {@code com.google.gwt.user.UserAgent.gwt.xml}.
  */
-public class UserAgentRule implements Rule {
+public class UserAgentRule {
   private static final Pattern msie = compile(".*msie ([0-9]+)\\.([0-9]+).*");
   private static final Pattern gecko = compile(".*rv:([0-9]+)\\.([0-9]+).*");
 
@@ -36,7 +36,6 @@
     return "user.agent";
   }
 
-  @Override
   public String select(HttpServletRequest req) {
     String ua = req.getHeader("User-Agent");
     if (ua == null) {
@@ -45,13 +44,13 @@
 
     ua = ua.toLowerCase();
 
-    if (ua.indexOf("opera") != -1) {
+    if (ua.contains("opera")) {
       return "opera";
 
-    } else if (ua.indexOf("webkit") != -1) {
+    } else if (ua.contains("webkit")) {
       return "safari";
 
-    } else if (ua.indexOf("msie") != -1) {
+    } else if (ua.contains("msie")) {
       // GWT 2.0 uses document.documentMode here, which we can't do
       // on the server side.
 
@@ -73,7 +72,7 @@
       }
       return null;
 
-    } else if (ua.indexOf("gecko") != -1) {
+    } else if (ua.contains("gecko")) {
       Matcher m = gecko.matcher(ua);
       if (m.matches() && m.groupCount() == 2) {
         if (makeVersion(m) >= 1008) {
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/progress/client/ProgressBar.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/progress/client/ProgressBar.java
index 5e13f55..931e84e 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/progress/client/ProgressBar.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/progress/client/ProgressBar.java
@@ -71,7 +71,7 @@
   public void setValue(final int pComplete) {
     assert 0 <= pComplete && pComplete <= 100;
     value = pComplete;
-    bar.setWidth("" + (2 * pComplete) + "px");
+    bar.setWidth(2 * pComplete + "px");
     msg.setText(callerText + pComplete + "%");
   }
 }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
index 61bec18..ed4e6cb 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/HighlightSuggestOracle.java
@@ -23,8 +23,10 @@
  * <p>
  * Suggestions supplied by the implementation of
  * {@link #onRequestSuggestions(Request, Callback)} are modified to wrap all
- * occurrences of the {@link SuggestOracle.Request#getQuery()} substring in HTML
- * <code>&lt;strong&gt;</code> tags, so they can be emphasized to the user.
+ * occurrences of the
+ * {@link com.google.gwt.user.client.ui.SuggestOracle.Request#getQuery()}
+ * substring in HTML {@code &lt;strong&gt;} tags, so they can be emphasized to
+ * the user.
  */
 public abstract class HighlightSuggestOracle extends SuggestOracle {
   private static String escape(String ds) {
@@ -57,9 +59,10 @@
   }
 
   /**
-   * @return true if {@link SuggestOracle.Suggestion#getDisplayString()} returns
-   *         HTML; false if the text must be escaped before evaluating in an
-   *         HTML like context.
+   * @return true if
+   *         {@link com.google.gwt.user.client.ui.SuggestOracle.Suggestion#getDisplayString()}
+   *         returns HTML; false if the text must be escaped before evaluating
+   *         in an HTML like context.
    */
   protected boolean isHTML() {
     return false;
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/LinkFindReplace.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/LinkFindReplace.java
index 7e32ff0..464cbe9 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/LinkFindReplace.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/LinkFindReplace.java
@@ -49,7 +49,7 @@
   /**
    * @param find regular expression pattern to match substrings with.
    * @param link replacement link href. Capture groups within
-   *        <code>find</code> can be referenced with <code>$<i>n</i></code>.
+   *        {@code find} can be referenced with {@code $<i>n</i>}.
    */
   public LinkFindReplace(String find, String link) {
     this.pat = RegExp.compile(find);
@@ -68,13 +68,12 @@
       throw new IllegalArgumentException(
           "Invalid scheme (" + toString() + "): " + href);
     }
-    String result = new SafeHtmlBuilder()
+    return new SafeHtmlBuilder()
         .openAnchor()
         .setAttribute("href", href)
         .append(SafeHtml.asis(input))
         .closeAnchor()
         .asString();
-    return result;
   }
 
   @Override
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/RawFindReplace.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/RawFindReplace.java
index 63b5fde..96026ad 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/RawFindReplace.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/RawFindReplace.java
@@ -31,7 +31,7 @@
   /**
    * @param find regular expression pattern to match substrings with.
    * @param replace replacement expression. Capture groups within
-   *        <code>find</code> can be referenced with <code>$<i>n</i></code>.
+   *        {@code find} can be referenced with {@code $<i>n</i>}.
    */
   public RawFindReplace(String find, String replace) {
     this.pat = RegExp.compile(find);
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
index f752780..143ecef 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
@@ -52,6 +52,10 @@
               return "wikiPreFormat";
             }
 
+            public String wikiQuote() {
+              return "wikiQuote";
+            }
+
             public boolean ensureInjected() {
               return false;
             }
@@ -109,8 +113,8 @@
   }
 
   /** Parse an HTML block and return the first (typically root) element. */
-  public static com.google.gwt.user.client.Element parse(SafeHtml html) {
-    com.google.gwt.user.client.Element e = DOM.createDiv();
+  public static Element parse(SafeHtml html) {
+    Element e = DOM.createDiv();
     setInnerHTML(e, html);
     return DOM.getFirstChild(e);
   }
@@ -139,7 +143,10 @@
   public SafeHtml wikify() {
     final SafeHtmlBuilder r = new SafeHtmlBuilder();
     for (final String p : linkify().asString().split("\n\n")) {
-      if (isPreFormat(p)) {
+      if (isQuote(p)) {
+        wikifyQuote(r, p);
+
+      } else if (isPreFormat(p)) {
         r.openElement("p");
         for (final String line : p.split("\n")) {
           r.openSpan();
@@ -202,6 +209,31 @@
     }
   }
 
+  private void wikifyQuote(SafeHtmlBuilder r, String p) {
+    r.openElement("blockquote");
+    r.setStyleName(RESOURCES.css().wikiQuote());
+    if (p.startsWith("&gt; ")) {
+      p = p.substring(5);
+    } else if (p.startsWith(" &gt; ")) {
+      p = p.substring(6);
+    }
+    p = p.replaceAll("\\n ?&gt; ", "\n");
+    for (String e : p.split("\n\n")) {
+      if (isQuote(e)) {
+        SafeHtmlBuilder b = new SafeHtmlBuilder();
+        wikifyQuote(b, e);
+        r.append(b);
+      } else {
+        r.append(asis(e));
+      }
+    }
+    r.closeElement("blockquote");
+  }
+
+  private static boolean isQuote(String p) {
+    return p.startsWith("&gt; ") || p.startsWith(" &gt; ");
+  }
+
   private static boolean isPreFormat(final String p) {
     return p.contains("\n ") || p.contains("\n\t") || p.startsWith(" ")
         || p.startsWith("\t");
@@ -213,7 +245,7 @@
   }
 
   /**
-   * Replace first occurrence of <code>regex</code> with <code>repl</code> .
+   * Replace first occurrence of {@code regex} with {@code repl} .
    * <p>
    * <b>WARNING:</b> This replacement is being performed against an otherwise
    * safe HTML string. The caller must ensure that the replacement does not
@@ -221,7 +253,7 @@
    *
    * @param regex regular expression pattern to match the substring with.
    * @param repl replacement expression. Capture groups within
-   *        <code>regex</code> can be referenced with <code>$<i>n</i></code>.
+   *        {@code regex} can be referenced with {@code $<i>n</i>}.
    * @return a new string, after the replacement has been made.
    */
   public SafeHtml replaceFirst(final String regex, final String repl) {
@@ -229,7 +261,7 @@
   }
 
   /**
-   * Replace each occurrence of <code>regex</code> with <code>repl</code> .
+   * Replace each occurrence of {@code regex} with {@code repl} .
    * <p>
    * <b>WARNING:</b> This replacement is being performed against an otherwise
    * safe HTML string. The caller must ensure that the replacement does not
@@ -237,7 +269,7 @@
    *
    * @param regex regular expression pattern to match substrings with.
    * @param repl replacement expression. Capture groups within
-   *        <code>regex</code> can be referenced with <code>$<i>n</i></code>.
+   *        {@code regex} can be referenced with {@code $<i>n</i>}.
    * @return a new string, after the replacements have been made.
    */
   public SafeHtml replaceAll(final String regex, final String repl) {
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
index 287988f..75337ac 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
@@ -165,8 +165,8 @@
    * Open an element, appending "<tagName>" to the buffer.
    * <p>
    * After the element is open the attributes may be manipulated until the next
-   * <code>append</code>, <code>openElement</code>, <code>closeSelf</code> or
-   * <code>closeElement</code> call.
+   * {@code append}, {@code openElement}, {@code closeSelf} or
+   * {@code closeElement} call.
    *
    * @param tagName name of the HTML element to open.
    */
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlCss.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlCss.java
index f6836a0..651ebaf 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlCss.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/SafeHtmlCss.java
@@ -17,6 +17,7 @@
 import com.google.gwt.resources.client.CssResource;
 
 public interface SafeHtmlCss extends CssResource {
-  String wikiPreFormat();
   String wikiList();
+  String wikiPreFormat();
+  String wikiQuote();
 }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/safehtml.css b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/safehtml.css
index fcad92c..163c548 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/safehtml.css
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/safehtml/client/safehtml.css
@@ -13,11 +13,17 @@
  * limitations under the License.
  */
 
+.wikiList {
+}
+
 .wikiPreFormat {
   white-space: pre;
   font-family: 'Lucida Console', 'Lucida Sans Typewriter', Monaco, monospace;
   font-size: small;
 }
 
-.wikiList {
+.wikiQuote {
+  margin-left: 0;
+  border-left: 1px solid #888;
+  padding-left: 5px;
 }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java
index 2033c62..54bfcd0 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/server/CacheControlFilter.java
@@ -35,7 +35,7 @@
  * URL changes, so user agents would request a different resource. We force
  * these resources to have very long expiration times.
  * <p>
- * To use, add the following block to your <code>web.xml</code>:
+ * To use, add the following block to your {@code web.xml}:
  *
  * <pre>
  * &lt;filter&gt;
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/View.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/View.java
index 35ecb12..f826fd0 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/View.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/View.java
@@ -20,12 +20,12 @@
 /**
  * Widget to display within a {@link ViewSite}.
  *<p>
- * Implementations must override <code>protected void onLoad()</code> and
+ * Implementations must override {@code protected void onLoad()} and
  * arrange for {@link #display()} to be invoked once the DOM within the view is
  * consistent for presentation to the user. Typically this means that the
- * subclass can start RPCs within <code>onLoad()</code> and then invoke
- * <code>display()</code> from within the AsyncCallback's
- * <code>onSuccess(Object)</code> method.
+ * subclass can start RPCs within {@code onLoad()} and then invoke
+ * {@code display()} from within the AsyncCallback's
+ * {@code onSuccess(Object)} method.
  */
 public abstract class View extends Composite {
   ViewSite<? extends View> site;
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/ViewSite.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/ViewSite.java
index 30b8408f..629b6b3 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/ViewSite.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/user/client/ViewSite.java
@@ -22,7 +22,7 @@
  * Hosts a single {@link View}.
  * <p>
  * View instances are attached inside of an invisible DOM node, permitting their
- * <code>onLoad()</code> method to be invoked and to update the DOM prior to the
+ * {@code onLoad()} method to be invoked and to update the DOM prior to the
  * elements being made visible in the UI.
  * <p>
  * Complaint View instances must invoke {@link View#display()} once the DOM is
@@ -48,7 +48,7 @@
    * Set the next view to display.
    * <p>
    * The view will be attached to the DOM tree within a hidden container,
-   * permitting its <code>onLoad()</code> method to execute and update the DOM
+   * permitting its {@code onLoad()} method to execute and update the DOM
    * without the user seeing the result.
    *
    * @param view the next view to display.
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/LinkFindReplaceTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/LinkFindReplaceTest.java
index 97f816f..0d8336e 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/LinkFindReplaceTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/LinkFindReplaceTest.java
@@ -15,10 +15,15 @@
 package com.google.gwtexpui.safehtml.client;
 
 import static com.google.gwtexpui.safehtml.client.LinkFindReplace.hasValidScheme;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
-public class LinkFindReplaceTest extends TestCase {
+public class LinkFindReplaceTest {
+  @Test
   public void testNoEscaping() {
     String find = "find";
     String link = "link";
@@ -28,12 +33,14 @@
     assertEquals("find = " + find + ", link = " + link, a.toString());
   }
 
+  @Test
   public void testBackreference() {
     assertEquals("<a href=\"/bug?id=123\">issue 123</a>",
         new LinkFindReplace("(bug|issue)\\s*([0-9]+)", "/bug?id=$2")
             .replace("issue 123"));
   }
 
+  @Test
   public void testHasValidScheme() {
     assertTrue(hasValidScheme("/absolute/path"));
     assertTrue(hasValidScheme("relative/path"));
@@ -46,6 +53,7 @@
     assertFalse(hasValidScheme("javascript:alert(1)"));
   }
 
+  @Test
   public void testInvalidSchemeInReplace() {
     try {
       new LinkFindReplace("find", "javascript:alert(1)").replace("find");
@@ -54,6 +62,7 @@
     }
   }
 
+  @Test
   public void testInvalidSchemeWithBackreference() {
     try {
       new LinkFindReplace(".*(script:[^;]*)", "java$1")
@@ -63,11 +72,13 @@
     }
   }
 
+  @Test
   public void testReplaceEscaping() {
     assertEquals("<a href=\"a&quot;&amp;&#39;&lt;&gt;b\">find</a>",
         new LinkFindReplace("find", "a\"&'<>b").replace("find"));
   }
 
+  @Test
   public void testHtmlInFind() {
     String rawFind = "<b>&quot;bold&quot;</b>";
     LinkFindReplace a = new LinkFindReplace(rawFind, "/bold");
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java
index 9c450bd..bc20a9d 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/RawFindReplaceTest.java
@@ -14,9 +14,11 @@
 
 package com.google.gwtexpui.safehtml.client;
 
-import junit.framework.TestCase;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
 
-public class RawFindReplaceTest extends TestCase {
+public class RawFindReplaceTest {
+  @Test
   public void testFindReplace() {
     final String find = "find";
     final String replace = "replace";
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilderTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilderTest.java
index a6b0012..0163d7f 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilderTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilderTest.java
@@ -14,9 +14,18 @@
 
 package com.google.gwtexpui.safehtml.client;
 
-import junit.framework.TestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-public class SafeHtmlBuilderTest extends TestCase {
+import org.junit.Test;
+
+public class SafeHtmlBuilderTest {
+  @Test
   public void testEmpty() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertTrue(b.isEmpty());
@@ -28,6 +37,7 @@
     assertEquals("a", b.asString());
   }
 
+  @Test
   public void testToSafeHtml() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     b.append(1);
@@ -39,6 +49,7 @@
     assertEquals("1", h.asString());
   }
 
+  @Test
   public void testAppend_boolean() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append(true));
@@ -46,6 +57,7 @@
     assertEquals("truefalse", b.asString());
   }
 
+  @Test
   public void testAppend_char() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append('a'));
@@ -53,6 +65,7 @@
     assertEquals("ab", b.asString());
   }
 
+  @Test
   public void testAppend_int() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append(4));
@@ -61,6 +74,7 @@
     assertEquals("42-100", b.asString());
   }
 
+  @Test
   public void testAppend_long() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append(4L));
@@ -68,18 +82,21 @@
     assertEquals("42", b.asString());
   }
 
+  @Test
   public void testAppend_float() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append(0.0f));
     assertEquals("0.0", b.asString());
   }
 
+  @Test
   public void testAppend_double() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append(0.0));
     assertEquals("0.0", b.asString());
   }
 
+  @Test
   public void testAppend_String() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append((String) null));
@@ -89,6 +106,7 @@
     assertEquals("foobar", b.asString());
   }
 
+  @Test
   public void testAppend_StringBuilder() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append((StringBuilder) null));
@@ -98,6 +116,7 @@
     assertEquals("foobar", b.asString());
   }
 
+  @Test
   public void testAppend_StringBuffer() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append((StringBuffer) null));
@@ -107,6 +126,7 @@
     assertEquals("foobar", b.asString());
   }
 
+  @Test
   public void testAppend_Object() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append((Object) null));
@@ -120,6 +140,7 @@
     assertEquals("foobar", b.asString());
   }
 
+  @Test
   public void testAppend_CharSequence() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append((CharSequence) null));
@@ -129,6 +150,7 @@
     assertEquals("foobar", b.asString());
   }
 
+  @Test
   public void testAppend_SafeHtml() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.append((SafeHtml) null));
@@ -138,6 +160,7 @@
     assertEquals("foobar", b.asString());
   }
 
+  @Test
   public void testHtmlSpecialCharacters() {
     assertEquals("&amp;", escape("&"));
     assertEquals("&lt;", escape("<"));
@@ -155,18 +178,21 @@
     assertEquals("&amp;lt;b&amp;gt;", escape("&lt;b&gt;"));
   }
 
+  @Test
   public void testEntityNbsp() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.nbsp());
     assertEquals("&nbsp;", b.asString());
   }
 
+  @Test
   public void testTagBr() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.br());
     assertEquals("<br />", b.asString());
   }
 
+  @Test
   public void testTagTableTrTd() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.openElement("table"));
@@ -179,6 +205,7 @@
     assertEquals("<table><tr><td>d&lt;a&gt;ta</td></tr></table>", b.asString());
   }
 
+  @Test
   public void testTagDiv() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.openDiv());
@@ -187,6 +214,7 @@
     assertEquals("<div>d&lt;a&gt;ta</div>", b.asString());
   }
 
+  @Test
   public void testTagAnchor() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.openAnchor());
@@ -206,6 +234,7 @@
     assertEquals("<a href=\"d&lt;a&gt;ta\">go</a>", b.asString());
   }
 
+  @Test
   public void testTagHeightWidth() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.openElement("img"));
@@ -215,6 +244,7 @@
     assertEquals("<img height=\"100\" width=\"42\" />", b.asString());
   }
 
+  @Test
   public void testStyleName() {
     final SafeHtmlBuilder b = new SafeHtmlBuilder();
     assertSame(b, b.openSpan());
@@ -225,6 +255,7 @@
     assertEquals("<span class=\"foo bar\">d&lt;a&gt;ta</span>", b.asString());
   }
 
+  @Test
   public void testRejectJavaScript_AnchorHref() {
     final String href = "javascript:window.close();";
     try {
@@ -235,6 +266,7 @@
     }
   }
 
+  @Test
   public void testRejectJavaScript_ImgSrc() {
     final String href = "javascript:window.close();";
     try {
@@ -245,6 +277,7 @@
     }
   }
 
+  @Test
   public void testRejectJavaScript_FormAction() {
     final String href = "javascript:window.close();";
     try {
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java
index a9d9450..749df17 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_LinkifyTest.java
@@ -14,9 +14,13 @@
 
 package com.google.gwtexpui.safehtml.client;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
-public class SafeHtml_LinkifyTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+
+public class SafeHtml_LinkifyTest {
+  @Test
   public void testLinkify_SimpleHttp1() {
     final SafeHtml o = html("A http://go.here/ B");
     final SafeHtml n = o.linkify();
@@ -24,6 +28,7 @@
     assertEquals("A <a href=\"http://go.here/\" target=\"_blank\">http://go.here/</a> B", n.asString());
   }
 
+  @Test
   public void testLinkify_SimpleHttps2() {
     final SafeHtml o = html("A https://go.here/ B");
     final SafeHtml n = o.linkify();
@@ -31,6 +36,7 @@
     assertEquals("A <a href=\"https://go.here/\" target=\"_blank\">https://go.here/</a> B", n.asString());
   }
 
+  @Test
   public void testLinkify_Parens1() {
     final SafeHtml o = html("A (http://go.here/) B");
     final SafeHtml n = o.linkify();
@@ -38,6 +44,7 @@
     assertEquals("A (<a href=\"http://go.here/\" target=\"_blank\">http://go.here/</a>) B", n.asString());
   }
 
+  @Test
   public void testLinkify_Parens() {
     final SafeHtml o = html("A http://go.here/#m() B");
     final SafeHtml n = o.linkify();
@@ -45,6 +52,7 @@
     assertEquals("A <a href=\"http://go.here/#m()\" target=\"_blank\">http://go.here/#m()</a> B", n.asString());
   }
 
+  @Test
   public void testLinkify_AngleBrackets1() {
     final SafeHtml o = html("A <http://go.here/> B");
     final SafeHtml n = o.linkify();
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java
index d7a3aaf..71b55a1 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_ReplaceTest.java
@@ -14,19 +14,25 @@
 
 package com.google.gwtexpui.safehtml.client;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-public class SafeHtml_ReplaceTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+public class SafeHtml_ReplaceTest {
+  @Test
   public void testReplaceEmpty() {
     SafeHtml o = html("A\nissue42\nB");
     assertSame(o, o.replaceAll(null));
     assertSame(o, o.replaceAll(Collections.<FindReplace> emptyList()));
   }
 
+  @Test
   public void testReplaceOneLink() {
     SafeHtml o = html("A\nissue 42\nB");
     SafeHtml n = o.replaceAll(repls(
@@ -35,6 +41,7 @@
     assertEquals("A\n<a href=\"?42\">issue 42</a>\nB", n.asString());
   }
 
+  @Test
   public void testReplaceNoLeadingOrTrailingText() {
     SafeHtml o = html("issue 42");
     SafeHtml n = o.replaceAll(repls(
@@ -43,6 +50,7 @@
     assertEquals("<a href=\"?42\">issue 42</a>", n.asString());
   }
 
+  @Test
   public void testReplaceTwoLinks() {
     SafeHtml o = html("A\nissue 42\nissue 9918\nB");
     SafeHtml n = o.replaceAll(repls(
@@ -55,6 +63,7 @@
     , n.asString());
   }
 
+  @Test
   public void testReplaceInOrder() {
     SafeHtml o = html("A\nissue 42\nReally GWTEXPUI-9918 is better\nB");
     SafeHtml n = o.replaceAll(repls(
@@ -70,6 +79,7 @@
     , n.asString());
   }
 
+  @Test
   public void testReplaceOverlappingAfterFirstChar() {
     SafeHtml o = html("abcd");
     RawFindReplace ab = new RawFindReplace("ab", "AB");
@@ -81,6 +91,7 @@
     assertEquals("ABYZ", o.replaceAll(repls(ab, bc, cd)).asString());
   }
 
+  @Test
   public void testReplaceOverlappingAtFirstCharLongestMatch() {
     SafeHtml o = html("abcd");
     RawFindReplace ab = new RawFindReplace("ab", "AB");
@@ -90,6 +101,7 @@
     assertEquals("234d", o.replaceAll(repls(abc, ab)).asString());
   }
 
+  @Test
   public void testReplaceOverlappingAtFirstCharFirstMatch() {
     SafeHtml o = html("abcd");
     RawFindReplace ab1 = new RawFindReplace("ab", "AB");
@@ -99,6 +111,7 @@
     assertEquals("12cd", o.replaceAll(repls(ab2, ab1)).asString());
   }
 
+  @Test
   public void testFailedSanitization() {
     SafeHtml o = html("abcd");
     LinkFindReplace evil = new LinkFindReplace("(b)", "javascript:alert('$1')");
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java
index 250a1b5..045555a 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyListTest.java
@@ -14,9 +14,12 @@
 
 package com.google.gwtexpui.safehtml.client;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
-public class SafeHtml_WikifyListTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+
+public class SafeHtml_WikifyListTest {
   private static final String BEGIN_LIST = "<ul class=\"wikiList\">";
   private static final String END_LIST = "</ul>";
 
@@ -24,6 +27,7 @@
     return "<li>" + raw + "</li>";
   }
 
+  @Test
   public void testBulletList1() {
     final SafeHtml o = html("A\n\n* line 1\n* 2nd line");
     final SafeHtml n = o.wikify();
@@ -36,6 +40,7 @@
     , n.asString());
   }
 
+  @Test
   public void testBulletList2() {
     final SafeHtml o = html("A\n\n* line 1\n* 2nd line\n\nB");
     final SafeHtml n = o.wikify();
@@ -49,6 +54,7 @@
     , n.asString());
   }
 
+  @Test
   public void testBulletList3() {
     final SafeHtml o = html("* line 1\n* 2nd line\n\nB");
     final SafeHtml n = o.wikify();
@@ -61,6 +67,7 @@
     , n.asString());
   }
 
+  @Test
   public void testBulletList4() {
     final SafeHtml o = html("To see this bug, you have to:\n" //
         + "* Be on IMAP or EAS (not on POP)\n"//
@@ -75,6 +82,7 @@
     , n.asString());
   }
 
+  @Test
   public void testBulletList5() {
     final SafeHtml o = html("To see this bug,\n" //
         + "you have to:\n" //
@@ -90,6 +98,7 @@
     , n.asString());
   }
 
+  @Test
   public void testDashList1() {
     final SafeHtml o = html("A\n\n- line 1\n- 2nd line");
     final SafeHtml n = o.wikify();
@@ -102,6 +111,7 @@
     , n.asString());
   }
 
+  @Test
   public void testDashList2() {
     final SafeHtml o = html("A\n\n- line 1\n- 2nd line\n\nB");
     final SafeHtml n = o.wikify();
@@ -115,6 +125,7 @@
     , n.asString());
   }
 
+  @Test
   public void testDashList3() {
     final SafeHtml o = html("- line 1\n- 2nd line\n\nB");
     final SafeHtml n = o.wikify();
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java
index cbb315b..605185e 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyPreformatTest.java
@@ -14,9 +14,12 @@
 
 package com.google.gwtexpui.safehtml.client;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
-public class SafeHtml_WikifyPreformatTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+
+public class SafeHtml_WikifyPreformatTest {
   private static final String B = "<span class=\"wikiPreFormat\">";
   private static final String E = "</span><br />";
 
@@ -24,6 +27,7 @@
     return B + raw + E;
   }
 
+  @Test
   public void testPreformat1() {
     final SafeHtml o = html("A\n\n  This is pre\n  formatted");
     final SafeHtml n = o.wikify();
@@ -36,6 +40,7 @@
     , n.asString());
   }
 
+  @Test
   public void testPreformat2() {
     final SafeHtml o = html("A\n\n  This is pre\n  formatted\n\nbut this is not");
     final SafeHtml n = o.wikify();
@@ -49,6 +54,7 @@
     , n.asString());
   }
 
+  @Test
   public void testPreformat3() {
     final SafeHtml o = html("A\n\n  Q\n    <R>\n  S\n\nB");
     final SafeHtml n = o.wikify();
@@ -63,6 +69,7 @@
     , n.asString());
   }
 
+  @Test
   public void testPreformat4() {
     final SafeHtml o = html("  Q\n    <R>\n  S\n\nB");
     final SafeHtml n = o.wikify();
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java
new file mode 100644
index 0000000..d6fba26
--- /dev/null
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyQuoteTest.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "<p>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.gwtexpui.safehtml.client;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+
+public class SafeHtml_WikifyQuoteTest {
+  private static final String B = "<blockquote class=\"wikiQuote\">";
+  private static final String E = "</blockquote>";
+
+  private static String quote(String raw) {
+    return B + raw + E;
+  }
+
+  @Test
+  public void testQuote1() {
+    final SafeHtml o = html("> I'm happy\n > with quotes!\n\nSee above.");
+    final SafeHtml n = o.wikify();
+    assertNotSame(o, n);
+    assertEquals(
+        quote("I&#39;m happy\nwith quotes!")
+        + "<p>See above.</p>",
+      n.asString());
+  }
+
+  @Test
+  public void testQuote2() {
+    final SafeHtml o = html("See this said:\n\n > a quoted\n > string block\n\nOK?");
+    final SafeHtml n = o.wikify();
+    assertNotSame(o, n);
+    assertEquals(
+        "<p>See this said:</p>"
+        + quote("a quoted\nstring block")
+        + "<p>OK?</p>",
+      n.asString());
+  }
+
+  @Test
+  public void testNestedQuotes1() {
+    final SafeHtml o = html(" > > prior\n > \n > next\n");
+    final SafeHtml n = o.wikify();
+    assertEquals(
+      quote(quote("prior") + "next\n"),
+      n.asString());
+  }
+
+  private static SafeHtml html(String text) {
+    return new SafeHtmlBuilder().append(text).toSafeHtml();
+  }
+}
diff --git a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java
index c9837037..00b29de 100644
--- a/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java
+++ b/gerrit-gwtexpui/src/test/java/com/google/gwtexpui/safehtml/client/SafeHtml_WikifyTest.java
@@ -14,9 +14,13 @@
 
 package com.google.gwtexpui.safehtml.client;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
-public class SafeHtml_WikifyTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+
+public class SafeHtml_WikifyTest {
+  @Test
   public void testWikify_OneLine1() {
     final SafeHtml o = html("A  B");
     final SafeHtml n = o.wikify();
@@ -24,6 +28,7 @@
     assertEquals("<p>A  B</p>", n.asString());
   }
 
+  @Test
   public void testWikify_OneLine2() {
     final SafeHtml o = html("A  B\n");
     final SafeHtml n = o.wikify();
@@ -31,6 +36,7 @@
     assertEquals("<p>A  B\n</p>", n.asString());
   }
 
+  @Test
   public void testWikify_OneParagraph1() {
     final SafeHtml o = html("A\nB");
     final SafeHtml n = o.wikify();
@@ -38,6 +44,7 @@
     assertEquals("<p>A\nB</p>", n.asString());
   }
 
+  @Test
   public void testWikify_OneParagraph2() {
     final SafeHtml o = html("A\nB\n");
     final SafeHtml n = o.wikify();
@@ -45,6 +52,7 @@
     assertEquals("<p>A\nB\n</p>", n.asString());
   }
 
+  @Test
   public void testWikify_TwoParagraphs() {
     final SafeHtml o = html("A\nB\n\nC\nD");
     final SafeHtml n = o.wikify();
@@ -52,6 +60,7 @@
     assertEquals("<p>A\nB</p><p>C\nD</p>", n.asString());
   }
 
+  @Test
   public void testLinkify_SimpleHttp1() {
     final SafeHtml o = html("A http://go.here/ B");
     final SafeHtml n = o.wikify();
@@ -59,6 +68,7 @@
     assertEquals("<p>A <a href=\"http://go.here/\" target=\"_blank\">http://go.here/</a> B</p>", n.asString());
   }
 
+  @Test
   public void testLinkify_SimpleHttps2() {
     final SafeHtml o = html("A https://go.here/ B");
     final SafeHtml n = o.wikify();
@@ -66,6 +76,7 @@
     assertEquals("<p>A <a href=\"https://go.here/\" target=\"_blank\">https://go.here/</a> B</p>", n.asString());
   }
 
+  @Test
   public void testLinkify_Parens1() {
     final SafeHtml o = html("A (http://go.here/) B");
     final SafeHtml n = o.wikify();
@@ -73,6 +84,7 @@
     assertEquals("<p>A (<a href=\"http://go.here/\" target=\"_blank\">http://go.here/</a>) B</p>", n.asString());
   }
 
+  @Test
   public void testLinkify_Parens() {
     final SafeHtml o = html("A http://go.here/#m() B");
     final SafeHtml n = o.wikify();
@@ -80,6 +92,7 @@
     assertEquals("<p>A <a href=\"http://go.here/#m()\" target=\"_blank\">http://go.here/#m()</a> B</p>", n.asString());
   }
 
+  @Test
   public void testLinkify_AngleBrackets1() {
     final SafeHtml o = html("A <http://go.here/> B");
     final SafeHtml n = o.wikify();
diff --git a/gerrit-gwtui-common/BUCK b/gerrit-gwtui-common/BUCK
new file mode 100644
index 0000000..76f2db8
--- /dev/null
+++ b/gerrit-gwtui-common/BUCK
@@ -0,0 +1,30 @@
+SRC = 'src/main/java/com/google/gerrit/'
+
+gwt_module(
+  name = 'client',
+  srcs = glob([SRC + 'client/**/*.java']),
+  gwtxml = SRC + 'GerritGwtUICommon.gwt.xml',
+  compile_deps = ['//lib/gwt:user'],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'client-lib',
+  exported_deps = [':client-lib2'],
+  visibility = ['PUBLIC'],
+)
+
+java_library2(
+  name = 'client-lib2',
+  srcs = glob(['src/main/**/*.java']),
+  resources = glob(['src/main/**/*']),
+  compile_deps = ['//lib/gwt:user'],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'client-src-lib',
+  srcs = [],
+  resources = glob(['src/main/**/*']),
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/GerritGwtUICommon.gwt.xml b/gerrit-gwtui-common/src/main/java/com/google/gerrit/GerritGwtUICommon.gwt.xml
new file mode 100644
index 0000000..eb551c4
--- /dev/null
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/GerritGwtUICommon.gwt.xml
@@ -0,0 +1,18 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the 'License');
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an 'AS IS' BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<module>
+  <source path='client' />
+</module>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/NativeMap.java
similarity index 100%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeMap.java
rename to gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/NativeMap.java
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeString.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/NativeString.java
similarity index 84%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeString.java
rename to gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/NativeString.java
index be4cfd6..0e16dc0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/NativeString.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/NativeString.java
@@ -19,10 +19,16 @@
 
 /** Wraps a String that was returned from a JSON API. */
 public final class NativeString extends JavaScriptObject {
-  private static final JavaScriptObject TYPE = init();
+  public static final JavaScriptObject TYPE = init();
 
-  private static final native JavaScriptObject init()
-  /*-{ return function(s){this.s=s} }-*/;
+  // Used from core and plugins
+  private static final native JavaScriptObject init() /*-{
+    if ($wnd.Gerrit === undefined || $wnd.Gerrit.JsonString === undefined) {
+      return function(s){this.s=s};
+    } else {
+      return $wnd.Gerrit.JsonString;
+    }
+  }-*/;
 
   static final NativeString wrap(String s) {
     return wrap0(TYPE, s);
@@ -32,7 +38,6 @@
   /*-{ return new T(s) }-*/;
 
   public final native String asString() /*-{ return this.s; }-*/;
-  private final native void set(String v) /*-{ this.s = v; }-*/;
 
   public static final AsyncCallback<NativeString>
   unwrap(final AsyncCallback<String> cb) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/Natives.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/Natives.java
similarity index 83%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/Natives.java
rename to gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/Natives.java
index 41f0f23..564e95a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/Natives.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/Natives.java
@@ -16,6 +16,7 @@
 
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.json.client.JSONObject;
 
 import java.util.AbstractList;
@@ -35,6 +36,30 @@
     return Collections.emptySet();
   }
 
+  public static List<String> asList(final JsArrayString arr) {
+    if (arr == null) {
+      return null;
+    }
+    return new AbstractList<String>() {
+      @Override
+      public String set(int index, String element) {
+        String old = arr.get(index);
+        arr.set(index, element);
+        return old;
+      }
+
+      @Override
+      public String get(int index) {
+        return arr.get(index);
+      }
+
+      @Override
+      public int size() {
+        return arr.length();
+      }
+    };
+  }
+
   public static <T extends JavaScriptObject> List<T> asList(
       final JsArray<T> arr) {
     if (arr == null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/TransformCallback.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/TransformCallback.java
similarity index 100%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/TransformCallback.java
rename to gerrit-gwtui-common/src/main/java/com/google/gerrit/client/rpc/TransformCallback.java
diff --git a/gerrit-gwtui/.gitignore b/gerrit-gwtui/.gitignore
deleted file mode 100644
index 53d46b3..0000000
--- a/gerrit-gwtui/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-gwtui.iml
\ No newline at end of file
diff --git a/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs b/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index f9fe345..0000000
--- a/gerrit-gwtui/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-gwtui/.settings/org.eclipse.core.runtime.prefs b/gerrit-gwtui/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-gwtui/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-gwtui/.settings/org.eclipse.jdt.core.prefs b/gerrit-gwtui/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 470942d..0000000
--- a/gerrit-gwtui/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-gwtui/.settings/org.eclipse.jdt.ui.prefs b/gerrit-gwtui/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-gwtui/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index 6969bad..e978323 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -1,4 +1,5 @@
 include_defs('//gerrit-gwtui/gwt.defs')
+include_defs('//tools/gwt-constants.defs')
 
 genrule(
   name = 'ui_optdbg',
@@ -25,13 +26,7 @@
 gwt_application(
   name = 'ui_opt',
   module_target = MODULE,
-  compiler_opts = [
-    '-strict',
-    '-style', 'OBF',
-    '-optimize', '9',
-    '-XdisableClassMetadata',
-    '-XdisableCastChecking',
-  ],
+  compiler_opts = GWT_COMPILER_OPTS,
   deps = APP_DEPS + [':ui_dbg'],
 )
 
@@ -61,19 +56,21 @@
   gwtxml = 'src/main/java/%s.gwt.xml' % MODULE.replace('.', '/'),
   resources = glob(['src/main/java/**/*'], excludes = DIFFY),
   deps = [
-    ':diffy_logo',
     '//gerrit-gwtexpui:Clippy',
-    '//gerrit-gwtexpui:CSS',
     '//gerrit-gwtexpui:GlobalKey',
-    '//gerrit-gwtexpui:Linker',
     '//gerrit-gwtexpui:Progress',
     '//gerrit-gwtexpui:SafeHtml',
     '//gerrit-gwtexpui:UserAgent',
+    '//gerrit-gwtui-common:client',
     '//gerrit-common:client',
     '//gerrit-extension-api:client',
     '//gerrit-patch-jgit:client',
     '//gerrit-prettify:client',
     '//gerrit-reviewdb:client',
+  ],
+  compile_deps = [
+    ':diffy_logo',
+    '//gerrit-gwtexpui:CSS',
     '//lib:gwtjsonrpc',
     '//lib:gwtjsonrpc_src',
     '//lib:gwtorm',
@@ -116,16 +113,16 @@
     'src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml',
   ],
   deps = [
-    ':ui_module',
-    '//gerrit-common:client',
-    '//gerrit-extension-api:client',
+    ':ui_module_lib',
+    '//gerrit-common:client_lib',
+    '//gerrit-extension-api:client_lib',
     '//lib:junit',
     '//lib/gwt:dev',
     '//lib/gwt:user',
     '//lib/gwt:gwt-test-utils',
     '//lib/jgit:jgit',
   ],
-  source_under_test = [':ui_module'],
+  source_under_test = [':ui_module_lib'],
   vm_args = ['-Xmx512m'],
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
index 9fe04bc..b8102ec 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml
@@ -21,12 +21,12 @@
   <inherits name='com.google.gwtexpui.clippy.Clippy'/>
   <inherits name='com.google.gwtexpui.css.CSS'/>
   <inherits name='com.google.gwtexpui.globalkey.GlobalKey'/>
-  <inherits name='com.google.gwtexpui.linker.ServerPlannedIFrameLinker'/>
   <inherits name='com.google.gwtexpui.progress.Progress'/>
   <inherits name='com.google.gwtexpui.safehtml.SafeHtml'/>
   <inherits name='com.google.gerrit.extensions.Extensions'/>
   <inherits name='com.google.gerrit.prettify.PrettyFormatter'/>
   <inherits name='com.google.gerrit.Common'/>
+  <inherits name='com.google.gerrit.GerritGwtUICommon'/>
   <inherits name='com.google.gerrit.UserAgent'/>
   <inherits name='org.eclipse.jgit.JGit'/>
   <inherits name='net.codemirror.CodeMirror'/>
@@ -35,6 +35,8 @@
   <set-property-fallback name='locale' value='en'/>
   <set-property name='locale' value='en'/>
   <set-configuration-property name='UiBinder.useSafeHtmlTemplates' value='true'/>
+  <set-configuration-property name='CssResource.style' value='stable'/>
+  <add-linker name='xsiframe'/>
 
   <set-property name='gwt.logging.logLevel' value='SEVERE'/>
   <set-property name='gwt.logging.popupHandler' value='DISABLED'/>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/UserAgent.gwt.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/UserAgent.gwt.xml
index 9dde95c..e6f3acb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/UserAgent.gwt.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/UserAgent.gwt.xml
@@ -35,6 +35,7 @@
       <when-property-is name="user.agent" value="ie6"/>
       <when-property-is name="user.agent" value="ie8"/>
       <when-property-is name="user.agent" value="ie9"/>
+      <when-property-is name="user.agent" value="ie10"/>
     </any>
   </replace-with>
 </module>
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 0945ee9..1b89a5a 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
@@ -22,6 +22,7 @@
 import static com.google.gerrit.common.PageLinks.DASHBOARDS;
 import static com.google.gerrit.common.PageLinks.MINE;
 import static com.google.gerrit.common.PageLinks.PROJECTS;
+import static com.google.gerrit.common.PageLinks.QUERY;
 import static com.google.gerrit.common.PageLinks.REGISTER;
 import static com.google.gerrit.common.PageLinks.SETTINGS;
 import static com.google.gerrit.common.PageLinks.SETTINGS_AGREEMENTS;
@@ -34,6 +35,7 @@
 import static com.google.gerrit.common.PageLinks.SETTINGS_SSHKEYS;
 import static com.google.gerrit.common.PageLinks.SETTINGS_WEBIDENT;
 import static com.google.gerrit.common.PageLinks.op;
+import static com.google.gerrit.common.PageLinks.toChangeQuery;
 
 import com.google.gerrit.client.account.MyAgreementsScreen;
 import com.google.gerrit.client.account.MyContactInformationScreen;
@@ -60,6 +62,7 @@
 import com.google.gerrit.client.admin.ProjectInfoScreen;
 import com.google.gerrit.client.admin.ProjectListScreen;
 import com.google.gerrit.client.admin.ProjectScreen;
+import com.google.gerrit.client.api.ExtensionScreen;
 import com.google.gerrit.client.change.ChangeScreen2;
 import com.google.gerrit.client.changes.AccountDashboardScreen;
 import com.google.gerrit.client.changes.ChangeScreen;
@@ -70,7 +73,9 @@
 import com.google.gerrit.client.changes.QueryScreen;
 import com.google.gerrit.client.dashboards.DashboardInfo;
 import com.google.gerrit.client.dashboards.DashboardList;
+import com.google.gerrit.client.diff.DisplaySide;
 import com.google.gerrit.client.diff.SideBySide2;
+import com.google.gerrit.client.documentation.DocScreen;
 import com.google.gerrit.client.groups.GroupApi;
 import com.google.gerrit.client.groups.GroupInfo;
 import com.google.gerrit.client.patches.PatchScreen;
@@ -80,8 +85,8 @@
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.PatchSetDetail;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
@@ -90,10 +95,12 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.RunAsyncCallback;
 import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.Cookies;
 import com.google.gwt.user.client.Window;
 import com.google.gwtorm.client.KeyUtil;
 
 public class Dispatcher {
+  public static final String COOKIE_CS2 = "gerrit_cs2";
   public static boolean changeScreen2;
 
   public static String toPatchSideBySide(final Patch.Key id) {
@@ -104,9 +111,19 @@
     return toPatch("", diffBase, id);
   }
 
-  public static String toPatchSideBySide2(PatchSet.Id diffBase,
+  public static String toSideBySide(PatchSet.Id diffBase,
       PatchSet.Id revision, String fileName) {
-    return toPatch("cm", diffBase, revision, fileName);
+    return toPatch("", diffBase, revision, fileName, null, 0);
+  }
+
+  public static String toSideBySide(PatchSet.Id diffBase,
+      PatchSet.Id revision, String fileName, DisplaySide side, int line) {
+    return toPatch("", diffBase, revision, fileName, side, line);
+  }
+
+  public static String toUnified(PatchSet.Id diffBase,
+      PatchSet.Id revision, String fileName) {
+    return toPatch("unified", diffBase, revision, fileName, null, 0);
   }
 
   public static String toPatchUnified(final Patch.Key id) {
@@ -118,11 +135,11 @@
   }
 
   private static String toPatch(String type, PatchSet.Id diffBase, Patch.Key id) {
-    return toPatch(type, diffBase, id.getParentKey(), id.get());
+    return toPatch(type, diffBase, id.getParentKey(), id.get(), null, 0);
   }
 
   private static String toPatch(String type, PatchSet.Id diffBase,
-      PatchSet.Id revision, String fileName) {
+      PatchSet.Id revision, String fileName, DisplaySide side, int line) {
     Change.Id c = revision.getParentKey();
     StringBuilder p = new StringBuilder();
     p.append("/c/").append(c).append("/");
@@ -133,6 +150,11 @@
     if (type != null && !type.isEmpty()) {
       p.append(",").append(type);
     }
+    if (side == DisplaySide.A && line > 0) {
+      p.append("@a").append(line);
+    } else if (line > 0) {
+      p.append("@").append(line);
+    }
     return p.toString();
   }
 
@@ -150,11 +172,11 @@
   }
 
   public static String toGroup(final AccountGroup.Id id) {
-    return "/admin/groups/" + id.toString();
+    return ADMIN_GROUPS + id.toString();
   }
 
   public static String toGroup(AccountGroup.Id id, String panel) {
-    return "/admin/groups/" + id.toString() + "," + panel;
+    return ADMIN_GROUPS + id.toString() + "," + panel;
   }
 
   public static String toGroup(AccountGroup.UUID uuid) {
@@ -171,9 +193,9 @@
 
   public static String toProjectAdmin(Project.NameKey n, String panel) {
     if (panel == null || ProjectScreen.INFO.equals(panel)) {
-      return "/admin/projects/" + n.toString();
+      return ADMIN_PROJECTS + n.toString();
     }
-    return "/admin/projects/" + n.toString() + "," + panel;
+    return ADMIN_PROJECTS + n.toString() + "," + panel;
   }
 
   static final String RELOAD_UI = "/reload-ui/";
@@ -198,12 +220,18 @@
   }
 
   private static void select(final String token) {
-    if (matchPrefix("/q/", token)) {
+    if (matchPrefix(QUERY, token)) {
       query(token);
 
+    } else if (matchPrefix("/Documentation/", token)) {
+      docSearch(token);
+
     } else if (matchPrefix("/c/", token)) {
       change(token);
 
+    } else if (matchPrefix("/x/", token)) {
+      extension(token);
+
     } else if (matchExact(MINE, token)) {
       Gerrit.display(token, mine(token));
 
@@ -265,19 +293,19 @@
     }
 
     if (matchExact("mine,starred", token)) {
-      return PageLinks.toChangeQuery("is:starred");
+      return toChangeQuery("is:starred");
     }
 
     if (matchExact("mine,drafts", token)) {
-      return PageLinks.toChangeQuery("is:draft");
+      return toChangeQuery("is:draft");
     }
 
     if (matchExact("mine,comments", token)) {
-      return PageLinks.toChangeQuery("has:draft");
+      return toChangeQuery("has:draft");
     }
 
     if (matchPrefix("mine,watched,", token)) {
-      return PageLinks.toChangeQuery("is:watched status:open", skip(token));
+      return toChangeQuery("is:watched status:open");
     }
 
     return null;
@@ -285,15 +313,15 @@
 
   private static String legacyAll(final String token) {
     if (matchPrefix("all,abandoned,", token)) {
-      return PageLinks.toChangeQuery("status:abandoned", skip(token));
+      return toChangeQuery("status:abandoned");
     }
 
     if (matchPrefix("all,merged,", token)) {
-      return PageLinks.toChangeQuery("status:merged", skip(token));
+      return toChangeQuery("status:merged");
     }
 
     if (matchPrefix("all,open,", token)) {
-      return PageLinks.toChangeQuery("status:open", skip(token));
+      return toChangeQuery("status:open");
     }
 
     return null;
@@ -304,27 +332,21 @@
       final String s = skip(token);
       final int c = s.indexOf(',');
       Project.NameKey proj = Project.NameKey.parse(s.substring(0, c));
-      return PageLinks.toChangeQuery( //
-          "status:open " + op("project", proj.get()), //
-          s.substring(c + 1));
+      return toChangeQuery("status:open " + op("project", proj.get()));
     }
 
     if (matchPrefix("project,merged,", token)) {
       final String s = skip(token);
       final int c = s.indexOf(',');
       Project.NameKey proj = Project.NameKey.parse(s.substring(0, c));
-      return PageLinks.toChangeQuery( //
-          "status:merged " + op("project", proj.get()), //
-          s.substring(c + 1));
+      return toChangeQuery("status:merged " + op("project", proj.get()));
     }
 
     if (matchPrefix("project,abandoned,", token)) {
       final String s = skip(token);
       final int c = s.indexOf(',');
       Project.NameKey proj = Project.NameKey.parse(s.substring(0, c));
-      return PageLinks.toChangeQuery( //
-          "status:abandoned " + op("project", proj.get()), //
-          s.substring(c + 1));
+      return toChangeQuery("status:abandoned " + op("project", proj.get()));
     }
 
     return null;
@@ -353,7 +375,7 @@
 
   private static String legacyAdmin(String token) {
     if (matchPrefix("admin,group,", token)) {
-      return "/admin/groups/" + skip(token);
+      return ADMIN_GROUPS + skip(token);
     }
 
     if (matchPrefix("admin,project,", token)) {
@@ -382,10 +404,22 @@
     return null;
   }
 
-  private static void query(final String token) {
-    final String s = skip(token);
-    final int c = s.indexOf(',');
-    Gerrit.display(token, new QueryScreen(s.substring(0, c), s.substring(c + 1)));
+  private static void query(String token) {
+    String s = skip(token);
+    int c = s.indexOf(',');
+    Screen screen;
+    if (c >= 0) {
+      String prefix = s.substring(0, c);
+      if (s.substring(c).equals(",n,z")) {
+        // Respect legacy token with max sortkey.
+        screen = new QueryScreen(prefix, 0);
+      } else {
+        screen = new QueryScreen(prefix, Integer.parseInt(s.substring(c + 1)));
+      }
+    } else {
+      screen = new QueryScreen(s, 0);
+    }
+    Gerrit.display(token, screen);
   }
 
   private static Screen mine(final String token) {
@@ -436,7 +470,7 @@
           @Override
           public void onFailure(Throwable caught) {
             if ("default".equals(dashboardId) && RestApi.isNotFound(caught)) {
-              Gerrit.display(PageLinks.toChangeQuery(
+              Gerrit.display(toChangeQuery(
                   PageLinks.projectQuery(new Project.NameKey(project))));
             } else {
               super.onFailure(caught);
@@ -510,8 +544,21 @@
     }
 
     if (!rest.isEmpty()) {
+      DisplaySide side = DisplaySide.B;
+      int line = 0;
+      int at = rest.lastIndexOf('@');
+      if (at > 0) {
+        String l = rest.substring(at+1);
+        if (l.startsWith("a")) {
+          side = DisplaySide.A;
+          l = l.substring(1);
+        }
+        line = Integer.parseInt(l);
+        rest = rest.substring(0, at);
+      }
       Patch.Key p = new Patch.Key(ps, KeyUtil.decode(rest));
-      patch(token, base, p, 0, null, null, panel);
+      patch(token, base, p, side, line, 0,
+          null, null, null, panel);
     } else {
       if (panel == null) {
         Gerrit.display(token, isChangeScreen2()
@@ -529,8 +576,19 @@
     }
   }
 
+  private static void extension(final String token) {
+    ExtensionScreen view = new ExtensionScreen(skip(token));
+    if (view.isFound()) {
+      Gerrit.display(token, view);
+    } else {
+      Gerrit.display(token, new NotFoundScreen());
+    }
+  }
+
   public static boolean isChangeScreen2() {
-    if (changeScreen2) {
+    if (!Gerrit.getConfig().getNewFeatures()) {
+      return false;
+    } else if (changeScreen2) {
       return true;
     }
 
@@ -540,6 +598,11 @@
           .getGeneralPreferences()
           .getChangeScreen();
     }
+    String v = Cookies.getCookie(Dispatcher.COOKIE_CS2);
+    if (v != null) {
+      changeScreen2 = "1".equals(v);
+      return changeScreen2;
+    }
     if (ui == null) {
       ui = Gerrit.getConfig().getChangeScreen();
     }
@@ -562,17 +625,12 @@
   public static void patch(String token, PatchSet.Id base, Patch.Key id,
       int patchIndex, PatchSetDetail patchSetDetail,
       PatchTable patchTable, PatchScreen.TopView topView) {
-    patch(token, base, id, patchIndex, patchSetDetail, patchTable, topView, null);
-  }
-
-  public static void patch(String token, PatchSet.Id base, Patch.Key id,
-      int patchIndex, PatchSetDetail patchSetDetail,
-      PatchTable patchTable, String panelType) {
-    patch(token, base, id, patchIndex, patchSetDetail, patchTable,
-        null, panelType);
+    patch(token, base, id, null, 0, patchIndex,
+        patchSetDetail, patchTable, topView, null);
   }
 
   public static void patch(String token, final PatchSet.Id baseId, final Patch.Key id,
+      final DisplaySide side, final int line,
       final int patchIndex, final PatchSetDetail patchSetDetail,
       final PatchTable patchTable, final PatchScreen.TopView topView,
       final String panelType) {
@@ -594,7 +652,8 @@
 
           if ("".equals(panel)) {
             if (isChangeScreen2()) {
-              return new SideBySide2(baseId, id.getParentKey(), id.get());
+              return new SideBySide2(baseId, id.getParentKey(), id.get(),
+                  side, line);
             }
             return new PatchScreen.SideBySide( //
                 id, //
@@ -613,7 +672,8 @@
                 top, //
                 baseId //
             );
-          } else if ("cm".equals(panel)) {
+          } else if (("cm".equals(panel) && Gerrit.getConfig().getNewFeatures())
+              || ("".equals(panel) && isChangeScreen2())) {
             if (Gerrit.isSignedIn()
                 && DiffView.UNIFIED_DIFF.equals(Gerrit.getUserAccount()
                     .getGeneralPreferences().getDiffView())) {
@@ -626,7 +686,16 @@
                   baseId //
               );
             }
-            return new SideBySide2(baseId, id.getParentKey(), id.get());
+            return new SideBySide2(baseId, id.getParentKey(), id.get(),
+                side, line);
+          } else if ("".equals(panel) || "sidebyside".equals(panel)) {
+            return new PatchScreen.SideBySide(//
+                id, //
+                patchIndex,//
+                patchSetDetail,//
+                patchTable,//
+                top,//
+                baseId);//
           }
         }
 
@@ -709,7 +778,7 @@
             || matchExact("/admin/groups", token)) {
           Gerrit.display(token, new GroupListScreen());
 
-        } else if (matchPrefix("/admin/groups/", token)) {
+        } else if (matchPrefix(ADMIN_GROUPS, token)) {
           String rest = skip(token);
           if (rest.startsWith("?")) {
             Gerrit.display(token, new GroupListScreen(rest.substring(1)));
@@ -727,7 +796,7 @@
             || matchExact("/admin/projects", token)) {
           Gerrit.display(token, new ProjectListScreen());
 
-        } else if (matchPrefix("/admin/projects/", token)) {
+        } else if (matchPrefix(ADMIN_PROJECTS, token)) {
             String rest = skip(token);
             if (rest.startsWith("?")) {
               Gerrit.display(token, new ProjectListScreen(rest.substring(1)));
@@ -772,7 +841,7 @@
             group = p.substring(0, c);
             panel = p.substring(c + 1);
           }
-        } else if (matchPrefix("/admin/groups/", token)) {
+        } else if (matchPrefix(ADMIN_GROUPS, token)) {
           String p = skip(token);
           int c = p.indexOf(',');
           if (c < 0) {
@@ -797,8 +866,7 @@
               // for external and system groups the members cannot be
               // shown in the web UI).
               //
-              if (AccountGroup.isInternalGroup(group.getGroupUUID())
-                  && !AccountGroup.isSystemGroup(group.getGroupUUID())) {
+              if (AccountGroup.isInternalGroup(group.getGroupUUID())) {
                 Gerrit.display(toGroup(group.getGroupId(), AccountGroupScreen.MEMBERS),
                     new AccountGroupMembersScreen(group, token));
               } else {
@@ -817,7 +885,7 @@
       }
 
       private Screen selectProject() {
-        if (matchPrefix("/admin/projects/", token)) {
+        if (matchPrefix(ADMIN_PROJECTS, token)) {
           String rest = skip(token);
           int c = rest.lastIndexOf(',');
           if (c < 0) {
@@ -891,4 +959,12 @@
       }
     }
   }
+
+  private static void docSearch(final String token) {
+    GWT.runAsync(new AsyncSplit(token) {
+      public void onSuccess() {
+        Gerrit.display(token, new DocScreen(skip(token)));
+      }
+    });
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
index 2e94db7..4b6b092 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
@@ -129,10 +129,10 @@
    * <p>
    * Example output:
    * <ul>
-   * <li><code>A U. Thor &lt;author@example.com&gt;</code>: full populated</li>
-   * <li><code>A U. Thor (12)</code>: missing email address</li>
-   * <li><code>Anonymous Coward &lt;author@example.com&gt;</code>: missing name</li>
-   * <li><code>Anonymous Coward (12)</code>: missing name and email address</li>
+   * <li>{@code A U. Thor &lt;author@example.com&gt;}: full populated</li>
+   * <li>{@code A U. Thor (12)}: missing email address</li>
+   * <li>{@code Anonymous Coward &lt;author@example.com&gt;}: missing name</li>
+   * <li>{@code Anonymous Coward (12)}: missing name and email address</li>
    * </ul>
    */
   public static String nameEmail(AccountInfo info) {
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 568a15c..cd2cccd 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
@@ -22,6 +22,7 @@
 import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.admin.ProjectScreen;
 import com.google.gerrit.client.api.ApiGlue;
+import com.google.gerrit.client.api.PluginLoader;
 import com.google.gerrit.client.changes.ChangeConstants;
 import com.google.gerrit.client.changes.ChangeListScreen;
 import com.google.gerrit.client.config.ConfigServerApi;
@@ -49,12 +50,8 @@
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.aria.client.Roles;
-import com.google.gwt.core.client.Callback;
-import com.google.gwt.core.client.CodeDownloadException;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.ScriptInjector;
 import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -84,15 +81,11 @@
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.user.client.UserAgent;
 import com.google.gwtexpui.user.client.ViewSite;
-import com.google.gwtjsonrpc.client.CallbackHandle;
 import com.google.gwtjsonrpc.client.JsonDefTarget;
 import com.google.gwtjsonrpc.client.JsonUtil;
 import com.google.gwtjsonrpc.client.XsrfManager;
-import com.google.gwtjsonrpc.client.impl.ResultDeserializer;
-import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.gwtorm.client.KeyUtil;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -283,7 +276,7 @@
 
   public static GitwebLink getGitwebLink() {
     GitwebConfig gw = getConfig().getGitwebLink();
-    return gw != null ? new GitwebLink(gw) : null;
+    return gw != null && gw.type != null ? new GitwebLink(gw) : null;
   }
 
   /** Site theme information (site specific colors)/ */
@@ -446,38 +439,8 @@
     }
   }
 
-  private static ArrayList<JavaScriptObject> historyHooks;
-  private static Anchor signInAnchor;
-
-  private static native void initHistoryHooks()
-  /*-{ $wnd['gerrit_addHistoryHook'] = function(h) { @com.google.gerrit.client.Gerrit::addHistoryHook(Lcom/google/gwt/core/client/JavaScriptObject;)(h); }; }-*/;
-
-  static void addHistoryHook(final JavaScriptObject hook) {
-    if (historyHooks == null) {
-      historyHooks = new ArrayList<JavaScriptObject>();
-      History.addValueChangeHandler(new ValueChangeHandler<String>() {
-        @Override
-        public void onValueChange(ValueChangeEvent<String> event) {
-          dispatchHistoryHooks(event.getValue());
-        }
-      });
-    }
-    historyHooks.add(hook);
-  }
-
-  private static native void callHistoryHook(JavaScriptObject hook, String url)
-  /*-{ hook(url); }-*/;
-
-  private static void dispatchHistoryHooks(final String historyToken) {
-    if (signInAnchor != null) {
-      signInAnchor.setHref(loginRedirect(historyToken));
-    }
-    if (historyHooks != null) {
-      final String url = Location.getPath() + "#" + historyToken;
-      for (final JavaScriptObject hook : historyHooks) {
-        callHistoryHook(hook, url);
-      }
-    }
+  private static void dispatchHistoryHooks(String token) {
+    ApiGlue.fireEvent("history", token);
   }
 
   private static void populateBottomMenu(RootPanel btmmenu, HostPageData hpd) {
@@ -489,15 +452,15 @@
     btmmenu.add(new InlineLabel(C.keyHelp()));
     btmmenu.add(new InlineLabel(" | "));
     btmmenu.add(new InlineHTML(M.poweredBy(vs)));
-    if (getConfig().getReportBugUrl() != null) {
-      Anchor a = new Anchor(
-          C.reportBug(),
-          getConfig().getReportBugUrl());
-      a.setTarget("_blank");
-      a.setStyleName("");
-      btmmenu.add(new InlineLabel(" | "));
-      btmmenu.add(a);
-    }
+
+    final String reportBugText = getConfig().getReportBugText();
+    Anchor a = new Anchor(
+        reportBugText == null ? C.reportBug() : reportBugText,
+        getConfig().getReportBugUrl());
+    a.setTarget("_blank");
+    a.setStyleName("");
+    btmmenu.add(new InlineLabel(" | "));
+    btmmenu.add(a);
   }
 
   private void onModuleLoad2(HostPageData hpd) {
@@ -537,12 +500,9 @@
     body = new ViewSite<Screen>() {
       @Override
       protected void onShowView(Screen view) {
-        lastViewToken = History.getToken();
         String token = view.getToken();
-        if (!token.equals(lastViewToken)) {
-          History.newItem(token, false);
-          dispatchHistoryHooks(token);
-        }
+        History.newItem(token, false);
+        dispatchHistoryHooks(token);
 
         if (view instanceof ChangeListScreen) {
           lastChangeListToken = token;
@@ -550,6 +510,7 @@
 
         super.onShowView(view);
         view.onShowView();
+        lastViewToken = token;
       }
     };
     gBody.add(body);
@@ -572,15 +533,15 @@
     gStarting.getElement().getParentElement().removeChild(
         gStarting.getElement());
     RootPanel.detachNow(gStarting);
+    ApiGlue.init();
 
     applyUserPreferences();
-    initHistoryHooks();
     populateBottomMenu(bottomMenu, hpd);
     refreshMenuBar();
 
     History.addValueChangeHandler(new ValueChangeHandler<String>() {
       @Override
-      public void onValueChange(final ValueChangeEvent<String> event) {
+      public void onValueChange(ValueChangeEvent<String> event) {
         display(event.getValue());
       }
     });
@@ -592,12 +553,12 @@
           ? PageLinks.MINE
           : PageLinks.toChangeQuery("status:open");
     }
-    if (signInAnchor != null) {
-      signInAnchor.setHref(loginRedirect(token));
-    }
 
     saveDefaultTheme();
-    loadPlugins(hpd, token);
+    if (hpd.messages != null) {
+      new MessageOfTheDayBar(hpd.messages).show();
+    }
+    PluginLoader.load(hpd.plugins, token);
   }
 
   private void saveDefaultTheme() {
@@ -606,59 +567,11 @@
         Document.get().getElementById("gerrit_footer"));
   }
 
-  private void loadPlugins(HostPageData hpd, final String token) {
-    ApiGlue.init();
-    if (hpd.plugins != null && !hpd.plugins.isEmpty()) {
-      for (final String url : hpd.plugins) {
-        ScriptInjector.fromUrl(url)
-            .setWindow(ScriptInjector.TOP_WINDOW)
-            .setCallback(new Callback<Void, Exception>() {
-              @Override
-              public void onSuccess(Void result) {
-              }
-
-              @Override
-              public void onFailure(Exception reason) {
-                ErrorDialog d;
-                if (reason instanceof CodeDownloadException) {
-                  d = new ErrorDialog(M.cannotDownloadPlugin(url));
-                } else {
-                  d = new ErrorDialog(M.pluginFailed(url));
-                }
-                d.center();
-              }
-            }).inject();
-      }
-    }
-
-    CallbackHandle<Void> cb = new CallbackHandle<Void>(
-        new ResultDeserializer<Void>() {
-          @Override
-          public Void fromResult(JavaScriptObject responseObject) {
-            return null;
-          }
-        },
-        new AsyncCallback<Void>() {
-          @Override
-          public void onFailure(Throwable caught) {
-          }
-
-          @Override
-          public void onSuccess(Void result) {
-            display(token);
-          }
-        });
-    cb.install();
-    ScriptInjector.fromString(cb.getFunctionName() + "();")
-        .setWindow(ScriptInjector.TOP_WINDOW)
-        .inject();
-  }
-
   public static void refreshMenuBar() {
     menuLeft.clear();
     menuRight.clear();
 
-    menuBars = new HashMap<String, LinkMenuBar>();
+    menuBars = new HashMap<>();
 
     final boolean signedIn = isSignedIn();
     final GerritConfig cfg = getConfig();
@@ -749,7 +662,7 @@
     if (getConfig().isDocumentationAvailable()) {
       m = new LinkMenuBar();
       menuBars.put(GerritTopMenu.DOCUMENTATION.menuName, m);
-      addDocLink(m, C.menuDocumentationIndex(), "index.html");
+      addDocLink(m, C.menuDocumentationTOC(), "index.html");
       addDocLink(m, C.menuDocumentationSearch(), "user-search.html");
       addDocLink(m, C.menuDocumentationUpload(), "user-upload.html");
       addDocLink(m, C.menuDocumentationAccess(), "access-control.html");
@@ -975,12 +888,19 @@
     m.add(atag);
   }
 
-  private static void addExtensionLink(final LinkMenuBar m, final TopMenuItem item) {
-    final Anchor atag = anchor(item.getName(), item.getUrl());
+  private static void addExtensionLink(LinkMenuBar m, TopMenuItem item) {
+    Anchor atag = anchor(item.getName(), isAbsolute(item.getUrl())
+        ? item.getUrl()
+        : selfRedirect(item.getUrl()));
+
     atag.setTarget(item.getTarget());
     if (item.getId() != null) {
       atag.getElement().setAttribute("id", item.getId());
     }
     m.add(atag);
   }
+
+  private static boolean isAbsolute(String url) {
+    return url.matches("^https?://.*");
+  }
 }
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 be062bd..2d20b98 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
@@ -20,6 +20,7 @@
   String menuSignIn();
   String menuRegister();
   String reportBug();
+  String loadingPlugins();
 
   String signInDialogTitle();
   String signInDialogClose();
@@ -85,7 +86,7 @@
   String menuPluginsInstalled();
 
   String menuDocumentation();
-  String menuDocumentationIndex();
+  String menuDocumentationTOC();
   String menuDocumentationSearch();
   String menuDocumentationUpload();
   String menuDocumentationAccess();
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 8577c1c..af530f4 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
@@ -1,6 +1,7 @@
 menuSignIn = Sign In
 menuRegister = Register
 reportBug = Report Bug
+loadingPlugins = Loading plugins ...
 
 signInDialogTitle = Code Review - Sign In
 signInDialogClose = Close
@@ -68,13 +69,13 @@
 menuPluginsInstalled = Installed
 
 menuDocumentation = Documentation
-menuDocumentationIndex = Index
+menuDocumentationTOC = Table of Contents
 menuDocumentationSearch = Searching
 menuDocumentationUpload = Uploading
 menuDocumentationAccess = Access Controls
 menuDocumentationAPI = REST API
 
-searchHint = Change #, SHA-1, tr:id or owner:email
+searchHint = Search term
 searchButton = Search
 
 rpcStatusWorking = Working ...
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 9842049..98b9534 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
@@ -41,7 +41,9 @@
   String cAPPROVAL();
   String cLastUpdate();
   String cOWNER();
+  String cSIZE();
   String cSUBJECT();
+  String cSTATUS();
   String cellsNextToFileComment();
   String changeComments();
   String changeInfoBlock();
@@ -49,6 +51,7 @@
   String changeScreen();
   String changeScreenDescription();
   String changeScreenStarIcon();
+  String changeSize();
   String changeTable();
   String changeTablePrevNextLinks();
   String changeTypeCell();
@@ -106,6 +109,7 @@
   String downloadLinkListCell();
   String downloadLink_Active();
   String drafts();
+  String editHeadButton();
   String emptySection();
   String errorDialog();
   String errorDialogButtons();
@@ -113,6 +117,7 @@
   String errorDialogGlass();
   String errorDialogText();
   String errorDialogTitle();
+  String loadingPluginsDialog();
   String fileColumnHeader();
   String fileCommentBorder();
   String fileLine();
@@ -154,7 +159,7 @@
   String link();
   String linkMenuBar();
   String linkMenuItemNotLast();
-  String maxObjectSizeLimitPanel();
+  String maxObjectSizeLimitEffectiveLabel();
   String menuBarUserName();
   String menuBarUserNameAvatar();
   String menuBarUserNameFocusPanel();
@@ -186,6 +191,7 @@
   String patchSetRevision();
   String patchSetUserIdentity();
   String patchSizeCell();
+  String pluginProjectConfigInheritedValue();
   String pluginsTable();
   String posscore();
   String projectActions();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
index cbb5513..21da8ce 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.java
@@ -34,4 +34,6 @@
 
   String pluginFailed(String scriptPath);
   String cannotDownloadPlugin(String scriptPath);
+
+  String parentUpdateFailed(String message);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
index 5ab8b3b..4a97c81 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritMessages.properties
@@ -13,5 +13,7 @@
 branchAlreadyExists = A branch with the name {0} already exists.
 branchCreationConflict = Cannot create branch {0} since it conflicts with branch {1}.
 
-pluginFailed = Plugin JavaScript {0} failed to load
-cannotDownloadPlugin = Cannot download JavaScript plugin from: {0}.
+pluginFailed = Plugin "{0}" failed to load
+cannotDownloadPlugin = Cannot load plugin from {0}
+
+parentUpdateFailed = Setting parent project failed: {0}
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 80d65b0..3319457 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
@@ -57,4 +57,16 @@
 
   @Source("draftComments.png")
   public ImageResource draftComments();
+
+  @Source("readOnly.png")
+  public ImageResource readOnly();
+
+  @Source("gear.png")
+  public ImageResource gear();
+
+  @Source("info.png")
+  public ImageResource info();
+
+  @Source("warning.png")
+  public ImageResource warning();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GitwebLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GitwebLink.java
index 216395b..e32a602 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GitwebLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GitwebLink.java
@@ -56,7 +56,7 @@
 
   public String toRevision(String  project, String commit) {
     ParameterizedString pattern = new ParameterizedString(type.getRevision());
-    Map<String, String> p = new HashMap<String, String>();
+    Map<String, String> p = new HashMap<>();
     p.put("project", encode(project));
     p.put("commit", encode(commit));
     return baseUrl + pattern.replace(p);
@@ -69,7 +69,7 @@
   public String toProject(final Project.NameKey project) {
     ParameterizedString pattern = new ParameterizedString(type.getProject());
 
-    final Map<String, String> p = new HashMap<String, String>();
+    final Map<String, String> p = new HashMap<>();
     p.put("project", encode(project.get()));
     return baseUrl + pattern.replace(p);
   }
@@ -77,16 +77,28 @@
   public String toBranch(final Branch.NameKey branch) {
     ParameterizedString pattern = new ParameterizedString(type.getBranch());
 
-    final Map<String, String> p = new HashMap<String, String>();
+    final Map<String, String> p = new HashMap<>();
     p.put("project", encode(branch.getParentKey().get()));
     p.put("branch", encode(branch.get()));
     return baseUrl + pattern.replace(p);
   }
 
+  public String toFile(String  project, String commit, String file) {
+    Map<String, String> p = new HashMap<>();
+    p.put("project", encode(project));
+    p.put("commit", encode(commit));
+    p.put("file", encode(file));
+
+    ParameterizedString pattern = (file == null || file.isEmpty())
+        ? new ParameterizedString(type.getRootTree())
+        : new ParameterizedString(type.getFile());
+    return baseUrl + pattern.replace(p);
+  }
+
   public String toFileHistory(final Branch.NameKey branch, final String file) {
     ParameterizedString pattern = new ParameterizedString(type.getFileHistory());
 
-    final Map<String, String> p = new HashMap<String, String>();
+    final Map<String, String> p = new HashMap<>();
     p.put("project", encode(branch.getParentKey().get()));
     p.put("branch", encode(branch.get()));
     p.put("file", encode(file));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java
index a41ff02..45d9a91 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java
@@ -16,13 +16,27 @@
 
 import com.google.gerrit.common.PageLinks;
 import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.CompoundKeyCommand;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
 import com.google.gwtexpui.globalkey.client.KeyCommandSet;
 
-class JumpKeys {
+public class JumpKeys {
+  private static HandlerRegistration activeHandler;
+  private static KeyCommandSet keys;
+  private static Widget bodyWidget;
+
+  public static void enable(boolean enable) {
+    if (enable && activeHandler == null) {
+      activeHandler = GlobalKey.add(bodyWidget, keys);
+    } else if (!enable && activeHandler != null) {
+      activeHandler.removeHandler();
+      activeHandler = null;
+    }
+  }
+
   static void register(final Widget body) {
     final KeyCommandSet jumps = new KeyCommandSet();
 
@@ -78,9 +92,10 @@
       });
     }
 
-    final KeyCommandSet jumping = new KeyCommandSet(Gerrit.C.sectionJumping());
-    jumping.add(new CompoundKeyCommand(0, 'g', "", jumps));
-    GlobalKey.add(body, jumping);
+    keys = new KeyCommandSet(Gerrit.C.sectionJumping());
+    keys.add(new CompoundKeyCommand(0, 'g', "", jumps));
+    bodyWidget = body;
+    activeHandler = GlobalKey.add(body, keys);
   }
 
   private JumpKeys() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.java
new file mode 100644
index 0000000..fa569b8
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client;
+
+import com.google.gerrit.common.data.HostPageData;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Cookies;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwtexpui.safehtml.client.SafeHtml;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Displays pending messages from the server. */
+class MessageOfTheDayBar extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, MessageOfTheDayBar> {}
+  private static final Binder uiBinder = GWT.create(Binder.class);
+
+  private final List<HostPageData.Message> motd;
+  @UiField HTML message;
+  @UiField Anchor dismiss;
+
+  MessageOfTheDayBar(List<HostPageData.Message> motd) {
+    this.motd = filter(motd);
+    initWidget(uiBinder.createAndBindUi(this));
+
+    SafeHtmlBuilder b = new SafeHtmlBuilder();
+    if (motd.size() == 1) {
+      b.append(SafeHtml.asis(motd.get(0).html));
+    } else {
+      for (HostPageData.Message m : motd) {
+        b.openDiv();
+        b.append(SafeHtml.asis(m.html));
+        b.closeDiv();
+      }
+    }
+    message.setHTML(b);
+  }
+
+  void show() {
+    if (!motd.isEmpty()) {
+      RootPanel.get().add(this);
+    }
+  }
+
+  @UiHandler("dismiss")
+  void onDismiss(ClickEvent e) {
+    removeFromParent();
+
+    for (HostPageData.Message m : motd) {
+      Cookies.setCookie(cookieName(m), "1", m.redisplay);
+    }
+  }
+
+  private static List<HostPageData.Message> filter(List<HostPageData.Message> in) {
+    List<HostPageData.Message> show = new ArrayList<HostPageData.Message>();
+    for (HostPageData.Message m : in) {
+      if (Cookies.getCookie(cookieName(m)) == null) {
+        show.add(m);
+      }
+    }
+    return show;
+  }
+
+  private static String cookieName(HostPageData.Message m) {
+    return "msg-" + m.id;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.ui.xml
new file mode 100644
index 0000000..ff50ec6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/MessageOfTheDayBar.ui.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:style>
+    .popup {
+      position: fixed;
+      top: 5px;
+      left: 50%;
+      margin-left: -200px;
+      z-index: 201;
+      padding-top: 5px;
+      padding-bottom: 5px;
+      padding-left: 12px;
+      padding-right: 12px;
+      background: #FFF1A8;
+      border-radius: 10px;
+    }
+
+    @if user.agent safari {
+      .popup {
+        \-webkit-border-radius: 10px;
+      }
+    }
+    @if user.agent gecko1_8 {
+      .popup {
+        \-moz-border-radius: 10px;
+      }
+    }
+
+    .message {
+      display: inline;
+    }
+    .message a {
+      color: #222;
+      text-decoration: underline;
+    }
+    a.action {
+      color: #222;
+      text-decoration: underline;
+      display: inline-block;
+      margin-left: 0.5em;
+    }
+  </ui:style>
+  <g:HTMLPanel styleName='{style.popup}'>
+    <g:HTML ui:field='message' styleName='{style.message}'/>
+    <g:Anchor ui:field='dismiss'
+        styleName='{style.action}'
+        href='javascript:;'
+        title='Hide this message'>
+      <ui:attribute name='title'/>
+      Dismiss
+    </g:Anchor>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/RelativeDateFormatter.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/RelativeDateFormatter.java
index 7b6009a..5fc8cb3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/RelativeDateFormatter.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/RelativeDateFormatter.java
@@ -133,12 +133,10 @@
   }
 
   private static long upperLimit(long unit) {
-    long limit = unit + unit / 2;
-    return limit;
+    return unit + unit / 2;
   }
 
   private static long round(long n, long unit) {
-    long rounded = (n + unit / 2) / unit;
-    return rounded;
+    return (n + unit / 2) / unit;
   }
 }
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
index da9f1c6..0715aee 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -22,13 +22,13 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.TreeSet;
 
 public class SearchSuggestOracle extends HighlightSuggestOracle {
   private static final List<ParamSuggester> paramSuggester = Arrays.asList(
-      new ParamSuggester("project:", new ProjectNameSuggestOracle()),
+      new ParamSuggester(Arrays.asList("project:", "parentproject:"),
+          new ProjectNameSuggestOracle()),
       new ParamSuggester(Arrays.asList("owner:", "reviewer:"),
           new AccountSuggestOracle() {
             @Override
@@ -39,8 +39,7 @@
                     final Response response) {
                   if ("self".startsWith(request.getQuery())) {
                     final ArrayList<SuggestOracle.Suggestion> r =
-                        new ArrayList<SuggestOracle.Suggestion>(response
-                            .getSuggestions().size() + 1);
+                        new ArrayList<>(response.getSuggestions().size() + 1);
                     r.addAll(response.getSuggestions());
                     r.add(new SuggestOracle.Suggestion() {
                       @Override
@@ -62,7 +61,7 @@
       new ParamSuggester(Arrays.asList("ownerin:", "reviewerin:"),
           new AccountGroupSuggestOracle()));
 
-  private static final TreeSet<String> suggestions = new TreeSet<String>();
+  private static final TreeSet<String> suggestions = new TreeSet<>();
 
   static {
     suggestions.add("age:");
@@ -80,7 +79,9 @@
 
     suggestions.add("commit:");
     suggestions.add("comment:");
+    suggestions.add("conflicts:");
     suggestions.add("project:");
+    suggestions.add("parentproject:");
     suggestions.add("branch:");
     suggestions.add("topic:");
     suggestions.add("ref:");
@@ -89,7 +90,6 @@
     suggestions.add("label:");
     suggestions.add("message:");
     suggestions.add("file:");
-
     suggestions.add("has:");
     suggestions.add("has:draft");
     suggestions.add("has:star");
@@ -106,6 +106,7 @@
     suggestions.add("is:submitted");
     suggestions.add("is:merged");
     suggestions.add("is:abandoned");
+    suggestions.add("is:mergeable");
 
     suggestions.add("status:");
     suggestions.add("status:open");
@@ -122,7 +123,7 @@
 
   @Override
   public void requestDefaultSuggestions(Request request, Callback done) {
-    final ArrayList<SearchSuggestion> r = new ArrayList<SearchSuggestOracle.SearchSuggestion>();
+    final ArrayList<SearchSuggestion> r = new ArrayList<>();
     // No text - show some default suggestions.
     r.add(new SearchSuggestion("status:open", "status:open"));
     r.add(new SearchSuggestion("age:1week", "age:1week"));
@@ -150,7 +151,7 @@
       }
     }
 
-    final ArrayList<SearchSuggestion> r = new ArrayList<SearchSuggestOracle.SearchSuggestion>();
+    final ArrayList<SearchSuggestion> r = new ArrayList<>();
     for (String suggestion : suggestions.tailSet(lastWord)) {
       if ((lastWord.length() < suggestion.length()) && suggestion.startsWith(lastWord)) {
         if (suggestion.contains("self") && !Gerrit.isSignedIn()) {
@@ -207,11 +208,6 @@
     private final List<String> operators;
     private final SuggestOracle parameterSuggestionOracle;
 
-    ParamSuggester(final String operator,
-        final SuggestOracle parameterSuggestionOracle) {
-      this(Collections.singletonList(operator), parameterSuggestionOracle);
-    }
-
     ParamSuggester(final List<String> operators,
         final SuggestOracle parameterSuggestionOracle) {
       this.operators = operators;
@@ -243,8 +239,7 @@
                 final Response response) {
               final String query = request.getQuery();
               final List<SearchSuggestOracle.Suggestion> r =
-                  new ArrayList<SuggestOracle.Suggestion>(response
-                      .getSuggestions().size());
+                  new ArrayList<>(response.getSuggestions().size());
               for (final SearchSuggestOracle.Suggestion s : response
                   .getSuggestions()) {
                 r.add(new SearchSuggestion(s.getDisplayString(),
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
index 06fad36..8906da4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java
@@ -33,6 +33,11 @@
     return new RestApi("/accounts/").view("self");
   }
 
+  public static void putDiffPreferences(DiffPreferences in,
+      AsyncCallback<DiffPreferences> cb) {
+    self().view("preferences.diff").put(in, cb);
+  }
+
   /** Retrieve the username */
   public static void getUsername(String account, AsyncCallback<NativeString> cb) {
     new RestApi("/accounts/").id(account).view("username").get(cb);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index e0e2d22..d26ee7d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -38,6 +38,7 @@
   String showUsernameInReviewCategory();
   String buttonSaveChanges();
   String showRelativeDateInChangeTable();
+  String showSizeBarInChangeTable();
 
   String changeScreenOldUi();
   String changeScreenNewUi();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index b1001a4..90c6f5f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -17,7 +17,8 @@
 dateFormatLabel = Date/Time Format:
 contextWholeFile = Whole File
 buttonSaveChanges = Save Changes
-showRelativeDateInChangeTable = Show Relative Dates in Changes Table
+showRelativeDateInChangeTable = Show Relative Dates In Changes Table
+showSizeBarInChangeTable = Show Change Sizes As Colored Bars In Changes Table
 
 changeScreenOldUi = Old Screen
 changeScreenNewUi = New Screen
@@ -77,14 +78,14 @@
       Confirm the default path <em>.ssh/id_rsa</em>\
     </li>\
     <li>\
-      Enter a passphrase (recommended) or leave it blank.<br>\
-      Remember this passphrase, as you will need it to unlock the<br>\
+      Enter a passphrase (recommended) or leave it blank.<br />\
+      Remember this passphrase, as you will need it to unlock the<br />\
       key whenever you use it.\
     </li>\
     <li>\
-      Open <em>~/.ssh/id_rsa.pub</em> and copy & paste the contents into<br>\
-      the box below, then click on "Add".<br>\
-      Note that <em>id_rsa.pub</em> is your public key and can be shared,<br>\
+      Open <em>~/.ssh/id_rsa.pub</em> and copy & paste the contents into<br />\
+      the box below, then click on "Add".<br />\
+      Note that <em>id_rsa.pub</em> is your public key and can be shared,<br />\
       while <em>id_rsa</em> is your private key and should be kept secret.\
     </li>\
   <\ol>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountMessages.properties
index 313893e..994c236 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountMessages.properties
@@ -1,7 +1,7 @@
 lines = {0} lines
 rowsPerPage = {0} rows per page
 
-changeScreenServerDefault = Server Default ({0}) 
+changeScreenServerDefault = Server Default ({0})
 
 enterIAGREE = (enter {0} in the box to the left)
 contactOnFile = Contact information last updated on {0,date,medium} at {0,time,short}.
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
new file mode 100644
index 0000000..5c66f3c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
@@ -0,0 +1,122 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.account;
+
+import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class DiffPreferences extends JavaScriptObject {
+  public static DiffPreferences create(AccountDiffPreference in) {
+    DiffPreferences p = createObject().cast();
+    if (in == null) {
+      in = AccountDiffPreference.createDefault(null);
+    }
+    p.ignoreWhitespace(in.getIgnoreWhitespace());
+    p.tabSize(in.getTabSize());
+    p.lineLength(in.getLineLength());
+    p.context(in.getContext());
+    p.intralineDifference(in.isIntralineDifference());
+    p.showLineEndings(in.isShowLineEndings());
+    p.showTabs(in.isShowTabs());
+    p.showWhitespaceErrors(in.isShowWhitespaceErrors());
+    p.syntaxHighlighting(in.isSyntaxHighlighting());
+    p.hideTopMenu(in.isHideTopMenu());
+    p.hideLineNumbers(in.isHideLineNumbers());
+    p.expandAllComments(in.isExpandAllComments());
+    p.manualReview(in.isManualReview());
+    p.renderEntireFile(in.isRenderEntireFile());
+    p.theme(in.getTheme());
+    return p;
+  }
+
+  public final void copyTo(AccountDiffPreference p) {
+    p.setIgnoreWhitespace(ignoreWhitespace());
+    p.setTabSize(tabSize());
+    p.setLineLength(lineLength());
+    p.setContext((short)context());
+    p.setIntralineDifference(intralineDifference());
+    p.setShowLineEndings(showLineEndings());
+    p.setShowTabs(showTabs());
+    p.setShowWhitespaceErrors(showWhitespaceErrors());
+    p.setSyntaxHighlighting(syntaxHighlighting());
+    p.setHideTopMenu(hideTopMenu());
+    p.setHideLineNumbers(hideLineNumbers());
+    p.setExpandAllComments(expandAllComments());
+    p.setManualReview(manualReview());
+    p.setRenderEntireFile(renderEntireFile());
+    p.setTheme(theme());
+  }
+
+  public final void ignoreWhitespace(Whitespace i) {
+    setIgnoreWhitespaceRaw(i.toString());
+  }
+  private final native void setIgnoreWhitespaceRaw(String i) /*-{ this.ignore_whitespace = i }-*/;
+
+  public final void theme(Theme i) {
+    setThemeRaw(i != null ? i.toString() : Theme.DEFAULT.toString());
+  }
+  private final native void setThemeRaw(String i) /*-{ this.theme = i }-*/;
+
+  public final native void tabSize(int t) /*-{ this.tab_size = t }-*/;
+  public final native void lineLength(int c) /*-{ this.line_length = c }-*/;
+  public final native void context(int c) /*-{ this.context = c }-*/;
+  public final native void intralineDifference(boolean i) /*-{ this.intraline_difference = i }-*/;
+  public final native void showLineEndings(boolean s) /*-{ this.show_line_endings = s }-*/;
+  public final native void showTabs(boolean s) /*-{ this.show_tabs = s }-*/;
+  public final native void showWhitespaceErrors(boolean s) /*-{ this.show_whitespace_errors = s }-*/;
+  public final native void syntaxHighlighting(boolean s) /*-{ this.syntax_highlighting = s }-*/;
+  public final native void hideTopMenu(boolean s) /*-{ this.hide_top_menu = s }-*/;
+  public final native void hideLineNumbers(boolean s) /*-{ this.hide_line_numbers = s }-*/;
+  public final native void expandAllComments(boolean e) /*-{ this.expand_all_comments = e }-*/;
+  public final native void manualReview(boolean r) /*-{ this.manual_review = r }-*/;
+  public final native void renderEntireFile(boolean r) /*-{ this.render_entire_file = r }-*/;
+  public final void showLineNumbers(boolean s) { hideLineNumbers(!s); }
+
+  public final Whitespace ignoreWhitespace() {
+    String s = ignoreWhitespaceRaw();
+    return s != null ? Whitespace.valueOf(s) : Whitespace.IGNORE_NONE;
+  }
+  private final native String ignoreWhitespaceRaw() /*-{ return this.ignore_whitespace }-*/;
+
+  public final Theme theme() {
+    String s = themeRaw();
+    return s != null ? Theme.valueOf(s) : Theme.DEFAULT;
+  }
+  private final native String themeRaw() /*-{ return this.theme }-*/;
+
+  public final int tabSize() {return get("tab_size", 8); }
+  public final int context() {return get("context", 10); }
+  public final int lineLength() {return get("line_length", 100); }
+  public final native boolean intralineDifference() /*-{ return this.intraline_difference || false }-*/;
+  public final native boolean showLineEndings() /*-{ return this.show_line_endings || false }-*/;
+  public final native boolean showTabs() /*-{ return this.show_tabs || false }-*/;
+  public final native boolean showWhitespaceErrors() /*-{ return this.show_whitespace_errors || false }-*/;
+  public final native boolean syntaxHighlighting() /*-{ return this.syntax_highlighting || false }-*/;
+  public final native boolean hideTopMenu() /*-{ return this.hide_top_menu || false }-*/;
+  public final native boolean hideLineNumbers() /*-{ return this.hide_line_numbers || false }-*/;
+  public final native boolean expandAllComments() /*-{ return this.expand_all_comments || false }-*/;
+  public final native boolean manualReview() /*-{ return this.manual_review || false }-*/;
+  public final native boolean renderEntireFile() /*-{ return this.render_entire_file || false }-*/;
+  public final boolean showLineNumbers() { return !hideLineNumbers(); }
+  public final boolean autoReview() { return !manualReview(); }
+
+  private final native int get(String n, int d)
+  /*-{ return this.hasOwnProperty(n) ? this[n] : d }-*/;
+
+  protected DiffPreferences() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
index ca872cc..4703ef7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
@@ -106,8 +106,7 @@
     }
 
     void deleteChecked() {
-      final HashSet<AccountExternalId.Key> keys =
-          new HashSet<AccountExternalId.Key>();
+      final HashSet<AccountExternalId.Key> keys = new HashSet<>();
       for (int row = 1; row < table.getRowCount(); row++) {
         final AccountExternalId k = getRowItem(row);
         if (k == null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index 731eaeb..d8a0427 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -45,6 +45,7 @@
   private CheckBox reversePatchSetOrder;
   private CheckBox showUsernameInReviewCategory;
   private CheckBox relativeDateInChangeTable;
+  private CheckBox sizeBarInChangeTable;
   private ListBox maximumPageSize;
   private ListBox dateFormat;
   private ListBox timeFormat;
@@ -136,8 +137,9 @@
     }
 
     relativeDateInChangeTable = new CheckBox(Util.C.showRelativeDateInChangeTable());
+    sizeBarInChangeTable = new CheckBox(Util.C.showSizeBarInChangeTable());
 
-    final Grid formGrid = new Grid(11, 2);
+    final Grid formGrid = new Grid(12, 2);
 
     int row = 0;
     formGrid.setText(row, labelIdx, "");
@@ -168,22 +170,28 @@
     formGrid.setWidget(row, fieldIdx, dateTimePanel);
     row++;
 
-    formGrid.setText(row, labelIdx, "");
-    formGrid.setWidget(row, fieldIdx, relativeDateInChangeTable);
-    row++;
+    if (Gerrit.getConfig().getNewFeatures()) {
+      formGrid.setText(row, labelIdx, "");
+      formGrid.setWidget(row, fieldIdx, relativeDateInChangeTable);
+      row++;
+
+      formGrid.setText(row, labelIdx, "");
+      formGrid.setWidget(row, fieldIdx, sizeBarInChangeTable);
+      row++;
+    }
 
     formGrid.setText(row, labelIdx, Util.C.commentVisibilityLabel());
     formGrid.setWidget(row, fieldIdx, commentVisibilityStrategy);
     row++;
 
-    formGrid.setText(row, labelIdx, Util.C.changeScreenLabel());
-    formGrid.setWidget(row, fieldIdx, changeScreen);
-    row++;
+    if (Gerrit.getConfig().getNewFeatures()) {
+      formGrid.setText(row, labelIdx, Util.C.changeScreenLabel());
+      formGrid.setWidget(row, fieldIdx, changeScreen);
+      row++;
 
-    formGrid.setText(row, labelIdx, Util.C.diffViewLabel());
-    formGrid.setWidget(row, fieldIdx, diffView);
-    row++;
-
+      formGrid.setText(row, labelIdx, Util.C.diffViewLabel());
+      formGrid.setWidget(row, fieldIdx, diffView);
+    }
     add(formGrid);
 
     save = new Button(Util.C.buttonSaveChanges());
@@ -206,6 +214,7 @@
     e.listenTo(dateFormat);
     e.listenTo(timeFormat);
     e.listenTo(relativeDateInChangeTable);
+    e.listenTo(sizeBarInChangeTable);
     e.listenTo(commentVisibilityStrategy);
     e.listenTo(changeScreen);
     e.listenTo(diffView);
@@ -231,6 +240,7 @@
     dateFormat.setEnabled(on);
     timeFormat.setEnabled(on);
     relativeDateInChangeTable.setEnabled(on);
+    sizeBarInChangeTable.setEnabled(on);
     commentVisibilityStrategy.setEnabled(on);
     changeScreen.setEnabled(on);
     diffView.setEnabled(on);
@@ -248,6 +258,7 @@
     setListBox(timeFormat, AccountGeneralPreferences.TimeFormat.HHMM_12, //
         p.getTimeFormat());
     relativeDateInChangeTable.setValue(p.isRelativeDateInChangeTable());
+    sizeBarInChangeTable.setValue(p.isSizeBarInChangeTable());
     setListBox(commentVisibilityStrategy,
         AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_RECENT,
         p.getCommentVisibilityStrategy());
@@ -280,7 +291,7 @@
         return;
       }
     }
-    if (currentValue != defaultValue) {
+    if (!currentValue.equals(defaultValue)) {
       setListBox(f, defaultValue, defaultValue);
     }
   }
@@ -325,6 +336,7 @@
         AccountGeneralPreferences.TimeFormat.HHMM_12,
         AccountGeneralPreferences.TimeFormat.values()));
     p.setRelativeDateInChangeTable(relativeDateInChangeTable.getValue());
+    p.setSizeBarInChangeTable(sizeBarInChangeTable.getValue());
     p.setCommentVisibilityStrategy(getListBox(commentVisibilityStrategy,
         CommentVisibilityStrategy.EXPAND_RECENT,
         CommentVisibilityStrategy.values()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
index 01d6e3c..52246b2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
@@ -115,6 +115,6 @@
     info.setText(row++, fieldIdx, account.getFullName());
     info.setText(row++, fieldIdx, account.getPreferredEmail());
     info.setText(row++, fieldIdx, mediumFormat(account.getRegisteredOn()));
-    info.setText(row++, fieldIdx, account.getId().toString());
+    info.setText(row, fieldIdx, account.getId().toString());
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
index 9f248ee..6c12e99 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
@@ -87,8 +87,7 @@
   }
 
   protected Set<AccountProjectWatch.Key> getCheckedIds() {
-    final Set<AccountProjectWatch.Key> ids =
-        new HashSet<AccountProjectWatch.Key>();
+    final Set<AccountProjectWatch.Key> ids = new HashSet<>();
     for (int row = 1; row < table.getRowCount(); row++) {
       final AccountProjectWatchInfo k = getRowItem(row);
       if (k != null && ((CheckBox) table.getWidget(row, 1)).getValue()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
index ac44dbe..275937e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/NewAgreementScreen.java
@@ -81,7 +81,7 @@
     Util.ACCOUNT_SVC.myAgreements(new GerritCallback<AgreementInfo>() {
       public void onSuccess(AgreementInfo result) {
         if (isAttached()) {
-          mySigned = new HashSet<String>(result.accepted);
+          mySigned = new HashSet<>(result.accepted);
           postRPC();
         }
       }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
index 0dfb520..fb1ed8b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SshPanel.java
@@ -259,7 +259,7 @@
     }
 
     void deleteChecked() {
-      final HashSet<Integer> sequenceNumbers = new HashSet<Integer>();
+      final HashSet<Integer> sequenceNumbers = new HashSet<>();
       for (int row = 1; row < table.getRowCount(); row++) {
         final SshKeyInfo k = getRowItem(row);
         if (k != null && ((CheckBox) table.getWidget(row, 1)).getValue()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index b734b41..3a0d2f7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -218,7 +218,7 @@
   }
 
   private void rebuildPermissionSelector() {
-    List<String> perms = new ArrayList<String>();
+    List<String> perms = new ArrayList<>();
 
     if (AccessSection.GLOBAL_CAPABILITIES.equals(value.getName())) {
       for (String varName : projectAccess.getCapabilities().keySet()) {
@@ -261,7 +261,7 @@
   @Override
   public void flush() {
     List<Permission> src = permissions.getList();
-    List<Permission> keep = new ArrayList<Permission>(src.size());
+    List<Permission> keep = new ArrayList<>(src.size());
 
     for (int i = 0; i < src.size(); i++) {
       PermissionEditor e = (PermissionEditor) permissionContainer.getWidget(i);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
index 8e0d22b..2cacf35 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
@@ -214,8 +214,7 @@
     ownerTxt.setText(group.owner() != null?group.owner():Util.M.deletedReference(group.getOwnerUUID().get()));
     descTxt.setText(group.description());
     visibleToAllCheckBox.setValue(group.options().isVisibleToAll());
-    setMembersTabVisible(AccountGroup.isInternalGroup(group.getGroupUUID())
-        && !AccountGroup.isSystemGroup(group.getGroupUUID()));
+    setMembersTabVisible(AccountGroup.isInternalGroup(group.getGroupUUID()));
 
     enableForm(canModify);
     saveName.setVisible(canModify);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
index 0f326bf..df74a41 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
@@ -149,8 +149,7 @@
 
   @Override
   protected void display(final GroupInfo group, final boolean canModify) {
-    if (AccountGroup.isInternalGroup(group.getGroupUUID())
-        && !AccountGroup.isSystemGroup(group.getGroupUUID())) {
+    if (AccountGroup.isInternalGroup(group.getGroupUUID())) {
       members.display(Natives.asList(group.members()));
       includes.display(Natives.asList(group.includes()));
     } else {
@@ -239,7 +238,7 @@
     }
 
     void deleteChecked() {
-      final HashSet<Integer> ids = new HashSet<Integer>();
+      final HashSet<Integer> ids = new HashSet<>();
       for (int row = 1; row < table.getRowCount(); row++) {
         final AccountInfo i = getRowItem(row);
         if (i != null && ((CheckBox) table.getWidget(row, 1)).getValue()) {
@@ -344,7 +343,7 @@
     }
 
     void deleteChecked() {
-      final HashSet<AccountGroup.UUID> ids = new HashSet<AccountGroup.UUID>();
+      final HashSet<AccountGroup.UUID> ids = new HashSet<>();
       for (int row = 1; row < table.getRowCount(); row++) {
         final GroupInfo i = getRowItem(row);
         if (i != null && ((CheckBox) table.getWidget(row, 1)).getValue()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
index ade00b9..8893549 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
@@ -37,8 +37,7 @@
 
     link(Util.C.groupTabGeneral(), getTabToken(token, INFO));
     link(Util.C.groupTabMembers(), membersTabToken,
-        AccountGroup.isInternalGroup(group.getGroupUUID())
-        && !AccountGroup.isSystemGroup(group.getGroupUUID()));
+        AccountGroup.isInternalGroup(group.getGroupUUID()));
   }
 
   private String getTabToken(final String token, final String tab) {
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 cb96e38..a92b736 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
@@ -92,6 +92,8 @@
   String initialRevision();
   String buttonAddBranch();
   String buttonDeleteBranch();
+  String saveHeadButton();
+  String cancelHeadButton();
 
   String groupItemHelp();
 
@@ -108,8 +110,10 @@
   String plugins();
   String pluginEnabled();
   String pluginDisabled();
+  String pluginSettingsToolTip();
 
   String columnPluginName();
+  String columnPluginSettings();
   String columnPluginVersion();
   String columnPluginStatus();
 
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 6a25a09..ef35e00 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
@@ -71,6 +71,8 @@
 initialRevision = Initial Revision
 buttonAddBranch = Create Branch
 buttonDeleteBranch = Delete
+saveHeadButton = Save
+cancelHeadButton = Cancel
 
 groupItemHelp = group
 
@@ -87,7 +89,9 @@
 plugins = Plugins
 pluginEnabled = Enabled
 pluginDisabled = Disabled
+pluginSettingsToolTip = Plugin Settings
 columnPluginName = Plugin Name
+columnPluginSettings = Settings
 columnPluginVersion = Version
 columnPluginStatus = Status
 
@@ -122,6 +126,7 @@
 	rebase, \
 	removeReviewer, \
 	submit, \
+	submitAs, \
 	viewDrafts
 
 abandon = Abandon
@@ -141,6 +146,7 @@
 rebase = Rebase
 removeReviewer = Remove Reviewer
 submit = Submit
+submitAs = Submit (On Behalf Of)
 viewDrafts = View Drafts
 
 refErrorEmpty = Reference must be supplied
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
index 6b1269d..05ffa9a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
@@ -28,4 +28,7 @@
 
   String effectiveMaxObjectSizeLimit(String effectiveMaxObjectSizeLimit);
   String globalMaxObjectSizeLimit(String globalMaxObjectSizeLimit);
+  String pluginProjectOptionsTitle(String pluginName);
+  String pluginProjectInheritedValue(String value);
+  String pluginProjectInheritedListValue(String value);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
index cb3784f..6338920 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
@@ -7,3 +7,7 @@
 deletedSection = Section {0} was deleted
 effectiveMaxObjectSizeLimit = effective: {0}
 globalMaxObjectSizeLimit = The global max object size limit is set to {0}. The limit cannot be increased on project level.
+pluginProjectOptionsTitle = {0} Plugin Options
+pluginProjectOptionsTitle = {0} Plugin
+pluginProjectInheritedValue = inherited: {0}
+pluginProjectInheritedListValue = INHERIT ({0})
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
index 7749e9c..0379cc0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
@@ -214,8 +214,8 @@
           }
         });
 
-        table.setWidget(row, 1, projectLink);
-        table.setText(row, 2, k.description());
+        table.setWidget(row, 2, projectLink);
+        table.setText(row, 3, k.description());
 
         setRowItem(row, k);
       }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
index 5222751..efb2fb4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.client.ui.SuggestUtil;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupInfo;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
@@ -26,6 +27,7 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.common.data.RefConfigSection;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.Scheduler;
@@ -55,6 +57,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 public class PermissionEditor extends Composite implements Editor<Permission>,
     ValueAwareEditor<Permission> {
@@ -101,6 +104,7 @@
   DivElement deleted;
 
   private final Project.NameKey projectName;
+  private final Map<AccountGroup.UUID, GroupInfo> groupInfo;
   private final boolean readOnly;
   private final AccessSection section;
   private final LabelTypes labelTypes;
@@ -115,6 +119,7 @@
     this.readOnly = readOnly;
     this.section = section;
     this.projectName = projectAccess.getProjectName();
+    this.groupInfo = projectAccess.getGroupInfo();
     this.labelTypes = labelTypes;
 
     PermissionNameRenderer nameRenderer =
@@ -290,7 +295,7 @@
   @Override
   public void flush() {
     List<PermissionRule> src = rules.getList();
-    List<PermissionRule> keep = new ArrayList<PermissionRule>(src.size());
+    List<PermissionRule> keep = new ArrayList<>(src.size());
 
     for (int i = 0; i < src.size(); i++) {
       PermissionRuleEditor e =
@@ -314,7 +319,7 @@
     @Override
     public PermissionRuleEditor create(int index) {
       PermissionRuleEditor subEditor =
-          new PermissionRuleEditor(readOnly, section, value, validRange);
+          new PermissionRuleEditor(readOnly, groupInfo, section, value, validRange);
       ruleContainer.insert(subEditor, index);
       return subEditor;
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
index d8ee195..7ca8f0f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
@@ -25,7 +25,7 @@
   private static final Map<String, String> permissions;
 
   static {
-    permissions = new HashMap<String, String>();
+    permissions = new HashMap<>();
     for (Map.Entry<String, String> e : Util.C.permissionNames().entrySet()) {
       permissions.put(e.getKey(), e.getValue());
       permissions.put(e.getKey().toLowerCase(), e.getValue());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
index 6c9a9b7..07862f4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
@@ -19,9 +19,10 @@
 import static com.google.gerrit.common.data.Permission.PUSH_TAG;
 
 import com.google.gerrit.client.Dispatcher;
-import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupInfo;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
@@ -35,6 +36,8 @@
 import com.google.gwt.editor.client.EditorDelegate;
 import com.google.gwt.editor.client.ValueAwareEditor;
 import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.text.shared.Renderer;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
@@ -49,6 +52,7 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 public class PermissionRuleEditor extends Composite implements
     Editor<PermissionRule>, ValueAwareEditor<PermissionRule> {
@@ -70,7 +74,7 @@
   CheckBox force;
 
   @UiField
-  Hyperlink groupNameLink;
+  Anchor groupNameLink;
   @UiField
   SpanElement groupNameSpan;
   @UiField
@@ -87,10 +91,16 @@
   @UiField
   SpanElement rangeEditor;
 
+  private Map<AccountGroup.UUID, GroupInfo> groupInfo;
   private boolean isDeleted;
+  private HandlerRegistration clickHandler;
 
-  public PermissionRuleEditor(boolean readOnly, AccessSection section,
-      Permission permission, PermissionRange.WithDefaults validRange) {
+  public PermissionRuleEditor(boolean readOnly,
+      Map<AccountGroup.UUID, GroupInfo> groupInfo,
+      AccessSection section,
+      Permission permission,
+      PermissionRange.WithDefaults validRange) {
+    this.groupInfo = groupInfo;
     action = new ValueListBox<PermissionRule.Action>(actionRenderer);
 
     if (validRange != null && 10 < validRange.getRangeSize()) {
@@ -181,12 +191,37 @@
 
   @Override
   public void setValue(PermissionRule value) {
+    if (clickHandler != null) {
+      clickHandler.removeHandler();
+      clickHandler = null;
+    }
+
     GroupReference ref = value.getGroup();
+    GroupInfo info = groupInfo != null && ref.getUUID() != null
+        ? groupInfo.get(ref.getUUID())
+        : null;
 
     boolean link;
     if (ref.getUUID() != null && AccountGroup.isInternalGroup(ref.getUUID())) {
+      final String token = Dispatcher.toGroup(ref.getUUID());
       groupNameLink.setText(ref.getName());
-      groupNameLink.setTargetHistoryToken(Dispatcher.toGroup(ref.getUUID()));
+      groupNameLink.setHref("#" + token);
+      groupNameLink.setTitle(info != null ? info.getDescription() : null);
+      groupNameLink.setTarget(null);
+      clickHandler = groupNameLink.addClickHandler(new ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          event.preventDefault();
+          event.stopPropagation();
+          Gerrit.display(token);
+        }
+      });
+      link = true;
+    } else if (info != null && info.getUrl() != null) {
+      groupNameLink.setText(ref.getName());
+      groupNameLink.setHref(info.getUrl());
+      groupNameLink.setTitle(info.getDescription());
+      groupNameLink.setTarget("_blank");
       link = true;
     } else {
       groupNameSpan.setInnerText(ref.getName());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml
index 26fc229..4d322c0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.ui.xml
@@ -78,7 +78,7 @@
     <g:Widget ui:field='max' styleName='{style.minmax}'/>
   </span>
 
-  <q:Hyperlink ui:field='groupNameLink' styleName='{style.groupName}'/>
+  <g:Anchor ui:field='groupNameLink' styleName='{style.groupName}'/>
   <span ui:field='groupNameSpan' styleName='{style.groupName}'/>
   <g:CheckBox ui:field='force' addStyleNames='{style.forcePush}'/>
   <g:Anchor
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
index f9f0362..e1c73fa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginListScreen.java
@@ -15,14 +15,17 @@
 package com.google.gerrit.client.admin;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.api.ExtensionScreen;
 import com.google.gerrit.client.plugins.PluginInfo;
 import com.google.gerrit.client.plugins.PluginMap;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.FancyFlexTable;
+import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
 import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.ImageResourceRenderer;
 import com.google.gwt.user.client.ui.Panel;
 
 public class PluginListScreen extends PluginScreen {
@@ -60,13 +63,15 @@
   private class PluginTable extends FancyFlexTable<PluginInfo> {
     PluginTable() {
       table.setText(0, 1, Util.C.columnPluginName());
-      table.setText(0, 2, Util.C.columnPluginVersion());
-      table.setText(0, 3, Util.C.columnPluginStatus());
+      table.setText(0, 2, Util.C.columnPluginSettings());
+      table.setText(0, 3, Util.C.columnPluginVersion());
+      table.setText(0, 4, Util.C.columnPluginStatus());
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
       fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
       fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
+      fmt.addStyleName(0, 4, Gerrit.RESOURCES.css().dataHeader());
     }
 
     void display(final PluginMap plugins) {
@@ -83,23 +88,31 @@
     }
 
     void populate(final int row, final PluginInfo plugin) {
-      if (plugin.isDisabled()) {
+      if (plugin.disabled() || plugin.indexUrl() == null) {
         table.setText(row, 1, plugin.name());
       } else {
-        table.setWidget(
-            row,
-            1,
-            new Anchor(plugin.name(), Gerrit.selfRedirect("/plugins/"
-                + plugin.name() + "/"), "_blank"));
+        table.setWidget(row, 1, new Anchor(plugin.name(),
+            Gerrit.selfRedirect(plugin.indexUrl()), "_blank"));
+
+        if (new ExtensionScreen(plugin.name() + "/settings").isFound()) {
+          InlineHyperlink adminScreenLink = new InlineHyperlink();
+          adminScreenLink.setHTML(new ImageResourceRenderer().render(Gerrit.RESOURCES.gear()));
+          adminScreenLink.setTargetHistoryToken("/x/" + plugin.name() + "/settings");
+          adminScreenLink.setTitle(Util.C.pluginSettingsToolTip());
+          table.setWidget(row, 2, adminScreenLink);
+        }
       }
-      table.setText(row, 2, plugin.version());
-      table.setText(row, 3, plugin.isDisabled() ? Util.C.pluginDisabled()
+
+      table.setText(row, 3, plugin.version());
+      table.setText(row, 4, plugin.disabled()
+          ? Util.C.pluginDisabled()
           : Util.C.pluginEnabled());
 
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
       fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
       fmt.addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
+      fmt.addStyleName(row, 4, Gerrit.RESOURCES.css().dataCell());
 
       setRowItem(row, plugin);
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
index 49a9aa4..8981e82 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.java
@@ -18,10 +18,12 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GitwebLink;
 import com.google.gerrit.client.ui.Hyperlink;
+import com.google.gerrit.client.ui.ParentProjectBox;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.DivElement;
 import com.google.gwt.dom.client.Style.Display;
@@ -56,6 +58,10 @@
   Hyperlink parentProject;
 
   @UiField
+  @Editor.Ignore
+  ParentProjectBox parentProjectBox;
+
+  @UiField
   DivElement history;
 
   @UiField
@@ -106,6 +112,11 @@
       parentProject.setText(parent.get());
       parentProject.setTargetHistoryToken( //
           Dispatcher.toProjectAdmin(parent, ProjectScreen.ACCESS));
+
+      parentProjectBox.setVisible(editing);
+      parentProjectBox.setProject(value.getProjectName());
+      parentProjectBox.setParentProject(value.getInheritsFrom());
+      parentProject.setVisible(!parentProjectBox.isVisible());
     } else {
       inheritsFrom.getStyle().setDisplay(Display.NONE);
     }
@@ -115,7 +126,7 @@
       history.getStyle().setDisplay(Display.BLOCK);
       gitweb.setText(c.getLinkName());
       gitweb.setHref(c.toFileHistory(new Branch.NameKey(value.getProjectName(),
-          "refs/meta/config"), "project.config"));
+          RefNames.REFS_CONFIG), "project.config"));
     } else {
       history.getStyle().setDisplay(Display.NONE);
     }
@@ -126,7 +137,7 @@
   @Override
   public void flush() {
     List<AccessSection> src = local.getList();
-    List<AccessSection> keep = new ArrayList<AccessSection>(src.size());
+    List<AccessSection> keep = new ArrayList<>(src.size());
 
     for (int i = 0; i < src.size(); i++) {
       AccessSectionEditor e = (AccessSectionEditor) localContainer.getWidget(i);
@@ -135,6 +146,7 @@
       }
     }
     value.setLocal(keep);
+    value.setInheritsFrom(parentProjectBox.getParentProjectName());
   }
 
   @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml
index 4942b83..120824b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessEditor.ui.xml
@@ -56,6 +56,9 @@
   <div ui:field='inheritsFrom' class='{style.inheritsFrom}'>
     <span class='{style.parentTitle}'><ui:msg>Rights Inherit From:</ui:msg></span>
     <q:Hyperlink ui:field='parentProject' styleName='{style.parentLink}'/>
+    <q:ParentProjectBox
+      ui:field='parentProjectBox'
+      visible='false'/>
   </div>
   <div ui:field='history' class='{style.history}'>
     <span class='{style.historyTitle}'><ui:msg>History:</ui:msg></span>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index ef7f560..9c6cc1d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.common.ProjectAccessUtil.mergeSections;
 import static com.google.gerrit.common.ProjectAccessUtil.removeEmptyPermissionsAndSections;
 
+import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.config.CapabilityInfo;
 import com.google.gerrit.client.config.ConfigServerApi;
@@ -28,6 +29,7 @@
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.ProjectAccess;
+import com.google.gerrit.common.errors.UpdateParentFailedException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.GWT;
@@ -45,6 +47,7 @@
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwtexpui.globalkey.client.NpTextArea;
+import com.google.gwtjsonrpc.client.RemoteJsonException;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -142,7 +145,7 @@
 
   private void displayReadOnly(ProjectAccess access) {
     this.access = access;
-    Map<String, String> allCapabilities = new HashMap<String, String>();
+    Map<String, String> allCapabilities = new HashMap<>();
     for (CapabilityInfo c : Natives.asList(capabilityMap.values())) {
       allCapabilities.put(c.id(), c.name());
     }
@@ -205,6 +208,7 @@
         access.getRevision(), //
         message, //
         access.getLocal(), //
+        access.getInheritsFrom(), //
         new GerritCallback<ProjectAccess>() {
           @Override
           public void onSuccess(ProjectAccess newAccess) {
@@ -229,17 +233,16 @@
               ProjectAccess newAccess) {
             final List<AccessSection> wantedSections =
                 mergeSections(removeEmptyPermissionsAndSections(wantedAccess.getLocal()));
-            final HashSet<AccessSection> same =
-                new HashSet<AccessSection>(wantedSections);
+            final HashSet<AccessSection> same = new HashSet<>(wantedSections);
             final HashSet<AccessSection> different =
-                new HashSet<AccessSection>(wantedSections.size()
+                new HashSet<>(wantedSections.size()
                     + newAccess.getLocal().size());
             different.addAll(wantedSections);
             different.addAll(newAccess.getLocal());
             same.retainAll(newAccess.getLocal());
             different.removeAll(same);
 
-            final Set<String> differentNames = new HashSet<String>();
+            final Set<String> differentNames = new HashSet<>();
             for (final AccessSection s : different) {
               differentNames.add(s.getName());
             }
@@ -250,7 +253,15 @@
           public void onFailure(Throwable caught) {
             error.clear();
             enable(true);
-            super.onFailure(caught);
+            if (caught instanceof RemoteJsonException
+                && caught.getMessage().startsWith(
+                    UpdateParentFailedException.MESSAGE)) {
+              new ErrorDialog(Gerrit.M.parentUpdateFailed(caught.getMessage()
+                  .substring(UpdateParentFailedException.MESSAGE.length() + 1)))
+                  .center();
+            } else {
+              super.onFailure(caught);
+            }
           }
         });
   }
@@ -275,6 +286,7 @@
         access.getRevision(), //
         message, //
         access.getLocal(), //
+        access.getInheritsFrom(), //
         new GerritCallback<Change.Id>() {
           @Override
           public void onSuccess(Change.Id changeId) {
@@ -299,8 +311,8 @@
 
   private void enable(boolean enabled) {
     commitMessage.setEnabled(enabled);
-    commit.setEnabled(enabled ? !access.getOwnerOf().isEmpty() : false);
-    review.setEnabled(enabled ? access.canUpload() : false);
+    commit.setEnabled(enabled && !access.getOwnerOf().isEmpty());
+    review.setEnabled(enabled && access.canUpload());
     cancel1.setEnabled(enabled);
     cancel2.setEnabled(enabled);
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index 04d407e..a70aa8f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -25,12 +25,16 @@
 import com.google.gerrit.client.projects.BranchInfo;
 import com.google.gerrit.client.projects.ProjectApi;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.FancyFlexTable;
 import com.google.gerrit.client.ui.HintTextBox;
+import com.google.gerrit.client.ui.OnEditEnabler;
+import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -47,8 +51,11 @@
 import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineLabel;
 import com.google.gwt.user.client.ui.TextBox;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
 import java.util.Comparator;
@@ -242,7 +249,7 @@
     }
 
     Set<String> getCheckedRefs() {
-      Set<String> refs = new HashSet<String>();
+      Set<String> refs = new HashSet<>();
       for (int row = 1; row < table.getRowCount(); row++) {
         final BranchInfo k = getRowItem(row);
         if (k != null && table.getWidget(row, 1) instanceof CheckBox
@@ -371,7 +378,11 @@
       table.setText(row, 2, k.getShortName());
 
       if (k.revision() != null) {
-        table.setText(row, 3, k.revision());
+        if ("HEAD".equals(k.getShortName())) {
+          setHeadRevision(row, 3, k.revision());
+        } else {
+          table.setText(row, 3, k.revision());
+        }
       } else {
         table.setText(row, 3, "");
       }
@@ -384,7 +395,7 @@
       final FlexCellFormatter fmt = table.getFlexCellFormatter();
       String iconCellStyle = Gerrit.RESOURCES.css().iconCell();
       String dataCellStyle = Gerrit.RESOURCES.css().dataCell();
-      if ("refs/meta/config".equals(k.getShortName())
+      if (RefNames.REFS_CONFIG.equals(k.getShortName())
           || "HEAD".equals(k.getShortName())) {
         iconCellStyle = Gerrit.RESOURCES.css().specialBranchIconCell();
         dataCellStyle = Gerrit.RESOURCES.css().specialBranchDataCell();
@@ -400,6 +411,92 @@
       setRowItem(row, k);
     }
 
+    private void setHeadRevision(final int row, final int column,
+        final String rev) {
+      AccessMap.get(getProjectKey(),
+          new GerritCallback<ProjectAccessInfo>() {
+            @Override
+            public void onSuccess(ProjectAccessInfo result) {
+              if (result.isOwner()) {
+                table.setWidget(row, column, getHeadRevisionWidget(rev));
+              } else {
+                table.setText(row, 3, rev);
+              }
+            }
+          });
+    }
+
+    private Widget getHeadRevisionWidget(final String headRevision) {
+      FlowPanel p = new FlowPanel();
+      final InlineLabel l = new InlineLabel(headRevision);
+      final Image edit = new Image(Gerrit.RESOURCES.edit());
+      edit.addStyleName(Gerrit.RESOURCES.css().editHeadButton());
+
+      final NpTextBox input = new NpTextBox();
+      input.setVisibleLength(35);
+      input.setValue(headRevision);
+      input.setVisible(false);
+      final Button save = new Button();
+      save.setText(Util.C.saveHeadButton());
+      save.setVisible(false);
+      save.setEnabled(false);
+      final Button cancel = new Button();
+      cancel.setText(Util.C.cancelHeadButton());
+      cancel.setVisible(false);
+
+      OnEditEnabler e = new OnEditEnabler(save);
+      e.listenTo(input);
+
+      edit.addClickHandler(new  ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          l.setVisible(false);
+          edit.setVisible(false);
+          input.setVisible(true);
+          save.setVisible(true);
+          cancel.setVisible(true);
+        }
+      });
+      save.addClickHandler(new  ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          save.setEnabled(false);
+          ProjectApi.setHead(getProjectKey(), input.getValue().trim(),
+              new GerritCallback<NativeString>() {
+            @Override
+            public void onSuccess(NativeString result) {
+              Gerrit.display(PageLinks.toProjectBranches(getProjectKey()));
+            }
+
+            @Override
+            public void onFailure(Throwable caught) {
+              super.onFailure(caught);
+              save.setEnabled(true);
+            }
+          });
+        }
+      });
+      cancel.addClickHandler(new  ClickHandler() {
+        @Override
+        public void onClick(ClickEvent event) {
+          l.setVisible(true);
+          edit.setVisible(true);
+          input.setVisible(false);
+          input.setValue(headRevision);
+          save.setVisible(false);
+          save.setEnabled(false);
+          cancel.setVisible(false);
+        }
+      });
+
+      p.add(l);
+      p.add(edit);
+      p.add(input);
+      p.add(save);
+      p.add(cancel);
+      return p;
+    }
+
     boolean hasBranchCanDelete() {
       return canDelete;
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index f4db4ff..2b50cac 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -22,12 +22,16 @@
 import com.google.gerrit.client.change.Resources;
 import com.google.gerrit.client.download.DownloadPanel;
 import com.google.gerrit.client.projects.ConfigInfo;
+import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterInfo;
+import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterValue;
 import com.google.gerrit.client.projects.ConfigInfo.InheritedBooleanInfo;
 import com.google.gerrit.client.projects.ProjectApi;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.NpIntTextBox;
 import com.google.gerrit.client.ui.OnEditEnabler;
 import com.google.gerrit.client.ui.SmallHeading;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
@@ -39,20 +43,30 @@
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FocusWidget;
 import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.TextBox;
 import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.NpTextArea;
 import com.google.gwtexpui.globalkey.client.NpTextBox;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
 public class ProjectInfoScreen extends ProjectScreen {
   private boolean isOwner;
 
   private LabeledWidgetsGrid grid;
+  private Panel pluginOptionsPanel;
   private LabeledWidgetsGrid actionsGrid;
 
   // Section: Project Options
@@ -62,6 +76,7 @@
   private ListBox contentMerge;
   private NpTextBox maxObjectSizeLimit;
   private Label effectiveMaxObjectSizeLimit;
+  private Map<String, Map<String, FocusWidget>> pluginConfigWidgets;
 
   // Section: Contributor Agreements
   private ListBox contributorAgreements;
@@ -93,10 +108,12 @@
 
     initDescription();
     grid = new LabeledWidgetsGrid();
+    pluginOptionsPanel = new FlowPanel();
     actionsGrid = new LabeledWidgetsGrid();
     initProjectOptions();
     initAgreements();
     add(grid);
+    add(pluginOptionsPanel);
     add(saveProject);
     add(actionsGrid);
   }
@@ -140,6 +157,14 @@
     signedOffBy.setEnabled(isOwner);
     requireChangeID.setEnabled(isOwner);
     maxObjectSizeLimit.setEnabled(isOwner);
+
+    if (pluginConfigWidgets != null) {
+      for (Map<String, FocusWidget> widgetMap : pluginConfigWidgets.values()) {
+        for (FocusWidget widget : widgetMap.values()) {
+          widget.setEnabled(isOwner);
+        }
+      }
+    }
   }
 
   private void initDescription() {
@@ -159,6 +184,13 @@
   private void initProjectOptions() {
     grid.addHeader(new SmallHeading(Util.C.headingProjectOptions()));
 
+    state = new ListBox();
+    for (final Project.State stateValue : Project.State.values()) {
+      state.addItem(Util.toLongString(stateValue), stateValue.name());
+    }
+    saveEnabler.listenTo(state);
+    grid.add(Util.C.headingProjectState(), state);
+
     submitType = new ListBox();
     for (final Project.SubmitType type : Project.SubmitType.values()) {
       submitType.addItem(Util.toLongString(type), type.name());
@@ -172,13 +204,6 @@
     saveEnabler.listenTo(submitType);
     grid.add(Util.C.headingProjectSubmitType(), submitType);
 
-    state = new ListBox();
-    for (final Project.State stateValue : Project.State.values()) {
-      state.addItem(Util.toLongString(stateValue), stateValue.name());
-    }
-    saveEnabler.listenTo(state);
-    grid.add(Util.C.headingProjectState(), state);
-
     contentMerge = newInheritedBooleanBox();
     saveEnabler.listenTo(contentMerge);
     grid.add(Util.C.useContentMerge(), contentMerge);
@@ -190,8 +215,9 @@
     maxObjectSizeLimit = new NpTextBox();
     saveEnabler.listenTo(maxObjectSizeLimit);
     effectiveMaxObjectSizeLimit = new Label();
+    effectiveMaxObjectSizeLimit.setStyleName(
+        Gerrit.RESOURCES.css().maxObjectSizeLimitEffectiveLabel());
     HorizontalPanel p = new HorizontalPanel();
-    p.setStyleName(Gerrit.RESOURCES.css().maxObjectSizeLimitPanel());
     p.add(maxObjectSizeLimit);
     p.add(effectiveMaxObjectSizeLimit);
     grid.addHtml(Util.C.headingMaxObjectSizeLimit(), p);
@@ -322,9 +348,200 @@
     }
 
     saveProject.setEnabled(false);
+    initPluginOptions(result);
     initProjectActions(result);
   }
 
+  private void initPluginOptions(ConfigInfo info) {
+    pluginOptionsPanel.clear();
+    pluginConfigWidgets = new HashMap<>();
+
+    for (String pluginName : info.pluginConfig().keySet()) {
+      Map<String, FocusWidget> widgetMap = new HashMap<>();
+      pluginConfigWidgets.put(pluginName, widgetMap);
+      LabeledWidgetsGrid g = new LabeledWidgetsGrid();
+      g.addHeader(new SmallHeading(Util.M.pluginProjectOptionsTitle(pluginName)));
+      pluginOptionsPanel.add(g);
+      NativeMap<ConfigParameterInfo> pluginConfig =
+          info.pluginConfig(pluginName);
+      pluginConfig.copyKeysIntoChildren("name");
+      for (ConfigParameterInfo param : Natives.asList(pluginConfig.values())) {
+        FocusWidget w;
+        switch (param.type()) {
+          case "STRING":
+          case "INT":
+          case "LONG":
+            w = renderTextBox(g, param);
+            break;
+          case "BOOLEAN":
+            w = renderCheckBox(g, param);
+            break;
+          case "LIST":
+            w = renderListBox(g, param);
+            break;
+          case "ARRAY":
+            w = renderTextArea(g, param);
+            break;
+          default:
+            throw new UnsupportedOperationException("unsupported widget type");
+        }
+        if (param.editable()) {
+          widgetMap.put(param.name(), w);
+        } else {
+          w.setEnabled(false);
+        }
+      }
+    }
+
+    enableForm();
+  }
+
+  private TextBox renderTextBox(LabeledWidgetsGrid g,
+      ConfigParameterInfo param) {
+    NpTextBox textBox = param.type().equals("STRING")
+        ? new NpTextBox()
+        : new NpIntTextBox();
+    if (param.inheritable()) {
+      textBox.setValue(param.configuredValue());
+      Label inheritedLabel =
+          new Label(Util.M.pluginProjectInheritedValue(param
+              .inheritedValue()));
+      inheritedLabel.setStyleName(Gerrit.RESOURCES.css()
+          .pluginProjectConfigInheritedValue());
+      HorizontalPanel p = new HorizontalPanel();
+      p.add(textBox);
+      p.add(inheritedLabel);
+      addWidget(g, p, param);
+    } else {
+      textBox.setValue(param.value());
+      addWidget(g, textBox, param);
+    }
+    saveEnabler.listenTo(textBox);
+    return textBox;
+  }
+
+  private CheckBox renderCheckBox(LabeledWidgetsGrid g,
+      ConfigParameterInfo param) {
+    CheckBox checkBox = new CheckBox(getDisplayName(param));
+    checkBox.setValue(Boolean.parseBoolean(param.value()));
+    HorizontalPanel p = new HorizontalPanel();
+    p.add(checkBox);
+    if (param.description() != null) {
+      Image infoImg = new Image(Gerrit.RESOURCES.info());
+      infoImg.setTitle(param.description());
+      p.add(infoImg);
+    }
+    if (param.warning() != null) {
+      Image warningImg = new Image(Gerrit.RESOURCES.warning());
+      warningImg.setTitle(param.warning());
+      p.add(warningImg);
+    }
+    g.add((String)null, p);
+    saveEnabler.listenTo(checkBox);
+    return checkBox;
+  }
+
+  private ListBox renderListBox(LabeledWidgetsGrid g,
+      ConfigParameterInfo param) {
+    if (param.permittedValues() == null) {
+      return null;
+    }
+    ListBox listBox = new ListBox();
+    if (param.inheritable()) {
+      listBox.addItem(
+          Util.M.pluginProjectInheritedListValue(param.inheritedValue()));
+      if (param.configuredValue() == null) {
+        listBox.setSelectedIndex(0);
+      }
+      for (int i = 0; i < param.permittedValues().length(); i++) {
+        String pv = param.permittedValues().get(i);
+        listBox.addItem(pv);
+        if (pv.equals(param.configuredValue())) {
+          listBox.setSelectedIndex(i + 1);
+        }
+      }
+    } else {
+      for (int i = 0; i < param.permittedValues().length(); i++) {
+        String pv = param.permittedValues().get(i);
+        listBox.addItem(pv);
+        if (pv.equals(param.value())) {
+          listBox.setSelectedIndex(i);
+        }
+      }
+    }
+
+    if (param.editable()) {
+      saveEnabler.listenTo(listBox);
+      addWidget(g, listBox, param);
+    } else {
+      listBox.setEnabled(false);
+
+      if (param.inheritable() && listBox.getSelectedIndex() != 0) {
+        // the inherited value is not selected,
+        // since the listBox is disabled the inherited value cannot be
+        // seen and we have to display it explicitly
+        Label inheritedLabel =
+            new Label(Util.M.pluginProjectInheritedValue(param
+                .inheritedValue()));
+        inheritedLabel.setStyleName(Gerrit.RESOURCES.css()
+            .pluginProjectConfigInheritedValue());
+        HorizontalPanel p = new HorizontalPanel();
+        p.add(listBox);
+        p.add(inheritedLabel);
+        addWidget(g, p, param);
+      } else {
+        addWidget(g, listBox, param);
+      }
+    }
+
+    return listBox;
+  }
+
+  private NpTextArea renderTextArea(LabeledWidgetsGrid g,
+      ConfigParameterInfo param) {
+    NpTextArea txtArea = new NpTextArea();
+    txtArea.setVisibleLines(4);
+    txtArea.setCharacterWidth(40);
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < param.values().length(); i++) {
+      String v = param.values().get(i);
+      sb.append(v).append("\n");
+    }
+    txtArea.setText(sb.toString());
+    if (param.editable()) {
+      saveEnabler.listenTo(txtArea);
+    } else {
+      txtArea.setEnabled(false);
+    }
+    addWidget(g, txtArea, param);
+    return txtArea;
+  }
+
+  private void addWidget(LabeledWidgetsGrid g, Widget w, ConfigParameterInfo param) {
+    if (param.description() != null || param.warning() != null) {
+      HorizontalPanel p = new HorizontalPanel();
+      p.add(new Label(getDisplayName(param)));
+      if (param.description() != null) {
+        Image infoImg = new Image(Gerrit.RESOURCES.info());
+        infoImg.setTitle(param.description());
+        p.add(infoImg);
+      }
+      if (param.warning() != null) {
+        Image warningImg = new Image(Gerrit.RESOURCES.warning());
+        warningImg.setTitle(param.warning());
+        p.add(warningImg);
+      }
+      p.add(new Label(":"));
+      g.add(p, w);
+    } else {
+      g.add(getDisplayName(param), w);
+    }
+  }
+
+  private String getDisplayName(ConfigParameterInfo param) {
+    return param.displayName() != null ? param.displayName() : param.name();
+  }
+
   private void initProjectActions(ConfigInfo info) {
     actionsGrid.clear(true);
     actionsGrid.removeAllRows();
@@ -354,7 +571,7 @@
         maxObjectSizeLimit.getText().trim(),
         Project.SubmitType.valueOf(submitType.getValue(submitType.getSelectedIndex())),
         Project.State.valueOf(state.getValue(state.getSelectedIndex())),
-        new GerritCallback<ConfigInfo>() {
+        getPluginConfigValues(), new GerritCallback<ConfigInfo>() {
           @Override
           public void onSuccess(ConfigInfo result) {
             enableForm();
@@ -369,6 +586,40 @@
         });
   }
 
+  private Map<String, Map<String, ConfigParameterValue>> getPluginConfigValues() {
+    Map<String, Map<String, ConfigParameterValue>> pluginConfigValues =
+        new HashMap<>(pluginConfigWidgets.size());
+    for (Entry<String, Map<String, FocusWidget>> e : pluginConfigWidgets.entrySet()) {
+      Map<String, ConfigParameterValue> values = new HashMap<>(e.getValue().size());
+      pluginConfigValues.put(e.getKey(), values);
+      for (Entry<String, FocusWidget> e2 : e.getValue().entrySet()) {
+        FocusWidget widget = e2.getValue();
+        if (widget instanceof TextBox) {
+          values.put(e2.getKey(), ConfigParameterValue.create()
+              .value(((TextBox) widget).getValue().trim()));
+        } else if (widget instanceof CheckBox) {
+          values.put(e2.getKey(), ConfigParameterValue.create()
+              .value(Boolean.toString(((CheckBox) widget).getValue())));
+        } else if (widget instanceof ListBox) {
+          ListBox listBox = (ListBox) widget;
+          // the inherited value is at index 0,
+          // if it is selected no value should be set on this project
+          String value = listBox.getSelectedIndex() > 0
+              ? listBox.getValue(listBox.getSelectedIndex()) : null;
+          values.put(e2.getKey(), ConfigParameterValue.create()
+              .value(value));
+        } else if (widget instanceof NpTextArea) {
+          String text = ((NpTextArea) widget).getText().trim();
+          values.put(e2.getKey(), ConfigParameterValue.create()
+              .values(text.split("\n")));
+        } else {
+          throw new UnsupportedOperationException("unsupported widget type");
+        }
+      }
+    }
+    return pluginConfigValues;
+  }
+
   public class ProjectDownloadPanel extends DownloadPanel {
     public ProjectDownloadPanel(String project, boolean isAllowsAnonymous) {
       super(project, null, isAllowsAnonymous);
@@ -380,11 +631,21 @@
         if (allowedCommands.contains(DownloadCommand.CHECKOUT)
             || allowedCommands.contains(DownloadCommand.DEFAULT_DOWNLOADS)) {
           commands.add(cmdLinkfactory.new CloneCommandLink());
+          if (Gerrit.getConfig().getSshdAddress() != null && hasUserName()) {
+            commands.add(
+                cmdLinkfactory.new CloneWithCommitMsgHookCommandLink(getProjectKey()));
+          }
         }
       }
     }
   }
 
+  private static boolean hasUserName() {
+    return Gerrit.isSignedIn()
+        && Gerrit.getUserAccount().getUserName() != null
+        && Gerrit.getUserAccount().getUserName().length() > 0;
+  }
+
   private class LabeledWidgetsGrid extends FlexTable {
     private String labelSuffix;
 
@@ -421,5 +682,11 @@
       add(label, true, widget);
     }
 
+    public void add(Widget label, Widget widget) {
+      int row = getRowCount();
+      insertRow(row);
+      setWidget(row, 0, label);
+      setWidget(row, 1, widget);
+    }
   }
 }
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 68ac3a6..3a6cb7d 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
@@ -39,6 +39,7 @@
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwtexpui.globalkey.client.NpTextBox;
 
@@ -167,9 +168,11 @@
       protected void initColumnHeaders() {
         super.initColumnHeaders();
         if (Gerrit.getGitwebLink() != null) {
-          table.setText(0, 3, Util.C.projectRepoBrowser());
+          table.setText(0, ProjectsTable.C_REPO_BROWSER,
+              Util.C.projectRepoBrowser());
           table.getFlexCellFormatter().
-            addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
+            addStyleName(0, ProjectsTable.C_REPO_BROWSER,
+                Gerrit.RESOURCES.css().dataHeader());
         }
       }
 
@@ -187,20 +190,39 @@
         super.insert(row, k);
         if (Gerrit.getGitwebLink() != null) {
           table.getFlexCellFormatter().
-            addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
+            addStyleName(row, ProjectsTable.C_REPO_BROWSER,
+                Gerrit.RESOURCES.css().dataCell());
         }
       }
 
       @Override
       protected void populate(final int row, final ProjectInfo k) {
+        Image state = new Image();
+        switch (k.state()) {
+          case HIDDEN:
+            state.setResource(Gerrit.RESOURCES.redNot());
+            state.setTitle(Util.toLongString(k.state()));
+            table.setWidget(row, ProjectsTable.C_STATE, state);
+            break;
+          case READ_ONLY:
+            state.setResource(Gerrit.RESOURCES.readOnly());
+            state.setTitle(Util.toLongString(k.state()));
+            table.setWidget(row, ProjectsTable.C_STATE, state);
+            break;
+          default:
+            // Intentionally left blank, do not show an icon when active.
+            break;
+        }
+
         FlowPanel fp = new FlowPanel();
         fp.add(new ProjectSearchLink(k.name_key()));
         fp.add(new HighlightingInlineHyperlink(k.name(), link(k), subname));
-        table.setWidget(row, 1, fp);
-        table.setText(row, 2, k.description());
+        table.setWidget(row, ProjectsTable.C_NAME, fp);
+        table.setText(row, ProjectsTable.C_DESCRIPTION, k.description());
         GitwebLink l = Gerrit.getGitwebLink();
         if (l != null) {
-          table.setWidget(row, 3, new Anchor(l.getLinkName(), false, l.toProject(k
+          table.setWidget(row, ProjectsTable.C_REPO_BROWSER,
+              new Anchor(l.getLinkName(), false, l.toProject(k
               .name_key())));
         }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteHover.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteHover.png
index 839e8ef..9fde3fa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteHover.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteHover.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteNormal.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteNormal.png
index ffddb6f..47a1195 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteNormal.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/deleteNormal.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png
index 188e1c1..2927275 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/editText.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/undoNormal.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/undoNormal.png
index 8b0fef9..b780f75 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/undoNormal.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/undoNormal.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
index 60377c4..453a3f2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ActionContext.java
@@ -37,6 +37,8 @@
     Gerrit.ActionContext.prototype = {
       go: Gerrit.go,
       refresh: Gerrit.refresh,
+      refreshMenuBar: Gerrit.refreshMenuBar,
+      showError: Gerrit.showError,
 
       br: function(){return doc.createElement('br')},
       hr: function(){return doc.createElement('hr')},
@@ -93,6 +95,21 @@
         e.onkeypress = stopPropagation;
         return e;
       },
+      select: function(a,s) {
+        var e = doc.createElement('select');
+        for (var i = 0; i < a.length; i++) {
+          var o = doc.createElement('option');
+          if (i==s) {
+            o.setAttributeNode(doc.createAttribute("selected"));
+          }
+          o.appendChild(doc.createTextNode(a[i]));
+          e.appendChild(o);
+        }
+        return e;
+      },
+      selected: function(e) {
+        return e.options[e.selectedIndex].text;
+      },
 
       popup: function(e){this._p=@com.google.gerrit.client.api.PopupHelper::popup(Lcom/google/gerrit/client/api/ActionContext;Lcom/google/gwt/dom/client/Element;)(this,e)},
       hide: function() {
@@ -109,6 +126,7 @@
       post: function(i,b){@com.google.gerrit.client.api.ActionContext::post(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._u,i,b)},
       put: function(i,b){@com.google.gerrit.client.api.ActionContext::put(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._u,i,b)},
       'delete': function(b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._u,b)},
+      del: function(b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._u,b)},
     };
   }-*/;
 
@@ -135,10 +153,30 @@
   }
 
   static final void post(RestApi api, JavaScriptObject in, JavaScriptObject cb) {
+    if (NativeString.is(in)) {
+      post(api, ((NativeString) in).asString(), cb);
+    } else {
+      api.post(in, wrap(cb));
+    }
+  }
+
+  static final void post(RestApi api, String in, JavaScriptObject cb) {
     api.post(in, wrap(cb));
   }
 
+  static final void put(RestApi api, JavaScriptObject cb) {
+    api.put(wrap(cb));
+  }
+
   static final void put(RestApi api, JavaScriptObject in, JavaScriptObject cb) {
+    if (NativeString.is(in)) {
+      put(api, ((NativeString) in).asString(), cb);
+    } else {
+      api.put(in, wrap(cb));
+    }
+  }
+
+  static final void put(RestApi api, String in, JavaScriptObject cb) {
     api.put(in, wrap(cb));
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
index f3fb1fa..976dc0c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
@@ -14,8 +14,10 @@
 
 package com.google.gerrit.client.api;
 
+import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.user.client.History;
 import com.google.gwt.user.client.Window;
 
@@ -25,30 +27,57 @@
   public static void init() {
     init0();
     ActionContext.init();
+    HtmlTemplate.init();
+    Plugin.init();
+    addHistoryHook();
   }
 
   private static native void init0() /*-{
     var serverUrl = @com.google.gwt.core.client.GWT::getHostPageBaseURL()();
-    var Plugin = function (name){this.name = name};
-    var Gerrit = {
+    var ScreenDefinition = @com.google.gerrit.client.api.ExtensionScreen.Definition::TYPE;
+    $wnd.Gerrit = {
+      JsonString: @com.google.gerrit.client.rpc.NativeString::TYPE,
+      events: {},
+      plugins: {},
+      screens: {},
+      change_actions: {},
+      revision_actions: {},
+      project_actions: {},
+
       getPluginName: @com.google.gerrit.client.api.ApiGlue::getPluginName(),
+      injectCss: @com.google.gwt.dom.client.StyleInjector::inject(Ljava/lang/String;),
       install: function (f) {
-        var p = new Plugin(this.getPluginName());
-        @com.google.gerrit.client.api.ApiGlue::install(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(f,p);
+        var p = this._getPluginByUrl(@com.google.gerrit.client.api.PluginName::getCallerUrl()());
+        @com.google.gerrit.client.api.ApiGlue::install(
+            Lcom/google/gwt/core/client/JavaScriptObject;
+            Lcom/google/gerrit/client/api/Plugin;)
+          (f,p);
+      },
+      installGwt: function(u){return this._getPluginByUrl(u)},
+      _getPluginByUrl: function(u) {
+        return u.indexOf(serverUrl) == 0
+          ? this.plugins[u.substring(serverUrl.length)]
+          : this.plugins[u]
       },
 
       go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
       refresh: @com.google.gerrit.client.api.ApiGlue::refresh(),
+      refreshMenuBar: @com.google.gerrit.client.api.ApiGlue::refreshMenuBar(),
+      showError: @com.google.gerrit.client.api.ApiGlue::showError(Ljava/lang/String;),
 
-      change_actions: {},
-      revision_actions: {},
-      project_actions: {},
+      on: function (e,f){(this.events[e] || (this.events[e]=[])).push(f)},
       onAction: function (t,n,c){this._onAction(this.getPluginName(),t,n,c)},
       _onAction: function (p,t,n,c) {
         var i = p+'~'+n;
         if ('change' == t) this.change_actions[i]=c;
         else if ('revision' == t) this.revision_actions[i]=c;
         else if ('project' == t) this.project_actions[i]=c;
+        else if ('screen' == t) _screen(p,t,c);
+      },
+      screen: function(r,c){this._screen(this.getPluginName(),r,c)},
+      _screen: function(p,r,c){
+        var s = new ScreenDefinition(r,c);
+        (this.screens[p] || (this.screens[p]=[])).push(s);
       },
 
       url: function (d) {
@@ -57,51 +86,94 @@
         return serverUrl;
       },
 
-      _api: function(u) {return @com.google.gerrit.client.rpc.RestApi::new(Ljava/lang/String;)(u)},
-      get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
-      post: function(u,i,b){@com.google.gerrit.client.api.ActionContext::post(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
-      put: function(u,i,b){@com.google.gerrit.client.api.ActionContext::put(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
-      'delete': function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
-    };
-
-    Plugin.prototype = {
-      getPluginName: function(){return this.name},
-      go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
-      refresh: Gerrit.refresh,
-      onAction: function(t,n,c) {Gerrit._onAction(this.name,t,n,c)},
-
-      url: function (d) {
-        var u = serverUrl + 'plugins/' + this.name + '/';
-        if (d && d.length > 0) u += d.charAt(0)=='/' ? d.substring(1) : d;
-        return u;
-      },
-
-      _api: function(d) {
-        var u = 'plugins/' + this.name + '/';
-        if (d && d.length > 0) u += d.charAt(0)=='/' ? d.substring(1) : d;
+      _api: function(u) {
         return @com.google.gerrit.client.rpc.RestApi::new(Ljava/lang/String;)(u);
       },
-
-      get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
-      post: function(u,i,b){@com.google.gerrit.client.api.ActionContext::post(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
-      put: function(u,i,b){@com.google.gerrit.client.api.ActionContext::put(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
-      'delete': function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
+      get: function(u,b) {
+        @com.google.gerrit.client.api.ActionContext::get(
+            Lcom/google/gerrit/client/rpc/RestApi;
+            Lcom/google/gwt/core/client/JavaScriptObject;)
+          (this._api(u), b);
+      },
+      post: function(u,i,b) {
+        if (typeof i == 'string') {
+          @com.google.gerrit.client.api.ActionContext::post(
+              Lcom/google/gerrit/client/rpc/RestApi;
+              Ljava/lang/String;
+              Lcom/google/gwt/core/client/JavaScriptObject;)
+            (this._api(u), i, b);
+        } else {
+          @com.google.gerrit.client.api.ActionContext::post(
+              Lcom/google/gerrit/client/rpc/RestApi;
+              Lcom/google/gwt/core/client/JavaScriptObject;
+              Lcom/google/gwt/core/client/JavaScriptObject;)
+            (this._api(u), i, b);
+        }
+      },
+      put: function(u,i,b) {
+        if (b) {
+          if (typeof i == 'string') {
+            @com.google.gerrit.client.api.ActionContext::put(
+                Lcom/google/gerrit/client/rpc/RestApi;
+                Ljava/lang/String;
+                Lcom/google/gwt/core/client/JavaScriptObject;)
+              (this._api(u), i, b);
+          } else {
+            @com.google.gerrit.client.api.ActionContext::put(
+                Lcom/google/gerrit/client/rpc/RestApi;
+                Lcom/google/gwt/core/client/JavaScriptObject;
+                Lcom/google/gwt/core/client/JavaScriptObject;)
+              (this._api(u), i, b);
+          }
+        } else {
+          @com.google.gerrit.client.api.ActionContext::put(
+              Lcom/google/gerrit/client/rpc/RestApi;
+              Lcom/google/gwt/core/client/JavaScriptObject;)
+            (this._api(u), i);
+        }
+      },
+      'delete': function(u,b) {
+        @com.google.gerrit.client.api.ActionContext::delete(
+            Lcom/google/gerrit/client/rpc/RestApi;
+            Lcom/google/gwt/core/client/JavaScriptObject;)
+          (this._api(u), b);
+      },
+      del: function(u,b) {
+        @com.google.gerrit.client.api.ActionContext::delete(
+            Lcom/google/gerrit/client/rpc/RestApi;
+            Lcom/google/gwt/core/client/JavaScriptObject;)
+          (this._api(u), b);
+      },
     };
-
-    $wnd.Gerrit = Gerrit;
   }-*/;
 
-  private static void install(JavaScriptObject cb, JavaScriptObject p) {
+  /** Install deprecated {@code gerrit_addHistoryHook()} function. */
+  private static native void addHistoryHook() /*-{
+    $wnd.gerrit_addHistoryHook = function(h) {
+      var p = @com.google.gwt.user.client.Window.Location::getPath()();
+      $wnd.Gerrit.on('history', function(t) { h(p + "#" + t) })
+     };
+  }-*/;
+
+  private static void install(JavaScriptObject cb, Plugin p) throws Exception {
     try {
-      pluginName = PluginName.get();
+      pluginName = p.name();
       invoke(cb, p);
+      p._initialized();
+    } catch (Exception e) {
+      p.failure(e);
+      throw e;
     } finally {
       pluginName = null;
+      PluginLoader.loaded();
     }
   }
 
   private static final String getPluginName() {
-    return pluginName != null ? pluginName : PluginName.get();
+    if (pluginName != null) {
+      return pluginName;
+    }
+    return PluginName.fromUrl(PluginName.getCallerUrl());
   }
 
   private static final void go(String urlOrToken) {
@@ -118,10 +190,36 @@
     Gerrit.display(History.getToken());
   }
 
+  private static final void refreshMenuBar() {
+    Gerrit.refreshMenuBar();
+  }
+
+  private static final void showError(String message) {
+    new ErrorDialog(message).center();
+  }
+
   static final native void invoke(JavaScriptObject f) /*-{ f(); }-*/;
   static final native void invoke(JavaScriptObject f, JavaScriptObject a) /*-{ f(a); }-*/;
+  static final native void invoke(JavaScriptObject f, JavaScriptObject a, JavaScriptObject b) /*-{ f(a,b) }-*/;
   static final native void invoke(JavaScriptObject f, String a) /*-{ f(a); }-*/;
 
+  public static final void fireEvent(String event, String a) {
+    JsArray<JavaScriptObject> h = getEventHandlers(event);
+    for (int i = 0; i < h.length(); i++) {
+      invoke(h.get(i), a);
+    }
+  }
+
+  static final void fireEvent(String event, JavaScriptObject a, JavaScriptObject b) {
+    JsArray<JavaScriptObject> h = getEventHandlers(event);
+    for (int i = 0; i < h.length(); i++) {
+      invoke(h.get(i), a, b);
+    }
+  }
+
+  static final native JsArray<JavaScriptObject> getEventHandlers(String e)
+  /*-{ return $wnd.Gerrit.events[e] || [] }-*/;
+
   private ApiGlue() {
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ChangeGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ChangeGlue.java
index 7967147..a5243ae 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ChangeGlue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ChangeGlue.java
@@ -18,10 +18,26 @@
 import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
 
 public class ChangeGlue {
+  public static void fireShowChange(ChangeInfo change, RevisionInfo rev) {
+    ApiGlue.fireEvent("showchange", change, rev);
+  }
+
+  public static boolean onSubmitChange(ChangeInfo change, RevisionInfo rev) {
+    JsArray<JavaScriptObject> h = ApiGlue.getEventHandlers("submitchange");
+    for (int i = 0; i < h.length(); i++) {
+      if (!invoke(h.get(i), change, rev)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   public static void onAction(
       ChangeInfo change,
       ActionInfo action,
@@ -43,6 +59,10 @@
     return $wnd.Gerrit.change_actions[id];
   }-*/;
 
+  private static final native boolean invoke(JavaScriptObject h,
+      ChangeInfo a, RevisionInfo r)
+  /*-{ return h(a,r) }-*/;
+
   private ChangeGlue() {
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
index fcd6056..53a3784 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/DefaultActions.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -44,7 +45,8 @@
     invoke(action, api, cb);
   }
 
-  static void invokeProjectAction(ActionInfo action, RestApi api) {
+  static void invokeProjectAction(final Project.NameKey project,
+      ActionInfo action, RestApi api) {
     AsyncCallback<JavaScriptObject> cb = new GerritCallback<JavaScriptObject>() {
       @Override
       public void onSuccess(JavaScriptObject msg) {
@@ -54,7 +56,7 @@
             Window.alert(str.asString());
           }
         }
-        Gerrit.display(PageLinks.ADMIN_PROJECTS);
+        Gerrit.display(PageLinks.toProject(project));
       }
     };
     invoke(action, api, cb);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionScreen.java
new file mode 100644
index 0000000..98b06bb
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionScreen.java
@@ -0,0 +1,142 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.api;
+
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.ui.Screen;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Element;
+
+/** Screen contributed by a plugin. */
+public class ExtensionScreen extends Screen {
+  private Context ctx;
+
+  public ExtensionScreen(String token) {
+    if (token.contains("?")) {
+      token = token.substring(0, token.indexOf('?'));
+    }
+    String name;
+    String rest;
+    int s = token.indexOf('/');
+    if (0 < s) {
+      name = token.substring(0, s);
+      rest = token.substring(s + 1);
+    } else {
+      name = token;
+      rest = "";
+    }
+    ctx = create(name, rest);
+  }
+
+  private Context create(String name, String rest) {
+    for (Definition def : Natives.asList(Definition.get(name))) {
+      JsArrayString m = def.match(rest);
+      if (m != null) {
+        return Context.create(def, this, m);
+      }
+    }
+    return null;
+  }
+
+  public boolean isFound() {
+    return ctx != null;
+  }
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    setHeaderVisible(false);
+    ctx.onLoad();
+  }
+
+  @Override
+  protected void onUnload() {
+    super.onUnload();
+    for (JavaScriptObject u : Natives.asList(ctx.unload())) {
+      ApiGlue.invoke(u);
+    }
+  }
+
+  static class Definition extends JavaScriptObject {
+    static final JavaScriptObject TYPE = init();
+    private static native JavaScriptObject init() /*-{
+      function ScreenDefinition(r, c) {
+        this.pattern = r;
+        this.onLoad = c;
+      };
+      return ScreenDefinition;
+    }-*/;
+
+    static native JsArray<Definition> get(String n)
+    /*-{ return $wnd.Gerrit.screens[n] || [] }-*/;
+
+    final native JsArrayString match(String t)
+    /*-{
+      var p = this.pattern;
+      if (p instanceof $wnd.RegExp) {
+        var m = p.exec(t);
+        return m && m[0] == t ? m : null;
+      }
+      return p == t ? [t] : null;
+    }-*/;
+
+    protected Definition() {
+    }
+  }
+
+  static class Context extends JavaScriptObject {
+    static final Context create(
+        Definition def,
+        ExtensionScreen view,
+        JsArrayString match) {
+      return create(TYPE, def, view, view.getBody().getElement(), match);
+    }
+
+    final native void onLoad() /*-{ this._d.onLoad(this) }-*/;
+    final native JsArray<JavaScriptObject> unload() /*-{ return this._u }-*/;
+
+    private static final native Context create(
+        JavaScriptObject T,
+        Definition d,
+        ExtensionScreen s,
+        Element e,
+        JsArrayString m)
+    /*-{ return new T(d,s,e,m) }-*/;
+
+    private static final JavaScriptObject TYPE = init();
+    private static final native JavaScriptObject init() /*-{
+      var T = function(d,s,e,m) {
+        this._d = d;
+        this._s = s;
+        this._u = [];
+        this.body = e;
+        this.token = m[0];
+        this.token_match = m;
+      };
+      T.prototype = {
+        setTitle: function(t){this._s.@com.google.gerrit.client.ui.Screen::setPageTitle(Ljava/lang/String;)(t)},
+        setWindowTitle: function(t){this._s.@com.google.gerrit.client.ui.Screen::setWindowTitle(Ljava/lang/String;)(t)},
+        show: function(){$entry(this._s.@com.google.gwtexpui.user.client.View::display()())},
+        onUnload: function(f){this._u.push(f)},
+      };
+      return T;
+    }-*/;
+
+    protected Context() {
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/HtmlTemplate.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/HtmlTemplate.java
new file mode 100644
index 0000000..95757ab
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/HtmlTemplate.java
@@ -0,0 +1,157 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.api;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.user.client.DOM;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+final class HtmlTemplate {
+  static native void init() /*-{
+    var ElementSet = function(r,e) {
+      this.root = r;
+      this.elements = e;
+    };
+    ElementSet.prototype = {
+      clear: function() {
+        this.root = null;
+        this.elements = null;
+      },
+    };
+
+    $wnd.Gerrit.css = @com.google.gerrit.client.api.HtmlTemplate::css(Ljava/lang/String;);
+    $wnd.Gerrit.html = function(h,r,w) {
+      var i = {};
+      if (r) {
+        h = h.replace(
+          /\sid=['"]\{([a-z_][a-z0-9_]*)\}['"]|\{([a-z0-9._-]+)\}/gi,
+          function(m,a,b) {
+            if (a)
+              return @com.google.gerrit.client.api.HtmlTemplate::id(
+                  Lcom/google/gerrit/client/api/HtmlTemplate$IdMap;
+                  Ljava/lang/String;)
+                (i,a);
+            return @com.google.gerrit.client.api.HtmlTemplate::html(
+                Lcom/google/gerrit/client/api/HtmlTemplate$ReplacementMap;
+                Ljava/lang/String;)
+              (r,b);
+          });
+      }
+      var e = @com.google.gerrit.client.api.HtmlTemplate::parseHtml(
+          Ljava/lang/String;Lcom/google/gerrit/client/api/HtmlTemplate$IdMap;
+          Lcom/google/gerrit/client/api/HtmlTemplate$ReplacementMap;
+          Z)
+        (h,i,r,!!w);
+      return w ? new ElementSet(e,i) : e;
+    };
+  }-*/;
+
+  private static final String css(String css) {
+    String name = DOM.createUniqueId();
+    StyleInjector.inject("." + name + "{" + css + "}");
+    return name;
+  }
+
+  private static final String id(IdMap idMap, String key) {
+    String id = DOM.createUniqueId();
+    idMap.put(id, key);
+    return " id='" + id + "'";
+  }
+
+  private static final String html(ReplacementMap opts, String id) {
+    int d = id.indexOf('.');
+    if (0 < d) {
+      String name = id.substring(0, d);
+      String rest = id.substring(d + 1);
+      return html(opts.map(name), rest);
+    }
+    return new SafeHtmlBuilder().append(opts.str(id)).asString();
+  }
+
+  private static final Node parseHtml(
+      String html,
+      IdMap ids,
+      ReplacementMap opts,
+      boolean wantElements) {
+    Element div = Document.get().createDivElement();
+    div.setInnerHTML(html);
+    if (!ids.isEmpty()) {
+      attachHandlers(div, ids, opts, wantElements);
+    }
+    if (div.getChildCount() == 1) {
+      return div.getFirstChild();
+    }
+    return div;
+  }
+
+  private static void attachHandlers(
+      Element e,
+      IdMap ids,
+      ReplacementMap opts,
+      boolean wantElements) {
+    if (e.getId() != null) {
+      String key = ids.get(e.getId());
+      if (key != null) {
+        ids.remove(e.getId());
+        if (wantElements) {
+          ids.put(key, e);
+        }
+        e.setId(null);
+        opts.map(key).attachHandlers(e);
+      }
+    }
+    for (Element c = e.getFirstChildElement(); c != null;) {
+      attachHandlers(c, ids, opts, wantElements);
+      c = c.getNextSiblingElement();
+    }
+  }
+
+  private static class ReplacementMap extends JavaScriptObject {
+    final native ReplacementMap map(String n) /*-{ return this[n] }-*/;
+    final native String str(String n) /*-{ return ''+this[n] }-*/;
+    final native void attachHandlers(Element e) /*-{
+      for (var k in this) {
+        var f = this[k];
+        if (k.substring(0, 2) == 'on' && typeof f == 'function')
+          e[k] = f;
+      }
+    }-*/;
+
+    protected ReplacementMap() {
+    }
+  }
+
+  private static class IdMap extends JavaScriptObject {
+    final native String get(String i) /*-{ return this[i] }-*/;
+    final native void remove(String i) /*-{ delete this[i] }-*/;
+    final native void put(String i, String k) /*-{ this[i] = k }-*/;
+    final native void put(String k, Element e) /*-{ this[k] = e }-*/;
+    final native boolean isEmpty() /*-{
+      for (var i in this)
+        return false;
+      return true;
+    }-*/;
+
+    protected IdMap() {
+    }
+  }
+
+  private HtmlTemplate() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
new file mode 100644
index 0000000..7ef022a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
@@ -0,0 +1,81 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.api;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+final class Plugin extends JavaScriptObject {
+  private static final JavaScriptObject TYPE = createType();
+
+  static Plugin create(String url) {
+    int s = "plugins/".length();
+    int e = url.indexOf('/', s);
+    String name = url.substring(s, e);
+    return create(TYPE, url, name);
+  }
+
+  final native String url() /*-{ return this._scriptUrl }-*/;
+  final native String name() /*-{ return this.name }-*/;
+
+  final native boolean loaded() /*-{ return this._success || this._failure != null }-*/;
+  final native Exception failure() /*-{ return this._failure }-*/;
+  final native void failure(Exception e) /*-{ this._failure = e }-*/;
+  final native boolean success() /*-{ return this._success || false }-*/;
+  final native void _initialized() /*-{ this._success = true }-*/;
+
+  private static native Plugin create(JavaScriptObject T, String u, String n)
+  /*-{ return new T(u,n) }-*/;
+
+  private static native JavaScriptObject createType() /*-{
+    function Plugin(u, n) {
+      this._scriptUrl = u;
+      this.name = n;
+    }
+    return Plugin;
+  }-*/;
+
+  static native void init() /*-{
+    var G = $wnd.Gerrit;
+    @com.google.gerrit.client.api.Plugin::TYPE.prototype = {
+      getPluginName: function(){return this.name},
+      go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
+      refresh: @com.google.gerrit.client.api.ApiGlue::refresh(),
+      refreshMenuBar: @com.google.gerrit.client.api.ApiGlue::refreshMenuBar(),
+      showError: @com.google.gerrit.client.api.ApiGlue::showError(Ljava/lang/String;),
+      on: function(e,f){G.on(e,f)},
+      onAction: function(t,n,c){G._onAction(this.name,t,n,c)},
+      screen: function(p,c){G._screen(this.name,p,c)},
+
+      url: function (u){return G.url(this._url(u))},
+      get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
+      post: function(u,i,b){@com.google.gerrit.client.api.ActionContext::post(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
+      put: function(u,i,b){@com.google.gerrit.client.api.ActionContext::put(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
+      'delete': function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
+      del: function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
+
+      _loadedGwt: function(){@com.google.gerrit.client.api.PluginLoader::loaded()()},
+      _api: function(u){return @com.google.gerrit.client.rpc.RestApi::new(Ljava/lang/String;)(this._url(u))},
+      _url: function (d) {
+        var u = 'plugins/' + this.name + '/';
+        if (d && d.length > 0)
+          return u + (d.charAt(0)=='/' ? d.substring(1) : d);
+        return u;
+      },
+    };
+  }-*/;
+
+  protected Plugin() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginLoader.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginLoader.java
new file mode 100644
index 0000000..8b5c93e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginLoader.java
@@ -0,0 +1,185 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.api;
+
+import com.google.gerrit.client.ErrorDialog;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gwt.core.client.Callback;
+import com.google.gwt.core.client.CodeDownloadException;
+import com.google.gwt.core.client.ScriptInjector;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.DialogBox;
+import com.google.gwtexpui.progress.client.ProgressBar;
+
+import java.util.List;
+
+/** Loads JavaScript plugins with a progress meter visible. */
+public class PluginLoader extends DialogBox {
+  private static final int MAX_LOAD_TIME_MILLIS = 5000;
+  private static PluginLoader self;
+
+  public static void load(List<String> plugins, final String token) {
+    if (plugins == null || plugins.isEmpty()) {
+      Gerrit.display(token);
+    } else {
+      self = new PluginLoader(token);
+      self.load(plugins);
+      self.startTimers();
+      self.center();
+    }
+  }
+
+  static void loaded() {
+    self.loadedOne();
+  }
+
+  private final String token;
+  private ProgressBar progress;
+  private Timer show;
+  private Timer update;
+  private Timer timeout;
+  private boolean visible;
+
+  private PluginLoader(String tokenToDisplay) {
+    super(/* auto hide */false, /* modal */true);
+    token = tokenToDisplay;
+    progress = new ProgressBar(Gerrit.C.loadingPlugins());
+
+    setStyleName(Gerrit.RESOURCES.css().errorDialog());
+    addStyleName(Gerrit.RESOURCES.css().loadingPluginsDialog());
+  }
+
+  private void load(List<String> pluginUrls) {
+    for (String url : pluginUrls) {
+      Plugin plugin = Plugin.create(url);
+      plugins().put(url, plugin);
+      ScriptInjector.fromUrl(url)
+        .setWindow(ScriptInjector.TOP_WINDOW)
+        .setCallback(new LoadCallback(plugin))
+        .inject();
+    }
+  }
+
+  private void startTimers() {
+    show = new Timer() {
+      @Override
+      public void run() {
+        setText(Window.getTitle());
+        setWidget(progress);
+        setGlassEnabled(true);
+        getGlassElement().addClassName(Gerrit.RESOURCES.css().errorDialogGlass());
+        hide(true);
+        center();
+        visible = true;
+      }
+    };
+    show.schedule(500);
+
+    update = new Timer() {
+      private int cycle;
+
+      @Override
+      public void run() {
+        progress.setValue(100 * ++cycle * 250 / MAX_LOAD_TIME_MILLIS);
+      }
+    };
+    update.scheduleRepeating(250);
+
+    timeout = new Timer() {
+      @Override
+      public void run() {
+        finish();
+      }
+    };
+    timeout.schedule(MAX_LOAD_TIME_MILLIS);
+  }
+
+  private void loadedOne() {
+    boolean done = true;
+    for (Plugin plugin : Natives.asList(plugins().values())) {
+      done &= plugin.loaded();
+    }
+    if (done) {
+      finish();
+    }
+  }
+
+  private void finish() {
+    show.cancel();
+    update.cancel();
+    timeout.cancel();
+    self = null;
+
+    if (!hadFailures()) {
+      if (visible) {
+        progress.setValue(100);
+        new Timer() {
+          @Override
+          public void run() {
+            hide(true);
+          }
+        }.schedule(250);
+      } else {
+        hide(true);
+      }
+    }
+
+    Gerrit.display(token);
+  }
+
+  private boolean hadFailures() {
+    boolean failed = false;
+    for (Plugin plugin : Natives.asList(plugins().values())) {
+      if (!plugin.success()) {
+        failed = true;
+
+        Exception e = plugin.failure();
+        String msg;
+        if (e != null && e instanceof CodeDownloadException) {
+          msg = Gerrit.M.cannotDownloadPlugin(plugin.url());
+        } else {
+          msg = Gerrit.M.pluginFailed(plugin.name());
+        }
+        hide(true);
+        new ErrorDialog(msg).center();
+      }
+    }
+    return failed;
+  }
+
+  private static native NativeMap<Plugin> plugins()
+  /*-{ return $wnd.Gerrit.plugins }-*/;
+
+  private class LoadCallback implements Callback<Void, Exception> {
+    private final Plugin plugin;
+
+    LoadCallback(Plugin plugin) {
+      this.plugin = plugin;
+    }
+
+    @Override
+    public void onSuccess(Void result) {
+    }
+
+    @Override
+    public void onFailure(Exception reason) {
+      plugin.failure(reason);
+      loadedOne();
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java
index 5fc87d5..2c1f2c7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/PluginName.java
@@ -31,14 +31,29 @@
 class PluginName {
   private static final String UNKNOWN = "<unknown>";
 
-  static String get() {
-    return GWT.<PluginName> create(PluginName.class).guessName();
+  private static String baseUrl() {
+    return GWT.getHostPageBaseURL() + "plugins/";
   }
 
-  String guessName() {
+  static String getCallerUrl() {
+    return GWT.<PluginName> create(PluginName.class).findCallerUrl();
+  }
+
+  static String fromUrl(String url) {
+    String baseUrl = baseUrl();
+    if (url != null && url.startsWith(baseUrl)) {
+      int s = url.indexOf('/', baseUrl.length());
+      if (s > 0) {
+        return url.substring(baseUrl.length(), s);
+      }
+    }
+    return UNKNOWN;
+  }
+
+  String findCallerUrl() {
     JavaScriptException err = makeException();
     if (hasStack(err)) {
-      return PluginNameMoz.guessName(err);
+      return PluginNameMoz.getUrl(err);
     }
 
     String baseUrl = baseUrl();
@@ -46,19 +61,12 @@
     for (int i = trace.length - 1; i >= 0; i--) {
       String u = trace[i].getFileName();
       if (u != null && u.startsWith(baseUrl)) {
-        int s = u.indexOf('/', baseUrl.length());
-        if (s > 0) {
-          return u.substring(baseUrl.length(), s);
-        }
+        return u;
       }
     }
     return UNKNOWN;
   }
 
-  private static String baseUrl() {
-    return GWT.getHostPageBaseURL() + "plugins/";
-  }
-
   private static StackTraceElement[] getTrace(JavaScriptException err) {
     StackTraceCreator.fillInStackTrace(err);
     return err.getStackTrace();
@@ -72,21 +80,22 @@
 
   /** Extracts URL from the stack frame. */
   static class PluginNameMoz extends PluginName {
-    String guessName() {
-      return guessName(makeException());
+    String findCallerUrl() {
+      return getUrl(makeException());
     }
 
-    static String guessName(JavaScriptException e) {
+    private static String getUrl(JavaScriptException e) {
       String baseUrl = baseUrl();
       JsArrayString stack = getStack(e);
       for (int i = stack.length() - 1; i >= 0; i--) {
         String frame = stack.get(i);
         int at = frame.indexOf(baseUrl);
         if (at >= 0) {
-          int s = frame.indexOf('/', at + baseUrl.length());
-          if (s > 0) {
-            return frame.substring(at + baseUrl.length(), s);
+          int end = frame.indexOf(':', at + baseUrl.length());
+          if (end < 0) {
+            end = frame.length();
           }
+          return frame.substring(at, end);
         }
       }
       return UNKNOWN;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java
index b95f4e0..bce691c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ProjectGlue.java
@@ -35,7 +35,7 @@
       c.button(button);
       ApiGlue.invoke(f, c);
     } else {
-      DefaultActions.invokeProjectAction(action, api);
+      DefaultActions.invokeProjectAction(project, action, api);
     }
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
index 948ca3c..92444d0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.java
@@ -130,15 +130,15 @@
   }
 
   private static TreeSet<String> filterNonCore(NativeMap<ActionInfo> m) {
-    TreeSet<String> ids = new TreeSet<String>(m.keySet());
+    TreeSet<String> ids = new TreeSet<>(m.keySet());
     for (String id : CORE) {
       ids.remove(id);
     }
     return ids;
   }
 
-  void setSubmitEnabled(boolean ok) {
-    submit.setVisible(ok && canSubmit);
+  void setSubmitEnabled() {
+    submit.setVisible(canSubmit);
   }
 
   boolean isSubmitEnabled() {
@@ -183,7 +183,7 @@
 
   @UiHandler("submit")
   void onSubmit(ClickEvent e) {
-    SubmitAction.call(changeId, revision);
+    SubmitAction.call(changeInfo, changeInfo.revision(revision));
   }
 
   @UiHandler("cherrypick")
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
index a4b19ff..160ffc1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Actions.ui.xml
@@ -71,7 +71,6 @@
       background-color: #999;
       background-image: -webkit-linear-gradient(top, #999, #999);
     }
-
   </ui:style>
 
   <g:FlowPanel>
@@ -102,7 +101,7 @@
     </g:Button>
 
     <g:Button ui:field='submit' styleName='{style.submit}' visible='false'>
-      <div><ui:msg>Submit</ui:msg></div>
+      <div><ui:msg>Merge Change</ui:msg></div>
     </g:Button>
   </g:FlowPanel>
 </ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
index db3696f..32c69c5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.java
@@ -18,10 +18,10 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.actions.ActionInfo;
+import com.google.gerrit.client.api.ChangeGlue;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
-import com.google.gerrit.client.changes.ChangeInfo.MergeableInfo;
 import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
 import com.google.gerrit.client.changes.ChangeList;
@@ -47,7 +47,7 @@
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.client.ui.UserActivityMonitor;
 import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -56,7 +56,6 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.JsArrayString;
-import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.dom.client.SelectElement;
@@ -73,12 +72,12 @@
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.EventListener;
 import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.ListBox;
 import com.google.gwt.user.client.ui.ToggleButton;
-import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
 import com.google.gwtexpui.globalkey.client.KeyCommandSet;
@@ -106,7 +105,7 @@
   }
 
   static ChangeScreen2 get(NativeEvent in) {
-    com.google.gwt.user.client.Element e = in.getEventTarget().cast();
+    Element e = in.getEventTarget().cast();
     for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
       EventListener l = DOM.getEventListener(e);
       if (l instanceof ChangeScreen2) {
@@ -124,7 +123,7 @@
 
   private KeyCommandSet keysNavigation;
   private KeyCommandSet keysAction;
-  private List<HandlerRegistration> handlers = new ArrayList<HandlerRegistration>(4);
+  private List<HandlerRegistration> handlers = new ArrayList<>(4);
   private UpdateCheckTimer updateCheck;
   private Timestamp lastDisplayedUpdate;
   private UpdateAvailableBar updateAvailable;
@@ -134,20 +133,18 @@
   @UiField HTMLPanel headerLine;
   @UiField Style style;
   @UiField ToggleButton star;
-  @UiField Reload reload;
-  @UiField AnchorElement permalink;
+  @UiField Anchor permalink;
 
-  @UiField Element reviewersText;
+  @UiField Element ccText;
   @UiField Reviewers reviewers;
-  @UiField Element changeIdText;
-  @UiField Element ownerText;
+  @UiField InlineHyperlink ownerLink;
   @UiField Element statusText;
-  @UiField Image projectQuery;
+  @UiField Image projectSettings;
   @UiField InlineHyperlink projectLink;
   @UiField InlineHyperlink branchLink;
+  @UiField Element strategy;
   @UiField Element submitActionText;
   @UiField Element notMergeable;
-  @UiField CopyableLabel idText;
   @UiField Topic topic;
   @UiField Element actionText;
   @UiField Element actionDate;
@@ -161,17 +158,20 @@
   @UiField History history;
 
   @UiField Button includedIn;
-  @UiField Button revisions;
+  @UiField Button patchSets;
+  @UiField Element patchSetsText;
   @UiField Button download;
   @UiField Button reply;
+  @UiField Button openAll;
   @UiField Button expandAll;
   @UiField Button collapseAll;
   @UiField Button editMessage;
   @UiField QuickApprove quickApprove;
+
   private ReplyAction replyAction;
   private EditMessageAction editMessageAction;
   private IncludedInAction includedInAction;
-  private RevisionsAction revisionsAction;
+  private PatchSetsAction patchSetsAction;
   private DownloadAction downloadAction;
 
   public ChangeScreen2(Change.Id changeId, String base, String revision, boolean openReplyBox) {
@@ -211,6 +211,9 @@
 
   @Override
   protected void onUnload() {
+    if (replyAction != null) {
+      replyAction.hide();
+    }
     if (updateCheck != null) {
       updateCheck.cancel();
       updateCheck = null;
@@ -229,7 +232,7 @@
     Resources.I.style().ensureInjected();
     star.setVisible(Gerrit.isSignedIn());
     labels.init(style, statusText);
-    reviewers.init(style, reviewersText);
+    reviewers.init(style, ccText);
 
     keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
     keysNavigation.add(new KeyCommand(0, 'u', Util.C.upToChangeList()) {
@@ -241,9 +244,20 @@
     keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReloadChange()) {
       @Override
       public void onKeyPress(final KeyPressEvent event) {
-        reload.reload();
+        Gerrit.display(PageLinks.toChange(changeId));
       }
     });
+    keysNavigation.add(new KeyCommand(0, 'n', Util.C.keyNextPatchSet()) {
+        @Override
+        public void onKeyPress(final KeyPressEvent event) {
+          gotoSibling(1);
+        }
+      }, new KeyCommand(0, 'p', Util.C.keyPreviousPatchSet()) {
+        @Override
+        public void onKeyPress(final KeyPressEvent event) {
+          gotoSibling(-1);
+        }
+      });
 
     keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
     keysAction.add(new KeyCommand(0, 'a', Util.C.keyPublishComments()) {
@@ -284,6 +298,30 @@
     }
   }
 
+  private void gotoSibling(final int offset) {
+    if (offset > 0 && changeInfo.current_revision().equals(revision)) {
+      return;
+    }
+
+    if (offset < 0 && changeInfo.revision(revision)._number() == 1) {
+      return;
+    }
+
+    JsArray<RevisionInfo> revisions = changeInfo.revisions().values();
+    RevisionInfo.sortRevisionInfoByNumber(revisions);
+    for (int i = 0; i < revisions.length(); i++) {
+      if (revision.equals(revisions.get(i).name())) {
+        if (0 <= i + offset && i + offset < revisions.length()) {
+          Gerrit.display(PageLinks.toChange(
+              new PatchSet.Id(changeInfo.legacy_id(),
+              revisions.get(i + offset)._number())));
+          return;
+        }
+        return;
+      }
+    }
+  }
+
   private void initIncludedInAction(ChangeInfo info) {
     if (info.status() == Status.MERGED) {
       includedInAction = new IncludedInAction(
@@ -294,9 +332,22 @@
   }
 
   private void initRevisionsAction(ChangeInfo info, String revision) {
-    revisionsAction = new RevisionsAction(
+    int currentPatchSet;
+    if (info.current_revision() != null
+        && info.revisions().containsKey(info.current_revision())) {
+      currentPatchSet = info.revision(info.current_revision())._number();
+    } else {
+      JsArray<RevisionInfo> revList = info.revisions().values();
+      RevisionInfo.sortRevisionInfoByNumber(revList);
+      currentPatchSet = revList.get(revList.length() - 1)._number();
+    }
+
+    int currentlyViewedPatchSet = info.revision(revision)._number();
+    patchSetsText.setInnerText(Resources.M.patchSets(
+        currentlyViewedPatchSet, currentPatchSet));
+    patchSetsAction = new PatchSetsAction(
         info.legacy_id(), revision,
-        style, headerLine, revisions);
+        style, headerLine, patchSets);
   }
 
   private void initDownloadAction(ChangeInfo info, String revision) {
@@ -305,16 +356,16 @@
   }
 
   private void initProjectLinks(final ChangeInfo info) {
-    projectQuery.addDomHandler(new ClickHandler() {
+    projectSettings.addDomHandler(new ClickHandler() {
       @Override
       public void onClick(ClickEvent event) {
         Gerrit.display(
-            PageLinks.toProjectDefaultDashboard(info.project_name_key()));
+            PageLinks.toProject(info.project_name_key()));
       }
     }, ClickEvent.getType());
     projectLink.setText(info.project());
     projectLink.setTargetHistoryToken(
-        PageLinks.toProject(info.project_name_key()));
+        PageLinks.toProjectDefaultDashboard(info.project_name_key()));
   }
 
   private void initBranchLink(ChangeInfo info) {
@@ -325,7 +376,7 @@
                 info.project_name_key(),
                 info.status(),
                 info.branch(),
-                info.topic())));
+                null)));
   }
 
   private void initEditMessageAction(ChangeInfo info, String revision) {
@@ -354,13 +405,12 @@
     handlers.add(GlobalKey.add(this, keysNavigation));
     handlers.add(GlobalKey.add(this, keysAction));
     files.registerKeys();
-    related.registerKeys();
   }
 
   @Override
   public void onShowView() {
     super.onShowView();
-
+    commit.onShowView();
     related.setMaxHeight(commit.getElement()
         .getParentElement()
         .getOffsetHeight());
@@ -374,7 +424,11 @@
       }
     }
 
+    ChangeGlue.fireShowChange(changeInfo, changeInfo.revision(revision));
     startPoller();
+    if (NewChangeScreenBar.show()) {
+      add(new NewChangeScreenBar(changeId));
+    }
   }
 
   private void scrollToPath(String token) {
@@ -419,9 +473,9 @@
     downloadAction.show();
   }
 
-  @UiHandler("revisions")
-  void onRevision(ClickEvent e) {
-    revisionsAction.show();
+  @UiHandler("patchSets")
+  void onPatchSets(ClickEvent e) {
+    patchSetsAction.show();
   }
 
   @UiHandler("reply")
@@ -429,9 +483,15 @@
     onReply();
   }
 
+  @UiHandler("permalink")
+  void onReload(ClickEvent e) {
+    e.preventDefault();
+    Gerrit.display(PageLinks.toChange(changeId));
+  }
+
   private void onReply() {
     if (Gerrit.isSignedIn()) {
-      replyAction.onReply();
+      replyAction.onReply(null);
     } else {
       Gerrit.doSignIn(getToken());
     }
@@ -442,6 +502,11 @@
     editMessageAction.onEdit();
   }
 
+  @UiHandler("openAll")
+  void onOpenAll(ClickEvent e) {
+    files.openAll();
+  }
+
   @UiHandler("expandAll")
   void onExpandAll(ClickEvent e) {
     int n = history.getWidgetCount();
@@ -499,7 +564,7 @@
       }));
   }
 
-  private static Timestamp myLastReply(ChangeInfo info) {
+  static Timestamp myLastReply(ChangeInfo info) {
     if (Gerrit.isSignedIn() && info.messages() != null) {
       int self = Gerrit.getUserAccountInfo()._account_id();
       for (int i = info.messages().length() - 1; i >= 0; i--) {
@@ -553,8 +618,7 @@
   private List<NativeMap<JsArray<CommentInfo>>> loadComments(
       RevisionInfo rev, CallbackGroup group) {
     final int id = rev._number();
-    final List<NativeMap<JsArray<CommentInfo>>> r =
-        new ArrayList<NativeMap<JsArray<CommentInfo>>>(1);
+    final List<NativeMap<JsArray<CommentInfo>>> r = new ArrayList<>(1);
     ChangeApi.revision(changeId.get(), rev.name())
       .view("comments")
       .get(group.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@@ -573,8 +637,7 @@
 
   private List<NativeMap<JsArray<CommentInfo>>> loadDrafts(
       RevisionInfo rev, CallbackGroup group) {
-    final List<NativeMap<JsArray<CommentInfo>>> r =
-        new ArrayList<NativeMap<JsArray<CommentInfo>>>(1);
+    final List<NativeMap<JsArray<CommentInfo>>> r = new ArrayList<>(1);
     if (Gerrit.isSignedIn()) {
       ChangeApi.revision(changeId.get(), rev.name())
         .view("drafts")
@@ -609,38 +672,9 @@
       }));
   }
 
-  private void loadMergeable(final Change.Status status, final boolean canSubmit) {
-    if (Gerrit.getConfig().testChangeMerge()) {
-      ChangeApi.revision(changeId.get(), revision)
-        .view("mergeable")
-        .get(new AsyncCallback<MergeableInfo>() {
-          @Override
-          public void onSuccess(MergeableInfo result) {
-            if (canSubmit) {
-              actions.setSubmitEnabled(result.mergeable());
-              if (status == Change.Status.NEW) {
-                statusText.setInnerText(result.mergeable()
-                    ? Util.C.readyToSubmit()
-                    : Util.C.mergeConflict());
-              }
-            }
-            setVisible(notMergeable, !result.mergeable());
-            renderSubmitType(result.submit_type());
-          }
-
-          @Override
-          public void onFailure(Throwable caught) {
-            loadSubmitType(status, canSubmit);
-          }
-        });
-    } else {
-      loadSubmitType(status, canSubmit);
-    }
-  }
-
   private void loadSubmitType(final Change.Status status, final boolean canSubmit) {
     if (canSubmit) {
-      actions.setSubmitEnabled(true);
+      actions.setSubmitEnabled();
       if (status == Change.Status.NEW) {
         statusText.setInnerText(Util.C.readyToSubmit());
       }
@@ -650,6 +684,15 @@
       .get(new AsyncCallback<NativeString>() {
         @Override
         public void onSuccess(NativeString result) {
+          if (canSubmit) {
+            if (status == Change.Status.NEW) {
+              statusText.setInnerText(changeInfo.mergeable()
+                  ? Util.C.readyToSubmit()
+                  : Util.C.mergeConflict());
+            }
+          }
+          setVisible(notMergeable, !changeInfo.mergeable());
+
           renderSubmitType(result.asString());
         }
 
@@ -718,13 +761,14 @@
     lastDisplayedUpdate = info.updated();
     boolean current = info.status().isOpen()
         && revision.equals(info.current_revision());
-    boolean canSubmit = labels.set(info, current);
 
     if (!current && info.status() == Change.Status.NEW) {
       statusText.setInnerText(Util.C.notCurrent());
+      labels.setVisible(false);
     } else {
       statusText.setInnerText(Util.toLongString(info.status()));
     }
+    boolean canSubmit = labels.set(info, current);
 
     renderOwner(info);
     renderActionTextDate(info);
@@ -738,20 +782,16 @@
 
     star.setValue(info.starred());
     permalink.setHref(ChangeLink.permalink(changeId));
-    changeIdText.setInnerText(String.valueOf(info.legacy_id()));
-    idText.setText("Change-Id: " + info.change_id());
-    idText.setPreviewText(info.change_id());
-    reload.set(info);
+    permalink.setText(String.valueOf(info.legacy_id()));
     topic.set(info, revision);
     commit.set(commentLinkProcessor, info, revision);
     related.set(info, revision);
     reviewers.set(info);
-    quickApprove.set(info, revision);
-    history.set(commentLinkProcessor, changeId, info, expandAll, collapseAll);
 
     if (Gerrit.isSignedIn()) {
       initEditMessageAction(info, revision);
-      replyAction = new ReplyAction(info, revision, style, reply);
+      replyAction = new ReplyAction(info, revision,
+          style, commentLinkProcessor, reply);
       if (topic.canEdit()) {
         keysAction.add(new KeyCommand(0, 't', Util.C.keyEditTopic()) {
           @Override
@@ -761,8 +801,14 @@
         });
       }
     }
+    history.set(commentLinkProcessor, replyAction, changeId, info);
+
     if (current) {
-      loadMergeable(info.status(), canSubmit);
+      quickApprove.set(info, revision, replyAction);
+      loadSubmitType(info.status(), canSubmit);
+    } else {
+      quickApprove.setVisible(false);
+      setVisible(strategy, false);
     }
 
     StringBuilder sb = new StringBuilder();
@@ -779,8 +825,17 @@
     String name = info.owner().name() != null
         ? info.owner().name()
         : Gerrit.getConfig().getAnonymousCowardName();
-    ownerText.setInnerText(name);
-    ownerText.setTitle(name);
+
+    ownerLink.setText(name);
+    ownerLink.setTitle(info.owner().email() != null
+        ? info.owner().email()
+        : name);
+    ownerLink.setTargetHistoryToken(PageLinks.toAccountQuery(
+        info.owner().name() != null
+        ? info.owner().name()
+        : info.owner().email() != null
+        ? info.owner().email()
+        : String.valueOf(info.owner()._account_id()), Change.Status.NEW));
   }
 
   private void renderSubmitType(String action) {
@@ -850,7 +905,7 @@
       updateAvailable = new UpdateAvailableBar() {
         @Override
         void onShow() {
-          reload.reload();
+          Gerrit.display(PageLinks.toChange(changeId));
         }
 
         void onIgnore(Timestamp newTime) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
index 9090744..92d32ff 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen2.ui.xml
@@ -18,15 +18,14 @@
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:c='urn:import:com.google.gerrit.client.change'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'
-    xmlns:x='urn:import:com.google.gerrit.client.ui'
-    xmlns:clippy='urn:import:com.google.gwtexpui.clippy.client'>
+    xmlns:x='urn:import:com.google.gerrit.client.ui'>
   <ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
   <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
   <ui:style type='com.google.gerrit.client.change.ChangeScreen2.Style'>
     @eval textColor com.google.gerrit.client.Gerrit.getTheme().textColor;
     @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
 
-    @def INFO_WIDTH 450px;
+    @def COMMIT_WIDTH 560px;
     @def HEADER_HEIGHT 29px;
 
     .cs2 {
@@ -41,67 +40,89 @@
       padding: 0 5px;
     }
 
-    .idBlock {
+    .subjectLine {
       position: relative;
-      width: INFO_WIDTH;
+      width: COMMIT_WIDTH;
       height: HEADER_HEIGHT;
       background-color: trimColor;
       color: textColor;
       font-family: sans-serif;
-      font-weight: bold;
     }
-    .star {
-      cursor: pointer;
-      outline: none;
-      position: absolute;
-      left: 5px;
-      top: 5px;
-    }
-    .idLine, .idStatus {
+    .subjectText {
+      width: 460px;
+      height: HEADER_HEIGHT;
       line-height: HEADER_HEIGHT;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
     }
-    .idLine {
+    .subjectButtons {
       position: absolute;
       top: 0;
-      left: 29px;
-      width: 245px;
+      right: 3px;
+      height: HEADER_HEIGHT;
+      line-height: HEADER_HEIGHT;
+    }
+
+    .infoLine {
+      position: absolute;
+      top: 0;
+      left: COMMIT_WIDTH;
+      height: HEADER_HEIGHT;
+      padding-left: 25px;
+    }
+
+    .infoLineHeaderButtons {
+      display: inline-block;
+      height: HEADER_HEIGHT;
+      line-height: HEADER_HEIGHT;
+      vertical-align: top;
+    }
+    .statusRight {
+      position: absolute;
+      top: 0;
+      right: 0;
+      height: HEADER_HEIGHT;
+    }
+    .idAndStatus {
+      display: inline-block;
+      position: relative;
+      height: HEADER_HEIGHT;
+      width: 300px;
+    }
+    .star {
+      position: absolute;
+      top: 5px;
+      right: 2px;
+      cursor: pointer;
+      outline: none;
+    }
+    .changeId {
+      width: 300px;
       white-space: nowrap;
+      line-height: HEADER_HEIGHT;
       overflow: hidden;
       text-overflow: ellipsis;
     }
-    .idStatus {
-      position: absolute;
-      top: 0;
-      right: 26px;
-    }
-    .reload {
-      display: block;
-      position: absolute;
-      top: 7px;
-      right: 5px;
-      cursor: pointer;
+    .statusText {
+      font-weight: bold;
     }
 
-    .headerButtons {
-      position: absolute;
-      top: 0;
-      left: INFO_WIDTH;
-      height: HEADER_HEIGHT;
-      padding-left: 5px;
+    div.popdown {
+      display: inline-block;
+      margin-top: 2px;
+      margin-left: 5px;
+      margin-right: 25px;
     }
 
-    .popdown {
-      position: absolute;
-      top: 2px;
-      right: 0;
-    }
     .popdown button {
       cursor: pointer;
       height: 25px;
       border: none;
       border-left: 2px solid #fff;
+      border-right: 2px solid #fff;
       background-color: trimColor;
-      margin: 0;
+      margin: 0 0 0 -2px;
       padding-left: 2px;
       padding-right: 2px;
       min-width: 100px;
@@ -116,18 +137,22 @@
     .popdown button.selected {
       font-weight: bold;
     }
-    .headerLine button:disabled,
-    .headerTable button:disabled,
+    .popdown button:focus {
+      outline: none;
+    }
+
+    .headerButtons button:disabled,
+    #change_infoTable button:disabled,
     .popdown button:disabled {
       background-color: #999;
       background-image: -webkit-linear-gradient(top, #999, #999);
     }
 
-    .headerTable {
+    .infoTable {
       border-spacing: 0;
     }
 
-    .headerTable th {
+    .infoTable th {
       width: 60px;
       color: #444;
       font-weight: normal;
@@ -136,18 +161,15 @@
       padding: 0 5px 0 0;
     }
 
-    .clippy div {
+    .projectSettings {
       float: right;
-    }
-
-    .queryProject {
-      float: left;
       cursor: pointer;
     }
 
     .infoColumn {
       width: 440px;
-      padding-right: 10px;
+      padding-left: 17px;
+      padding-right: 17px;
       vertical-align: top;
     }
 
@@ -161,16 +183,15 @@
     .notMergeable {
       float: right;
       font-weight: bold;
-      color: red;
+      color: #d00;
     }
 
-    .commitColumn, .related {
+    .commitColumn, .relatedColumn {
       padding: 0;
       vertical-align: top;
     }
-    .commitColumn {
-      width: 600px;
-    }
+    .commitColumn { width: COMMIT_WIDTH; }
+    .relatedColumn { width: 375px; }
 
     .labels {
       border-spacing: 0;
@@ -252,10 +273,13 @@
       padding: 5px 5px;
     }
     .sectionHeader .headerButtons {
+      position: absolute;
+      left: 300px;
       top: 2px;
       height: 18px;
       line-height: 18px;
       border-left: 1px inset #fff;
+      padding-left: 5px;
       padding-top: 3px;
       padding-bottom: 3px;
     }
@@ -282,80 +306,93 @@
 
   <g:HTMLPanel styleName='{style.cs2}'>
     <g:HTMLPanel styleName='{style.headerLine}' ui:field='headerLine'>
-      <div class='{style.idBlock}'>
-        <c:StarIcon ui:field='star' styleName='{style.star}'/>
-        <div class='{style.idLine}'>
-          <ui:msg>Change <span ui:field='changeIdText'/> by <span ui:field='ownerText'/></ui:msg>
+      <div class='{style.subjectLine}'>
+        <div class='{style.idAndStatus}'>
+          <span class='{style.changeId}'>
+            <ui:msg>Change <g:Anchor ui:field='permalink' title='Reload the change (Shortcut: R)'>
+              <ui:attribute name='title'/>
+            </g:Anchor> - <span ui:field='statusText' class='{style.statusText}'/></ui:msg>
+          </span>
         </div>
-        <div ui:field='statusText' class='{style.idStatus}'/>
-        <a ui:field='permalink' class='{style.reload}'>
-          <c:Reload ui:field='reload'
-              title='Reload the change (Shortcut: R)'>
+        <div class='{style.subjectButtons} {style.headerButtons}'>
+          <g:Button ui:field='editMessage'
+              styleName=''
+              visible='false'
+              title='Edit commit message (Shortcut: e)'>
             <ui:attribute name='title'/>
-          </c:Reload>
-        </a>
-      </div>
-      <div class='{style.headerButtons}'>
-        <g:Button ui:field='reply'
-            styleName=''
-            title='Reply and score (Shortcut: a)'>
-          <ui:attribute name='title'/>
-          <div><ui:msg>Reply&#8230;</ui:msg></div>
-        </g:Button>
-        <c:QuickApprove ui:field='quickApprove'
-            styleName='{style.quickApprove}'
-            title='Apply score with one click'>
-          <ui:attribute name='title'/>
-        </c:QuickApprove>
-        <g:Button ui:field='editMessage'
-            styleName=''
-            visible='false'
-            title='Edit commit message (Shortcut: e)'>
-          <ui:attribute name='title'/>
-          <div><ui:msg>Edit Message</ui:msg></div>
-        </g:Button>
+            <div><ui:msg>Edit Message</ui:msg></div>
+          </g:Button>
+        </div>
       </div>
 
-      <g:FlowPanel styleName='{style.popdown}'>
-        <g:Button ui:field='includedIn' styleName='' visible="false">
-          <div><ui:msg>Included in</ui:msg></div>
-        </g:Button>
-        <g:Button ui:field='revisions' styleName=''>
-          <div><ui:msg>Revisions</ui:msg></div>
-        </g:Button>
-        <g:Button ui:field='download' styleName=''>
-          <div><ui:msg>Download</ui:msg></div>
-        </g:Button>
-      </g:FlowPanel>
+      <div class='{style.infoLine}'>
+        <div class='{style.headerButtons} {style.infoLineHeaderButtons}'>
+          <g:Button ui:field='reply'
+              styleName=''
+              title='Reply and score (Shortcut: a)'>
+            <ui:attribute name='title'/>
+            <div><ui:msg>Reply&#8230;</ui:msg></div>
+          </g:Button>
+          <c:QuickApprove ui:field='quickApprove'
+              styleName='{style.quickApprove}'
+              title='Apply score with one click'>
+            <ui:attribute name='title'/>
+          </c:QuickApprove>
+        </div>
+      </div>
+
+      <div class='{style.statusRight}'>
+        <g:FlowPanel styleName='{style.popdown}'>
+          <g:Button ui:field='includedIn' styleName='' visible="false">
+            <div><ui:msg>Included in</ui:msg></div>
+          </g:Button>
+          <g:Button ui:field='patchSets' styleName=''>
+            <div ui:field='patchSetsText'/>
+          </g:Button>
+          <g:Button ui:field='download' styleName=''>
+            <div><ui:msg>Download</ui:msg></div>
+          </g:Button>
+        </g:FlowPanel>
+        <c:StarIcon ui:field='star' styleName='{style.star}' title='Star the change (Shortcut: s)'>
+          <ui:attribute name='title'/>
+        </c:StarIcon>
+      </div>
     </g:HTMLPanel>
 
-    <table class='{style.headerTable}'>
+    <table class='{style.infoTable}'>
       <tr>
+        <td class='{style.commitColumn}'>
+          <c:CommitBox ui:field='commit'/>
+        </td>
         <td class='{style.infoColumn}'>
           <table id='change_infoTable'>
             <tr>
-              <th><ui:msg>Reviewers</ui:msg></th>
-              <td ui:field='reviewersText'/>
+              <th><ui:msg>Owner</ui:msg></th>
+              <td><x:InlineHyperlink ui:field='ownerLink'/></td>
             </tr>
             <tr>
-              <th><ui:msg>CC</ui:msg></th>
+              <th><ui:msg>Reviewers</ui:msg></th>
               <td>
                 <c:Reviewers ui:field='reviewers'/>
               </td>
             </tr>
             <tr>
+              <th/>
+              <td ui:field='ccText'/>
+            </tr>
+            <tr>
               <th><ui:msg>Project</ui:msg></th>
-              <td><g:Image
-                     ui:field='projectQuery'
-                     resource='{ico.queryIcon}'
-                     styleName='{style.queryProject}'
+              <td><x:InlineHyperlink ui:field='projectLink'
                      title='Search for changes on this project'>
-                    <ui:attribute name='title'/>
-                  </g:Image>
-                  <x:InlineHyperlink ui:field='projectLink'
-                     title='Go to project'>
                      <ui:attribute name='title'/>
                   </x:InlineHyperlink>
+                  <g:Image
+                     ui:field='projectSettings'
+                     resource='{ico.gear}'
+                     styleName='{style.projectSettings}'
+                     title='Go to project'>
+                    <ui:attribute name='title'/>
+                  </g:Image>
               </td>
             </tr>
             <tr>
@@ -367,40 +404,34 @@
               </td>
             </tr>
             <tr>
+              <th><ui:msg>Topic</ui:msg></th>
+              <td><c:Topic ui:field='topic'/></td>
+            </tr>
+            <tr ui:field='strategy'>
               <th><ui:msg>Strategy</ui:msg></th>
               <td>
                 <span ui:field='submitActionText'/>
                 <div ui:field='notMergeable'
                      class='{style.notMergeable}'
                      style='display: none'
-                     aria-hidden='true'>
+                     aria-hidden='true'
+                     title='The change cannot be merged due to a path conflict. Rebase the change locally and upload the rebased commit for review.'>
+                  <ui:attribute name='title'/>
                   <ui:msg>Cannot Merge</ui:msg>
                 </div>
               </td>
             </tr>
-            <tr><td colspan='2'><c:Actions ui:field='actions'/></td></tr>
             <tr>
               <th ui:field='actionText'/>
               <td ui:field='actionDate'/>
             </tr>
-            <tr>
-              <th><ui:msg>Change-Id</ui:msg></th>
-              <td><clippy:CopyableLabel styleName='{style.clippy}' ui:field='idText'/></td>
-            </tr>
-            <tr>
-              <th><ui:msg>Topic</ui:msg></th>
-              <td><c:Topic ui:field='topic'/></td>
-            </tr>
+            <tr><td colspan='2'><c:Actions ui:field='actions'/></td></tr>
           </table>
           <hr/>
           <c:Labels ui:field='labels' styleName='{style.labels}'/>
+          <div id='change_plugins'/>
         </td>
-
-        <td class='{style.commitColumn}'>
-          <c:CommitBox ui:field='commit'/>
-        </td>
-
-        <td class='{style.related}'>
+        <td class='{style.relatedColumn}'>
           <c:RelatedChanges ui:field='related'/>
         </td>
       </tr>
@@ -409,6 +440,12 @@
     <div class='{style.sectionHeader}'>
       <ui:msg>Files</ui:msg>
       <div class='{style.headerButtons}'>
+        <g:Button ui:field='openAll'
+            styleName=''
+            title='Open each file in a new tab'>
+          <ui:attribute name='title'/>
+          <div><ui:msg>Open All</ui:msg></div>
+        </g:Button>
         <div class='{style.diffBase}'>
           <ui:msg>Diff against: <g:ListBox ui:field='diffBase' styleName=''/></ui:msg>
         </div>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
index 5cb0443..931a94b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
@@ -28,27 +28,61 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+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.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
 import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
 class CommitBox extends Composite {
   interface Binder extends UiBinder<HTMLPanel, CommitBox> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
-  @UiField Element commitName;
+  interface Style extends CssResource {
+    String collapsed();
+    String expanded();
+  }
+
+  @UiField Style style;
+  @UiField CopyableLabel commitName;
   @UiField AnchorElement browserLink;
   @UiField InlineHyperlink authorNameEmail;
   @UiField Element authorDate;
   @UiField InlineHyperlink committerNameEmail;
   @UiField Element committerDate;
-  @UiField Element commitMessageText;
+  @UiField CopyableLabel idText;
+  @UiField HTML text;
+  @UiField ScrollPanel scroll;
+  @UiField Button more;
+  private boolean expanded;
 
   CommitBox() {
     initWidget(uiBinder.createAndBindUi(this));
+    addStyleName(style.collapsed());
+  }
+
+  void onShowView() {
+    more.setVisible(scroll.getMaximumVerticalScrollPosition() > 0);
+  }
+
+  @UiHandler("more")
+  void onMore(ClickEvent e) {
+    if (expanded) {
+      removeStyleName(style.expanded());
+      addStyleName(style.collapsed());
+    } else {
+      removeStyleName(style.collapsed());
+      addStyleName(style.expanded());
+    }
+    expanded = !expanded;
   }
 
   void set(CommentLinkProcessor commentLinkProcessor,
@@ -57,12 +91,14 @@
     RevisionInfo revInfo = change.revision(revision);
     CommitInfo commit = revInfo.commit();
 
-    commitName.setInnerText(revision);
+    commitName.setText(revision);
+    idText.setText("Change-Id: " + change.change_id());
+    idText.setPreviewText(change.change_id());
     formatLink(commit.author(), authorNameEmail,
         authorDate, change.status());
     formatLink(commit.committer(), committerNameEmail,
         committerDate, change.status());
-    commitMessageText.setInnerSafeHtml(commentLinkProcessor.apply(
+    text.setHTML(commentLinkProcessor.apply(
         new SafeHtmlBuilder().append(commit.message()).linkify()));
 
     GitwebLink gw = Gerrit.getGitwebLink();
@@ -86,7 +122,7 @@
     return person.name() + " <" + person.email() + ">";
   }
 
-  public static String owner(GitPerson person) {
+  private static String owner(GitPerson person) {
     if (person.email() != null) {
       return person.email();
     } else if (person.name() != null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
index a095926..c02e9f8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.ui.xml
@@ -17,34 +17,82 @@
 <ui:UiBinder
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'
-    xmlns:x='urn:import:com.google.gerrit.client.ui'>
-  <ui:style>
-    .commitHeader {
-      border-spacing: 0;
-      padding: 0;
-      width: 564px;
+    xmlns:x='urn:import:com.google.gerrit.client.ui'
+    xmlns:clippy='urn:import:com.google.gwtexpui.clippy.client'>
+  <ui:image field="toggle" src="more_less.png"/>
+  <ui:style type='com.google.gerrit.client.change.CommitBox.Style'>
+    @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
+
+    .collapsed .scroll { height: 250px }
+    .scroll, .more { width: 560px }
+    .scroll {
+      border-right: 1px solid trimColor;
+      border-bottom: 1px solid trimColor;
     }
 
-    .commitHeader th { width: 70px; }
-    .commitHeader td { white-space: pre; }
-
-    .commitMessageBox { margin: 2px; }
-    .commitMessage {
-      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
-      border: 1px solid white;
-      background-color: white;
+    .text {
       font-family: monospace;
       white-space: pre;
-      width: 47em;
     }
+
+    .more {
+      height: 8px;
+      line-height: 8px;
+      text-align: center;
+    }
+    .moreButton {
+      padding: 0 5px 0 5px;
+      margin: 0;
+      border: none;
+      height: 8px;
+      background-color: #F7F7F7;
+    }
+    .moreButton:focus {
+      outline: none;
+    }
+
+    @sprite .toggle {
+      gwt-image: "toggle";
+      width: 13px;
+      height: 8px;
+      padding: 0;
+    }
+    .collapsed .toggle { background-position: -13px -8px }
+    .expanded .toggle { background-position: 0px -8px }
+    .collapsed button:hover .toggle { background-position: -13px 0px }
+    .expanded button:hover .toggle { background-position: 0px 0px }
+
+    .header {
+      border-spacing: 0;
+      padding: 0;
+      width: 560px;
+    }
+    .header th { width: 70px; }
+    .header td { white-space: nowrap; }
+    .date { width: 132px; }
+
+    .clippy {
+      position: relative;
+    }
+    .clippy div {
+      position: absolute;
+      top: 0px;
+      right: -16px;
+     }
   </ui:style>
   <g:HTMLPanel>
-    <table class='{style.commitHeader}'>
-      <tr>
-        <th><ui:msg>Commit</ui:msg></th>
-        <td ui:field='commitName'/>
-        <td><a ui:field='browserLink' href=""/></td>
-      </tr>
+    <g:ScrollPanel styleName='{style.scroll}' ui:field='scroll'>
+      <g:HTML styleName='{style.text}' ui:field='text'/>
+    </g:ScrollPanel>
+    <div class='{style.more}'>
+      <g:Button ui:field='more'
+          styleName='{style.moreButton}'
+          title='Expand/Collapse'>
+        <ui:attribute name='title'/>
+        <div class='{style.toggle}'/>
+      </g:Button>
+    </div>
+    <table class='{style.header}'>
       <tr>
         <th><ui:msg>Author</ui:msg></th>
         <td><x:InlineHyperlink ui:field='authorNameEmail'
@@ -52,7 +100,7 @@
               <ui:attribute name='title'/>
             </x:InlineHyperlink>
         </td>
-        <td ui:field='authorDate'/>
+        <td ui:field='authorDate' class='{style.date}' colspan="2"/>
       </tr>
       <tr>
         <th><ui:msg>Committer</ui:msg></th>
@@ -61,12 +109,17 @@
               <ui:attribute name='title'/>
             </x:InlineHyperlink>
         </td>
-        <td ui:field='committerDate'/>
+        <td ui:field='committerDate' class='{style.date}' colspan="2"/>
+      </tr>
+      <tr>
+        <th><ui:msg>Commit</ui:msg></th>
+        <td><clippy:CopyableLabel styleName='{style.clippy}' ui:field='commitName'/></td>
+        <td><a style="margin-left:16px;" ui:field='browserLink' href=""/></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Change-Id</ui:msg></th>
+        <td><clippy:CopyableLabel styleName='{style.clippy}' ui:field='idText'/></td>
       </tr>
     </table>
-
-    <div class='{style.commitMessageBox}'>
-      <div class='{style.commitMessage}' ui:field='commitMessageText'/>
-    </div>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.java
index fcc369d..4d457ad 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.java
@@ -20,10 +20,25 @@
   String openChange();
   String reviewedFileTitle();
 
-  String ps();
+  String openLastFile();
+  String openCommitMessage();
+
+  String patchSet();
   String commit();
   String date();
   String author();
   String draft();
   String draftCommentsTooltip();
+
+  String notAvailable();
+  String relatedChanges();
+  String relatedChangesTooltip();
+  String conflictingChanges();
+  String conflictingChangesTooltip();
+  String cherryPicks();
+  String cherryPicksTooltip();
+  String sameTopic();
+  String sameTopicTooltip();
+  String noChanges();
+  String indirectAncestor();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.properties
index b8d14f3..289e9b4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Constants.properties
@@ -3,9 +3,24 @@
 openChange = Open related change
 reviewedFileTitle = Mark file as reviewed (Shortcut: r)
 
-ps = PS
+openLastFile = Open last file
+openCommitMessage = Open commit message
+
+patchSet = Patch Set
 commit = Commit
 date = Date
 author = Author / Committer
 draft = (DRAFT)
 draftCommentsTooltip = Draft comment(s) inside
+
+notAvailable = N/A
+relatedChanges = Related Changes
+relatedChangesTooltip = Same branch changes connected by Git history
+conflictingChanges = Conflicts With
+conflictingChangesTooltip = Open changes that conflict with this change
+cherryPicks = Cherry-Picks
+cherryPicksTooltip = Changes with the same Change-Id
+sameTopic = Same Topic
+sameTopicTooltip = Changes with the same topic
+noChanges = No Changes
+indirectAncestor = Indirect ancestor
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
index dbc1319..929bf90 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DownloadBox.java
@@ -23,7 +23,7 @@
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -40,9 +40,13 @@
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 
+import java.util.ArrayList;
 import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
 
 class DownloadBox extends VerticalPanel {
+  private final static String ARCHIVE[] = {"tar", "tbz2", "tgz", "txz"};
   private final ChangeInfo change;
   private final String revision;
   private final PatchSet.Id psId;
@@ -111,6 +115,7 @@
     if (change.revision(revision).commit().parents().length() == 1) {
       insertPatch();
     }
+    insertArchive();
     insertCommand(null, scheme);
   }
 
@@ -143,6 +148,34 @@
     insertCommand("Patch-File", p);
   }
 
+  private void insertArchive() {
+    List<Anchor> formats = new ArrayList<>(ARCHIVE.length);
+    for (String f : ARCHIVE) {
+      Anchor archive = new Anchor(f);
+      archive.setHref(new RestApi("/changes/")
+          .id(psId.getParentKey().get())
+          .view("revisions")
+          .id(revision)
+          .view("archive")
+          .addParameter("format", f)
+          .url());
+      formats.add(archive);
+    }
+
+    HorizontalPanel p = new HorizontalPanel();
+    Iterator<Anchor> it = formats.iterator();
+    while (it.hasNext()) {
+      Anchor a = it.next();
+      p.add(a);
+      if (it.hasNext()) {
+        InlineLabel spacer = new InlineLabel("|");
+        spacer.setStyleName(Gerrit.RESOURCES.css().downloadBoxSpacer());
+        p.add(spacer);
+      }
+    }
+    insertCommand("Archive", p);
+  }
+
   private void insertCommand(String commandName, Widget w) {
     int row = commandTable.getRowCount();
     commandTable.insertRow(row);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java
index 808e4f0..d0666c6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/DraftActions.java
@@ -23,7 +23,7 @@
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
-class DraftActions {
+public class DraftActions {
 
   static void publish(Change.Id id, String revision) {
     ChangeApi.publish(id.get(), revision, cs(id));
@@ -37,7 +37,7 @@
     ChangeApi.deleteChange(id.get(), mine());
   }
 
-  private static GerritCallback<JavaScriptObject> cs(
+  public static GerritCallback<JavaScriptObject> cs(
       final Change.Id id) {
     return new GerritCallback<JavaScriptObject>() {
       public void onSuccess(JavaScriptObject result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.java
index 2d444f0..11fc245 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.java
@@ -83,7 +83,7 @@
           public void onSuccess(JavaScriptObject msg) {
             Gerrit.display(PageLinks.toChange(changeId));
             hide();
-          };
+          }
         });
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.ui.xml
index 118409e..9df4bbc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditMessageBox.ui.xml
@@ -30,7 +30,7 @@
     <div class='{res.style.section}'>
       <c:NpTextArea
          visibleLines='30'
-         characterWidth='72'
+         characterWidth='78'
          styleName='{style.commitMessage}'
          ui:field='message'/>
     </div>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileComments.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileComments.java
index 9dbf994..7172011 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileComments.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileComments.java
@@ -14,21 +14,17 @@
 
 package com.google.gerrit.client.change;
 
-import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.changes.CommentInfo;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
-import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.uibinder.client.UiHandler;
-import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwtorm.client.KeyUtil;
 
 import java.util.Collections;
 import java.util.Comparator;
@@ -38,8 +34,7 @@
   interface Binder extends UiBinder<HTMLPanel, FileComments> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
-  private final String url;
-  @UiField Anchor path;
+  @UiField InlineHyperlink path;
   @UiField FlowPanel comments;
 
   FileComments(CommentLinkProcessor clp,
@@ -48,8 +43,7 @@
       List<CommentInfo> list) {
     initWidget(uiBinder.createAndBindUi(this));
 
-    url = url(ps, list.get(0));
-    path.setHref("#" + url);
+    path.setTargetHistoryToken(url(ps, list.get(0)));
     path.setText(title);
 
     Collections.sort(list, new Comparator<CommentInfo>() {
@@ -59,25 +53,11 @@
       }
     });
     for (CommentInfo c : list) {
-      comments.add(new LineComment(clp, c));
+      comments.add(new LineComment(clp, ps, c));
     }
   }
 
-  @UiHandler("path")
-  void onClick(ClickEvent e) {
-    e.preventDefault();
-    e.stopPropagation();
-    Gerrit.display(url);
-  }
-
   private static String url(PatchSet.Id ps, CommentInfo info) {
-    // TODO(sop): Switch to Dispatcher.toPatchSideBySide.
-    Change.Id c = ps.getParentKey();
-    return new StringBuilder()
-      .append("/c/").append(c.get()).append('/')
-      .append(ps.get()).append('/')
-      .append(KeyUtil.encode(info.path()))
-      .append(",cm")
-      .toString();
+    return Dispatcher.toSideBySide(null, ps, info.path());
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileComments.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileComments.ui.xml
index 9a7564f..5de04cc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileComments.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileComments.ui.xml
@@ -16,7 +16,7 @@
 -->
 <ui:UiBinder
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
-    xmlns:c='urn:import:com.google.gerrit.client'
+    xmlns:c='urn:import:com.google.gerrit.client.ui'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'>
   <ui:style>
     .box {
@@ -31,7 +31,7 @@
   </ui:style>
 
   <g:HTMLPanel styleName='{style.box}'>
-    <g:Anchor styleName='{style.path}' ui:field='path'/>
+    <c:InlineHyperlink styleName='{style.path}' ui:field='path'/>
     <g:FlowPanel styleName='{style.comments}' ui:field='comments'/>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
index 77a0091..e097006 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client.change;
 
+import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.CommentInfo;
@@ -23,9 +24,9 @@
 import com.google.gerrit.client.patches.PatchUtil;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.ui.NavigationTable;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.Patch.ChangeType;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -44,13 +45,13 @@
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.EventListener;
+import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 import com.google.gwt.user.client.ui.impl.HyperlinkImpl;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
 import com.google.gwtexpui.progress.client.ProgressBar;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import com.google.gwtorm.client.KeyUtil;
 
 import java.sql.Timestamp;
 
@@ -64,22 +65,26 @@
   }
 
   interface FileTableCss extends CssResource {
+    String table();
+    String nohover();
     String pointer();
     String reviewed();
     String status();
     String pathColumn();
+    String commonPrefix();
+    String renameCopySource();
     String draftColumn();
     String newColumn();
     String commentColumn();
     String deltaColumn1();
     String deltaColumn2();
-    String commonPrefix();
     String inserted();
     String deleted();
   }
 
   private static final String REVIEWED;
   private static final String OPEN;
+  private static final int C_PATH = 3;
   private static final HyperlinkImpl link = GWT.create(HyperlinkImpl.class);
 
   static {
@@ -110,6 +115,7 @@
       if (t != null) {
         t.onOpenRow(1 + idx);
         e.preventDefault();
+        e.stopPropagation();
         return false;
       }
     }
@@ -117,7 +123,7 @@
   }
 
   private static MyTable getMyTable(NativeEvent event) {
-    com.google.gwt.user.client.Element e = event.getEventTarget().cast();
+    Element e = event.getEventTarget().cast();
     for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
       EventListener l = DOM.getEventListener(e);
       if (l instanceof MyTable) {
@@ -184,6 +190,15 @@
     }
   }
 
+  void openAll() {
+    if (table != null) {
+      String self = Gerrit.selfRedirect(null);
+      for (FileInfo info : Natives.asList(table.list)) {
+        Window.open(self + "#" + url(info), "_blank", null);
+      }
+    }
+  }
+
   private void setTable(MyTable table) {
     clear();
     add(table);
@@ -203,16 +218,9 @@
   }
 
   private String url(FileInfo info) {
-    // TODO(sop): Switch to Dispatcher.toPatchSideBySide.
-    Change.Id c = curr.getParentKey();
-    StringBuilder p = new StringBuilder();
-    p.append("/c/").append(c).append('/');
-    if (base != null) {
-      p.append(base.get()).append("..");
-    }
-    p.append(curr.get()).append('/').append(KeyUtil.encode(info.path()));
-    p.append(info.binary() ? ",unified" : ",cm");
-    return p.toString();
+    return info.binary()
+      ? Dispatcher.toUnified(base, curr, info.path())
+      : Dispatcher.toSideBySide(base, curr, info.path());
   }
 
   private final class MyTable extends NavigationTable<FileInfo> {
@@ -224,12 +232,17 @@
       this.list = list;
       table.setWidth("");
 
-      keysNavigation.add(new PrevKeyCommand(0, 'k', Util.C.patchTablePrev()));
-      keysNavigation.add(new NextKeyCommand(0, 'j', Util.C.patchTableNext()));
+      keysNavigation.add(
+          new PrevKeyCommand(0, 'k', Util.C.patchTablePrev()),
+          new NextKeyCommand(0, 'j', Util.C.patchTableNext()));
       keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.patchTableOpenDiff()));
       keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER,
           Util.C.patchTableOpenDiff()));
 
+      keysNavigation.add(
+          new OpenFileCommand(list.length() - 1, 0, '[', Resources.C.openLastFile()),
+          new OpenFileCommand(0, 0, ']', Resources.C.openCommitMessage()));
+
       keysAction.add(new KeyCommand(0, 'r', PatchUtil.C.toggleReviewed()) {
         @Override
         public void onKeyPress(KeyPressEvent event) {
@@ -312,6 +325,29 @@
         Gerrit.display(url(list.get(row - 1)));
       }
     }
+
+    @Override
+    protected void onCellSingleClick(Event event, int row, int column) {
+      if (column == C_PATH && link.handleAsClick(event)) {
+        onOpenRow(row);
+      } else {
+        super.onCellSingleClick(event, row, column);
+      }
+    }
+
+    private class OpenFileCommand extends KeyCommand {
+      private final int index;
+
+      OpenFileCommand(int index, int modifiers, char c, String helpText) {
+        super(modifiers, c, helpText);
+        this.index = index;
+      }
+
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        Gerrit.display(url(list.get(index)));
+      }
+    }
   }
 
   private final class DisplayCommand implements RepeatingCommand {
@@ -342,6 +378,7 @@
       this.comments = comments;
       this.drafts = drafts;
       this.hasUser = Gerrit.isSignedIn();
+      table.addStyleName(R.css().table());
     }
 
     public boolean execute() {
@@ -410,7 +447,7 @@
     }
 
     private void header(SafeHtmlBuilder sb) {
-      sb.openTr();
+      sb.openTr().setStyleName(R.css().nohover());
       sb.openTh().setStyleName(R.css().pointer()).closeTh();
       sb.openTh().setStyleName(R.css().reviewed()).closeTh();
       sb.openTh().setStyleName(R.css().status()).closeTh();
@@ -481,8 +518,14 @@
         lastPath = path;
       }
 
-      sb.closeAnchor()
-        .closeTd();
+      sb.closeAnchor();
+      if (info.old_path() != null) {
+        sb.br();
+        sb.openSpan().setStyleName(R.css().renameCopySource())
+          .append(info.old_path())
+          .closeSpan();
+      }
+      sb.closeTd();
     }
 
     private int commonPrefix(String path) {
@@ -584,7 +627,7 @@
     }
 
     private void footer(SafeHtmlBuilder sb) {
-      sb.openTr();
+      sb.openTr().setStyleName(R.css().nohover());
       sb.openTh().setStyleName(R.css().pointer()).closeTh();
       sb.openTh().setStyleName(R.css().reviewed()).closeTh();
       sb.openTh().setStyleName(R.css().status()).closeTh();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/History.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/History.java
index e167927..36be692 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/History.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/History.java
@@ -14,10 +14,6 @@
 
 package com.google.gerrit.client.change;
 
-import static com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy.COLLAPSE_ALL;
-import static com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_ALL;
-
-import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.account.AccountInfo;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
@@ -26,12 +22,10 @@
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
-import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Widget;
 
@@ -45,20 +39,21 @@
 import java.util.Set;
 
 class History extends FlowPanel {
-  // This basically means 7 days in milliseconds and could be rewritten as
-  // TimeUnit.DAYS.toMillis(7)
-  // in plain Java, but unfortunately it isn't available in GWT
-  private final static long AGE = 7 * 24 * 60 * 60 * 1000L;
   private CommentLinkProcessor clp;
+  private ReplyAction replyAction;
   private Change.Id changeId;
 
-  private final Set<Integer> loaded = new HashSet<Integer>();
+  private final Set<Integer> loaded = new HashSet<>();
   private final Map<AuthorRevision, List<CommentInfo>> byAuthor =
-      new HashMap<AuthorRevision, List<CommentInfo>>();
+      new HashMap<>();
 
-  void set(CommentLinkProcessor clp, Change.Id id, ChangeInfo info,
-      Button expandAll, Button collapseAll) {
+  private final List<Integer> toLoad = new ArrayList<>(4);
+  private int active;
+
+  void set(CommentLinkProcessor clp, ReplyAction ra,
+      Change.Id id, ChangeInfo info) {
     this.clp = clp;
+    this.replyAction = ra;
     this.changeId = id;
 
     JsArray<MessageInfo> messages = info.messages();
@@ -70,8 +65,26 @@
         }
         add(ui);
       }
+      autoOpen(ChangeScreen2.myLastReply(info));
     }
-    initCommentVisibilityStrategy(expandAll, collapseAll);
+  }
+
+  private void autoOpen(Timestamp lastReply) {
+    if (lastReply == null) {
+      for (Widget child : getChildren()) {
+        ((Message) child).autoOpen();
+      }
+    } else {
+      for (int i = getChildren().size() - 1; i >= 0; i--) {
+        Message ui = (Message) getChildren().get(i);
+        MessageInfo msg = ui.getMessageInfo();
+        if (lastReply.compareTo(msg.date()) < 0) {
+          ui.autoOpen();
+        } else {
+          break;
+        }
+      }
+    }
   }
 
   CommentLinkProcessor getCommentLinkProcessor() {
@@ -82,17 +95,21 @@
     return changeId;
   }
 
+  void replyTo(MessageInfo info) {
+    replyAction.onReply(info);
+  }
+
   void addComments(int id, NativeMap<JsArray<CommentInfo>> map) {
     loaded.add(id);
 
     for (String path : map.keySet()) {
       for (CommentInfo c : Natives.asList(map.get(path))) {
-        c.setPath(path);
+        c.path(path);
         if (c.author() != null) {
           AuthorRevision k = new AuthorRevision(c.author(), id);
           List<CommentInfo> l = byAuthor.get(k);
           if (l == null) {
-            l = new ArrayList<CommentInfo>();
+            l = new ArrayList<>();
             byAuthor.put(k, l);
           }
           l.add(c);
@@ -101,24 +118,41 @@
     }
   }
 
-  void load(final int revisionNumber) {
+  void load(int revisionNumber) {
     if (revisionNumber > 0 && loaded.add(revisionNumber)) {
-      ChangeApi.revision(new PatchSet.Id(changeId, revisionNumber))
-        .view("comments")
-        .get(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
-          @Override
-          public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
-            addComments(revisionNumber, result);
-            update(revisionNumber);
-          }
-
-          @Override
-          public void onFailure(Throwable caught) {
-          }
-        });
+      toLoad.add(revisionNumber);
+      start();
     }
   }
 
+  private void start() {
+    if (active >= 2 || toLoad.isEmpty() || !isAttached()) {
+      return;
+    }
+
+    final int revisionNumber = toLoad.remove(0);
+    active++;
+    ChangeApi.revision(new PatchSet.Id(changeId, revisionNumber))
+      .view("comments")
+      .get(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
+        @Override
+        public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+          addComments(revisionNumber, result);
+          update(revisionNumber);
+          --active;
+          start();
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+          loaded.remove(revisionNumber);
+          loaded.removeAll(toLoad);
+          toLoad.clear();
+          active--;
+        }
+      });
+  }
+
   private void update(int revisionNumber) {
     for (Widget child : getChildren()) {
       Message ui = (Message) child;
@@ -141,10 +175,9 @@
     }
 
     Timestamp when = msg.date();
-    List<CommentInfo> match = new ArrayList<CommentInfo>();
-    List<CommentInfo> other = new ArrayList<CommentInfo>();
-    for (int i = 0; i < list.size(); i++) {
-      CommentInfo c = list.get(i);
+    List<CommentInfo> match = new ArrayList<>();
+    List<CommentInfo> other = new ArrayList<>();
+    for (CommentInfo c : list) {
       if (c.updated().compareTo(when) <= 0) {
         match.add(c);
       } else {
@@ -161,51 +194,6 @@
     return match;
   }
 
-  private void initCommentVisibilityStrategy(Button expandAll, Button collapseAll) {
-    CommentVisibilityStrategy commentVisibilityStrategy =
-        CommentVisibilityStrategy.EXPAND_RECENT;
-    if (Gerrit.isSignedIn()) {
-      commentVisibilityStrategy = Gerrit.getUserAccount()
-          .getGeneralPreferences().getCommentVisibilityStrategy();
-    }
-
-    Timestamp aged = new Timestamp(System.currentTimeMillis() - AGE);
-
-    int n = getWidgetCount();
-    for (int i = 0; i < n; i++) {
-      Message msg = (Message) getWidget(i);
-
-      boolean isRecent = (i == n - 1)
-          ? true
-          : msg.getMessageInfo().date().after(aged);
-
-      boolean isOpen = false;
-      switch (commentVisibilityStrategy) {
-        case COLLAPSE_ALL:
-          break;
-        case EXPAND_ALL:
-          isOpen = true;
-          break;
-        case EXPAND_MOST_RECENT:
-          isOpen = i == n - 1;
-          break;
-        case EXPAND_RECENT:
-        default:
-          isOpen = isRecent;
-          break;
-      }
-      msg.setOpen(isOpen);
-    }
-
-    if (commentVisibilityStrategy == COLLAPSE_ALL) {
-      expandAll.setVisible(true);
-      collapseAll.setVisible(false);
-    } else if (commentVisibilityStrategy == EXPAND_ALL) {
-      expandAll.setVisible(false);
-      collapseAll.setVisible(true);
-    }
-  }
-
   private static final class AuthorRevision {
     final int author;
     final int revision;
@@ -222,6 +210,9 @@
 
     @Override
     public boolean equals(Object o) {
+      if (!(o instanceof AuthorRevision)) {
+        return false;
+      }
       AuthorRevision b = (AuthorRevision) o;
       return author == b.author && revision == b.revision;
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
index e410b00..2680347 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Labels.java
@@ -100,7 +100,7 @@
   }
 
   boolean set(ChangeInfo info, boolean current) {
-    List<String> names = new ArrayList<String>(info.labels());
+    List<String> names = new ArrayList<>(info.labels());
     Collections.sort(names);
 
     boolean canSubmit = info.status().isOpen();
@@ -142,14 +142,14 @@
   }
 
   private Widget renderUsers(LabelInfo label) {
-    Map<Integer, List<ApprovalInfo>> m = new HashMap<Integer, List<ApprovalInfo>>(4);
+    Map<Integer, List<ApprovalInfo>> m = new HashMap<>(4);
     int approved = 0, rejected = 0;
 
     for (ApprovalInfo ai : Natives.asList(label.all())) {
       if (ai.value() != 0) {
         List<ApprovalInfo> l = m.get(Integer.valueOf(ai.value()));
         if (l == null) {
-          l = new ArrayList<ApprovalInfo>(label.all().length());
+          l = new ArrayList<>(label.all().length());
           m.put(Integer.valueOf(ai.value()), l);
         }
         l.add(ai);
@@ -178,14 +178,14 @@
       }
       html.append(val).append(" ");
       html.append(formatUserList(style, m.get(v),
-          Collections.<Integer> emptySet()));
+          Collections.<Integer> emptySet(), null));
       html.closeSpan();
     }
     return html.toBlockWidget();
   }
 
   private static List<Integer> sort(Set<Integer> keySet, int a, int b) {
-    List<Integer> r = new ArrayList<Integer>(keySet);
+    List<Integer> r = new ArrayList<>(keySet);
     Collections.sort(r);
     if (keySet.contains(a)) {
       r.remove(Integer.valueOf(a));
@@ -224,8 +224,9 @@
 
   static SafeHtml formatUserList(ChangeScreen2.Style style,
       Collection<? extends AccountInfo> in,
-      Set<Integer> removable) {
-    List<AccountInfo> users = new ArrayList<AccountInfo>(in);
+      Set<Integer> removable,
+      Map<Integer, VotableInfo> votable) {
+    List<AccountInfo> users = new ArrayList<>(in);
     Collections.sort(users, new Comparator<AccountInfo>() {
       @Override
       public int compare(AccountInfo a, AccountInfo b) {
@@ -263,9 +264,25 @@
         name = Integer.toString(ai._account_id());
       }
 
+      String votableCategories = "";
+      if (votable != null) {
+        Set<String> s = votable.get(ai._account_id()).votableLabels();
+        if (!s.isEmpty()) {
+          StringBuilder sb = new StringBuilder(Util.C.votable());
+          sb.append(" ");
+          for (Iterator<String> it = s.iterator(); it.hasNext();) {
+            sb.append(it.next());
+            if (it.hasNext()) {
+              sb.append(", ");
+            }
+          }
+          votableCategories = sb.toString();
+        }
+      }
       html.openSpan()
           .setAttribute("role", "listitem")
           .setAttribute(DATA_ID, ai._account_id())
+          .setAttribute("title", getTitle(ai, votableCategories))
           .setStyleName(style.label_user());
       if (img != null) {
         html.openElement("img")
@@ -294,4 +311,15 @@
     }
     return html;
   }
+
+  private static String getTitle(AccountInfo ai, String votableCategories) {
+    String title = ai.email() != null ? ai.email() : "";
+    if (!votableCategories.isEmpty()) {
+      if (!title.isEmpty()) {
+        title += " ";
+      }
+      title += votableCategories;
+    }
+    return title;
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.java
index 2c3e14e..6540638 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.java
@@ -14,9 +14,13 @@
 
 package com.google.gerrit.client.change;
 
+import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.changes.CommentInfo;
-import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.diff.DisplaySide;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.client.ui.InlineHyperlink;
+import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -29,18 +33,36 @@
   interface Binder extends UiBinder<HTMLPanel, LineComment> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
-  @UiField Element location;
+  @UiField Element fileLoc;
+  @UiField Element lineLoc;
+  @UiField InlineHyperlink line;
   @UiField Element message;
 
-  LineComment(CommentLinkProcessor clp, CommentInfo info) {
+  LineComment(CommentLinkProcessor clp, PatchSet.Id ps, CommentInfo info) {
     initWidget(uiBinder.createAndBindUi(this));
 
-    location.setInnerText(info.has_line()
-        ? Util.M.lineHeader(info.line())
-        : Util.C.fileCommentHeader());
+    if (info.has_line()) {
+      fileLoc.removeFromParent();
+      fileLoc = null;
+
+      line.setTargetHistoryToken(url(ps, info));
+      line.setText(Integer.toString(info.line()));
+
+    } else {
+      lineLoc.removeFromParent();
+      lineLoc = null;
+      line = null;
+    }
+
     if (info.message() != null) {
       message.setInnerSafeHtml(clp.apply(new SafeHtmlBuilder()
           .append(info.message().trim()).wikify()));
     }
   }
+
+  private static String url(PatchSet.Id ps, CommentInfo info) {
+    return Dispatcher.toSideBySide(null, ps, info.path(),
+        info.side() == Side.PARENT ? DisplaySide.A : DisplaySide.B,
+        info.line());
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.ui.xml
index 98c2853..8dc1245 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/LineComment.ui.xml
@@ -16,7 +16,7 @@
 -->
 <ui:UiBinder
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
-    xmlns:c='urn:import:com.google.gerrit.client'
+    xmlns:c='urn:import:com.google.gerrit.client.ui'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'>
   <ui:style>
     .box {
@@ -34,7 +34,8 @@
   </ui:style>
 
   <g:HTMLPanel styleName='{style.box}'>
-    <div class='{style.location}' ui:field='location'/>
+    <div class='{style.location}' ui:field='fileLoc'><ui:msg>File Comment</ui:msg></div>
+    <div class='{style.location}' ui:field='lineLoc'><ui:msg>Line <c:InlineHyperlink ui:field='line'/>:</ui:msg></div>
     <div class='{style.message}' ui:field='message'/>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
index 4477665..e3799ca 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
@@ -25,11 +25,14 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Visibility;
 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.uibinder.client.UiHandler;
+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.HTMLPanel;
@@ -55,12 +58,14 @@
   @UiField Element name;
   @UiField Element summary;
   @UiField Element date;
+  @UiField Button reply;
   @UiField Element message;
   @UiField FlowPanel comments;
 
   private final History history;
   private final MessageInfo info;
   private List<CommentInfo> commentList;
+  private boolean autoOpen;
 
   @UiField(provided = true)
   AvatarImage avatar;
@@ -91,6 +96,19 @@
       summary.setInnerText(msg);
       message.setInnerSafeHtml(history.getCommentLinkProcessor()
         .apply(new SafeHtmlBuilder().append(msg).wikify()));
+    } else {
+      reply.getElement().getStyle().setVisibility(Visibility.HIDDEN);
+    }
+  }
+
+  @UiHandler("reply")
+  void onReply(ClickEvent e) {
+    e.stopPropagation();
+
+    if (Gerrit.isSignedIn()) {
+      history.replyTo(info);
+    } else {
+      Gerrit.doSignIn(com.google.gwt.user.client.History.getToken());
     }
   }
 
@@ -122,6 +140,15 @@
     }
   }
 
+  void autoOpen() {
+    if (commentList == null) {
+      autoOpen = true;
+      history.load(info._revisionNumber());
+    } else if (!commentList.isEmpty()) {
+      setOpen(true);
+    }
+  }
+
   void addComments(List<CommentInfo> list) {
     if (isOpen()) {
       renderComments(list);
@@ -129,6 +156,9 @@
       commentList = Collections.emptyList();
     } else {
       commentList = list;
+      if (autoOpen && !commentList.isEmpty()) {
+        setOpen(true);
+      }
     }
   }
 
@@ -149,12 +179,11 @@
 
   private static TreeMap<String, List<CommentInfo>>
   byPath(List<CommentInfo> list) {
-    TreeMap<String, List<CommentInfo>> m =
-        new TreeMap<String, List<CommentInfo>>();
+    TreeMap<String, List<CommentInfo>> m = new TreeMap<>();
     for (CommentInfo c : list) {
       List<CommentInfo> l = m.get(c.path());
       if (l == null) {
-        l = new ArrayList<CommentInfo>();
+        l = new ArrayList<>();
         m.put(c.path(), l);
       }
       l.add(c);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml
index 1964d0b..ab1b53e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.ui.xml
@@ -22,6 +22,7 @@
     .messageBox {
       position: relative;
       width: 1168px;
+      padding: 2px 0 2px 0;
       border-left: 1px solid #e3e9ff;
       border-right: 1px solid #e3e9ff;
       border-bottom: 1px solid #e3e9ff;
@@ -29,6 +30,10 @@
       -webkit-border-bottom-right-radius: 8px;
     }
 
+    .header {
+      cursor: pointer;
+    }
+
     .avatar {
       position: absolute;
       width: 26px;
@@ -36,8 +41,10 @@
     }
     .closed .avatar {
       position: absolute;
-      width: 16px;
-      height: 16px;
+      top: 0;
+      left: -1px;
+      width: 20px;
+      height: 20px;
     }
 
     .contents {
@@ -45,7 +52,8 @@
       position: relative;
     }
 
-    .contents p {
+    .contents p,
+    .contents blockquote {
       -webkit-margin-before: 0;
       -webkit-margin-after: 0.3em;
     }
@@ -66,7 +74,7 @@
       position: absolute;
       top: 0;
       left: 120px;
-      width: 915px;
+      width: 880px;
       overflow: hidden;
       text-overflow: ellipsis;
       white-space: nowrap;
@@ -76,7 +84,25 @@
       white-space: nowrap;
       position: absolute;
       top: 0;
-      right: 5px;
+      right: 18px;
+    }
+
+    .reply {
+      position: absolute;
+      top: 0;
+      right: 1px;
+      cursor: pointer;
+      outline: none;
+      border: none;
+      background: transparent;
+      margin: 0;
+      padding: 0;
+      line-height: 15px;
+      font-family: Arial Unicode MS, sans-serif;
+      font-size: 18px;
+    }
+    .closed .reply {
+      visibility: HIDDEN;
     }
   </ui:style>
 
@@ -85,10 +111,16 @@
       addStyleNames='{style.closed}'>
     <c:AvatarImage ui:field='avatar' styleName='{style.avatar}'/>
     <div class='{style.contents}'>
-      <g:HTMLPanel ui:field='header'>
+      <g:HTMLPanel ui:field='header' styleName='{style.header}'>
         <div class='{style.name}' ui:field='name'/>
         <div ui:field='summary' class='{style.summary}'/>
         <div class='{style.date}' ui:field='date'/>
+        <g:Button styleName='{style.reply}'
+            ui:field='reply'
+            title='Reply to this message'>
+          <ui:attribute name='title'/>
+          <div>&#x21a9;</div>
+        </g:Button>
       </g:HTMLPanel>
       <div ui:field='message'
            aria-hidden='true'
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Messages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Messages.java
index ea5e8d8..1d2ea4b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Messages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Messages.java
@@ -15,5 +15,14 @@
 package com.google.gerrit.client.change;
 
 public interface Messages extends com.google.gwt.i18n.client.Messages {
+  String patchSets(int currentlyViewedPatchSet, int currentPatchSet);
   String changeWithNoRevisions(int changeId);
+  String relatedChanges(int count);
+  String relatedChanges(String count);
+  String conflictingChanges(int count);
+  String conflictingChanges(String count);
+  String cherryPicks(int count);
+  String cherryPicks(String count);
+  String sameTopic(int count);
+  String sameTopic(String count);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Messages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Messages.properties
index c22d587..5fddd8e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Messages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Messages.properties
@@ -1,2 +1,6 @@
+patchSets = Patch Sets ({0}/{1})
 changeWithNoRevisions = Cannot display change {0} because it has no revisions.
-
+relatedChanges = Related Changes ({0})
+conflictingChanges = Conflicts With ({0})
+cherryPicks = Cherry-Picks ({0})
+sameTopic = Same Topic ({0})
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.java
new file mode 100644
index 0000000..fa65514
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.java
@@ -0,0 +1,105 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.change;
+
+import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.account.AccountApi;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Cookies;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+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.UIObject;
+
+import java.util.Date;
+
+/** Displays a welcome to the new change screen bar. */
+class NewChangeScreenBar extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, NewChangeScreenBar> {}
+  private static final Binder uiBinder = GWT.create(Binder.class);
+
+  static boolean show() {
+    if (Gerrit.isSignedIn()) {
+      return Gerrit.getUserAccount()
+          .getGeneralPreferences()
+          .getChangeScreen() == null;
+    }
+    return Cookies.getCookie(Dispatcher.COOKIE_CS2) == null;
+  }
+
+  private final Change.Id id;
+
+  @UiField Element docs;
+  @UiField Element settings;
+  @UiField Anchor keepNew;
+  @UiField Anchor keepOld;
+
+  NewChangeScreenBar(Change.Id id) {
+    this.id = id;
+    initWidget(uiBinder.createAndBindUi(this));
+    UIObject.setVisible(docs, Gerrit.getConfig().isDocumentationAvailable());
+    UIObject.setVisible(settings, Gerrit.isSignedIn());
+  }
+
+  @UiHandler("keepOld")
+  void onKeepOld(ClickEvent e) {
+    save(ChangeScreen.OLD_UI);
+    Gerrit.display(PageLinks.toChange(id));
+  }
+
+  @UiHandler("keepNew")
+  void onKeepNew(ClickEvent e) {
+    save(ChangeScreen.CHANGE_SCREEN2);
+  }
+
+  private void save(ChangeScreen sel) {
+    removeFromParent();
+    Dispatcher.changeScreen2 = sel == ChangeScreen.CHANGE_SCREEN2;
+
+    if (Gerrit.isSignedIn()) {
+      Gerrit.getUserAccount().getGeneralPreferences().setChangeScreen(sel);
+
+      Prefs in = Prefs.createObject().cast();
+      in.change_screen(sel.name());
+      AccountApi.self().view("preferences").background().post(in,
+        new AsyncCallback<JavaScriptObject>() {
+          @Override public void onFailure(Throwable caught) {}
+          @Override public void onSuccess(JavaScriptObject result) {}
+        });
+    } else {
+      Cookies.setCookie(
+        Dispatcher.COOKIE_CS2,
+        Dispatcher.changeScreen2 ? "1" : "0",
+        new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000));
+    }
+  }
+
+  private static class Prefs extends JavaScriptObject {
+    final native void change_screen(String n) /*-{ this.change_screen=n }-*/;
+    protected Prefs() {
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.ui.xml
new file mode 100644
index 0000000..b91c08d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/NewChangeScreenBar.ui.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2013 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:style>
+    .popup {
+      position: fixed;
+      top: 5px;
+      left: 50%;
+      margin-left: -200px;
+      z-index: 201;
+      padding-top: 5px;
+      padding-bottom: 5px;
+      padding-left: 12px;
+      padding-right: 12px;
+      text-align: center;
+      background: #FFF1A8;
+      border-radius: 10px;
+    }
+
+    @if user.agent safari {
+      .popup {
+        \-webkit-border-radius: 10px;
+      }
+    }
+    @if user.agent gecko1_8 {
+      .popup {
+        \-moz-border-radius: 10px;
+      }
+    }
+
+    a.action {
+      color: #222;
+      text-decoration: underline;
+      display: inline-block;
+      margin-left: 0.5em;
+    }
+    .welcome { font-weight: bold; }
+  </ui:style>
+  <g:HTMLPanel styleName='{style.popup}'>
+    <div><ui:msg><span class='{style.welcome}'>Welcome to the new change screen!</span>
+      <a ui:field='docs'
+         class='{style.action}'
+         href='Documentation/intro-change-screen.html'
+         target='_blank'>Learn more</a></ui:msg>
+    </div>
+    <div>
+      <ui:msg>You can<g:Anchor ui:field='keepOld'
+          styleName='{style.action}'
+          href='javascript:;'
+          title='Switch back to the old screen'><ui:attribute name='title'/>revert
+            to the old screen</g:Anchor><span ui:field='settings'> in Settings &gt; Preferences</span>.
+      <g:Anchor ui:field='keepNew'
+          styleName='{style.action}'
+          href='javascript:;'
+          title='Keep the new change screen'>
+        <ui:attribute name='title'/>
+        Got it!
+      </g:Anchor></ui:msg>
+    </div>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsAction.java
similarity index 85%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsAction.java
rename to gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsAction.java
index d66127b..b29ca11 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsAction.java
@@ -18,17 +18,17 @@
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwt.user.client.ui.Widget;
 
-class RevisionsAction extends RightSidePopdownAction {
-  private final RevisionsBox revisionBox;
+class PatchSetsAction extends RightSidePopdownAction {
+  private final PatchSetsBox revisionBox;
 
-  RevisionsAction(
+  PatchSetsAction(
       Change.Id changeId,
       String revision,
       ChangeScreen2.Style style,
       UIObject relativeTo,
       Widget downloadButton) {
     super(style, relativeTo, downloadButton);
-    this.revisionBox = new RevisionsBox(changeId, revision);
+    this.revisionBox = new PatchSetsBox(changeId, revision);
   }
 
   Widget getWidget() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java
similarity index 89%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.java
rename to gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java
index 6dac1e9..2fa721e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.java
@@ -26,10 +26,11 @@
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.ui.FancyFlexTableImpl;
 import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -50,8 +51,8 @@
 import java.util.Collections;
 import java.util.EnumSet;
 
-class RevisionsBox extends Composite {
-  interface Binder extends UiBinder<HTMLPanel, RevisionsBox> {}
+class PatchSetsBox extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, PatchSetsBox> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
   private static final String OPEN;
@@ -64,13 +65,13 @@
 
   private static final native void init(String o) /*-{
     $wnd[o] = $entry(function(e,i) {
-      return @com.google.gerrit.client.change.RevisionsBox::onOpen(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i);
+      return @com.google.gerrit.client.change.PatchSetsBox::onOpen(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i);
     });
   }-*/;
 
   private static boolean onOpen(NativeEvent e, int idx) {
     if (link.handleAsClick(e.<Event> cast())) {
-      RevisionsBox t = getRevisionBox(e);
+      PatchSetsBox t = getRevisionBox(e);
       if (t != null) {
         t.onOpenRow(idx);
         e.preventDefault();
@@ -80,12 +81,12 @@
     return true;
   }
 
-  private static RevisionsBox getRevisionBox(NativeEvent event) {
-    com.google.gwt.user.client.Element e = event.getEventTarget().cast();
+  private static PatchSetsBox getRevisionBox(NativeEvent event) {
+    Element e = event.getEventTarget().cast();
     for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
       EventListener l = DOM.getEventListener(e);
-      if (l instanceof RevisionsBox) {
-        return (RevisionsBox) l;
+      if (l instanceof PatchSetsBox) {
+        return (PatchSetsBox) l;
       }
     }
     return null;
@@ -106,7 +107,7 @@
   @UiField FlexTable table;
   @UiField Style style;
 
-  RevisionsBox(Change.Id changeId, String revision) {
+  PatchSetsBox(Change.Id changeId, String revision) {
     this.changeId = changeId;
     this.revision = revision;
     initWidget(uiBinder.createAndBindUi(this));
@@ -160,7 +161,7 @@
     sb.openTr()
       .openTh()
           .setStyleName(style.legacy_id())
-          .append(Resources.C.ps())
+          .append(Resources.C.patchSet())
           .closeTh()
       .openTh().append(Resources.C.commit()).closeTh()
       .openTh().append(Resources.C.date()).closeTh()
@@ -204,8 +205,8 @@
       .append(FormatUtil.shortFormatDayTime(c.committer().date()))
       .closeTd();
 
-    String an = c.author() != null ? c.author().name() : null;
-    String cn = c.committer() != null ? c.committer().name() : null;
+    String an = c.author() != null ? c.author().name() : "";
+    String cn = c.committer() != null ? c.committer().name() : "";
     sb.openTd();
     sb.append(an);
     if (!"".equals(an) && !"".equals(cn) && !an.equals(cn)) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.ui.xml
similarity index 96%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.ui.xml
rename to gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.ui.xml
index d7a8fc4..bd69cd6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RevisionsBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/PatchSetsBox.ui.xml
@@ -18,7 +18,7 @@
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'>
   <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
-  <ui:style type='com.google.gerrit.client.change.RevisionsBox.Style'>
+  <ui:style type='com.google.gerrit.client.change.PatchSetsBox.Style'>
     @eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
 
     .revisionBox {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java
index ddb7d56..98d495a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/QuickApprove.java
@@ -34,12 +34,13 @@
   private Change.Id changeId;
   private String revision;
   private ReviewInput input;
+  private ReplyAction replyAction;
 
   QuickApprove() {
     addClickHandler(this);
   }
 
-  void set(ChangeInfo info, String commit) {
+  void set(ChangeInfo info, String commit, ReplyAction action) {
     if (!info.has_permitted_labels() || !info.status().isOpen()) {
       // Quick approve needs at least one label on an open change.
       setVisible(false);
@@ -94,6 +95,7 @@
       revision = commit;
       input = ReviewInput.create();
       input.label(qName, qValue);
+      replyAction = action;
       setText(qName + qValueStr);
     } else {
       setVisible(false);
@@ -107,6 +109,9 @@
 
   @Override
   public void onClick(ClickEvent event) {
+    if (replyAction != null) {
+      input.message(replyAction.getMessage());
+    }
     ChangeApi.revision(changeId.get(), revision)
       .view("review")
       .post(input, new GerritCallback<ReviewInput>() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
index f8a7cc7..1690d5c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
@@ -14,331 +14,373 @@
 
 package com.google.gerrit.client.change;
 
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.GitwebLink;
+import static com.google.gerrit.common.PageLinks.op;
+
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
-import com.google.gerrit.client.ui.NavigationTable;
-import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.changes.ChangeList;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.RepeatingCommand;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+import com.google.gwt.resources.client.ClientBundle;
 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.Event;
-import com.google.gwt.user.client.EventListener;
-import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.ScrollPanel;
-import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwt.user.client.ui.impl.HyperlinkImpl;
-import com.google.gwtexpui.progress.client.ProgressBar;
-import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+import com.google.gwt.user.client.ui.TabBar;
+import com.google.gwt.user.client.ui.TabPanel;
 
-class RelatedChanges extends Composite {
-  interface Binder extends UiBinder<HTMLPanel, RelatedChanges> {}
-  private static final Binder uiBinder = GWT.create(Binder.class);
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
 
-  private static final String OPEN;
-  private static final HyperlinkImpl link = GWT.create(HyperlinkImpl.class);
+public class RelatedChanges extends TabPanel {
+  static final RelatedChangesResources R = GWT
+      .create(RelatedChangesResources.class);
 
-  static {
-    OPEN = DOM.createUniqueId().replace('-', '_');
-    init(OPEN);
+  interface RelatedChangesResources extends ClientBundle {
+    @Source("related_changes.css")
+    RelatedChangesCss css();
   }
 
-  private static final native void init(String o) /*-{
-    $wnd[o] = $entry(function(e,i) {
-      return @com.google.gerrit.client.change.RelatedChanges::onOpen(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i);
-    });
-  }-*/;
-
-  private static boolean onOpen(NativeEvent e, int idx) {
-    if (link.handleAsClick(e.<Event> cast())) {
-      MyTable t = getMyTable(e);
-      if (t != null) {
-        t.onOpenRow(idx);
-        e.preventDefault();
-        return false;
-      }
-    }
-    return true;
-  }
-
-  private static MyTable getMyTable(NativeEvent event) {
-    com.google.gwt.user.client.Element e = event.getEventTarget().cast();
-    for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
-      EventListener l = DOM.getEventListener(e);
-      if (l instanceof MyTable) {
-        return (MyTable) l;
-      }
-    }
-    return null;
-  }
-
-  interface Style extends CssResource {
+  interface RelatedChangesCss extends CssResource {
+    String activeRow();
+    String current();
+    String gitweb();
+    String indirect();
+    String notCurrent();
+    String pointer();
+    String row();
     String subject();
+    String tabPanel();
   }
 
-  private String project;
-  private MyTable table;
-  private boolean register;
+  private enum Tab {
+    RELATED_CHANGES(Resources.C.relatedChanges(),
+        Resources.C.relatedChangesTooltip()) {
+      @Override
+      String getTitle(int count) {
+        return Resources.M.relatedChanges(count);
+      }
 
-  @UiField Style style;
-  @UiField Element header;
-  @UiField Element none;
-  @UiField ScrollPanel scroll;
-  @UiField ProgressBar progress;
-  @UiField Element error;
+      @Override
+      String getTitle(String count) {
+        return Resources.M.relatedChanges(count);
+      }
+    },
+
+    SAME_TOPIC(Resources.C.sameTopic(),
+        Resources.C.sameTopicTooltip()) {
+      @Override
+      String getTitle(int count) {
+        return Resources.M.sameTopic(count);
+      }
+
+      @Override
+      String getTitle(String count) {
+        return Resources.M.sameTopic(count);
+      }
+    },
+
+    CONFLICTING_CHANGES(Resources.C.conflictingChanges(),
+        Resources.C.conflictingChangesTooltip()) {
+      @Override
+      String getTitle(int count) {
+        return Resources.M.conflictingChanges(count);
+      }
+
+      @Override
+      String getTitle(String count) {
+        return Resources.M.conflictingChanges(count);
+      }
+    },
+
+    CHERRY_PICKS(Resources.C.cherryPicks(),
+        Resources.C.cherryPicksTooltip()) {
+      @Override
+      String getTitle(int count) {
+        return Resources.M.cherryPicks(count);
+      }
+
+      @Override
+      String getTitle(String count) {
+        return Resources.M.cherryPicks(count);
+      }
+    };
+
+    final String defaultTitle;
+    final String tooltip;
+
+    abstract String getTitle(int count);
+    abstract String getTitle(String count);
+
+    private Tab(String defaultTitle, String tooltip) {
+      this.defaultTitle = defaultTitle;
+      this.tooltip = tooltip;
+    }
+  }
+
+  private final List<RelatedChangesTab> tabs;
+  private int maxHeightWithHeader;
+  private int selectedTab;
+  private int outstandingCallbacks;
 
   RelatedChanges() {
-    initWidget(uiBinder.createAndBindUi(this));
+    tabs = new ArrayList<>(Tab.values().length);
+    selectedTab = -1;
+
+    setVisible(false);
+    addStyleName(R.css().tabPanel());
+    initTabBar();
   }
 
-  void set(ChangeInfo info, final String revision) {
-    if (info.status().isClosed()) {
-      setVisible(false);
-      return;
+  private void initTabBar() {
+    TabBar tabBar = getTabBar();
+    tabBar.addSelectionHandler(new SelectionHandler<Integer>() {
+      @Override
+      public void onSelection(SelectionEvent<Integer> event) {
+        if (selectedTab >= 0) {
+          tabs.get(selectedTab).registerKeys(false);
+        }
+        selectedTab = event.getSelectedItem();
+        tabs.get(selectedTab).registerKeys(true);
+      }
+    });
+
+    for (Tab tabInfo : Tab.values()) {
+      RelatedChangesTab panel = new RelatedChangesTab();
+      add(panel, tabInfo.defaultTitle);
+      tabs.add(panel);
+
+      TabBar.Tab tab = tabBar.getTab(tabInfo.ordinal());
+      tab.setWordWrap(false);
+      ((Composite) tab).setTitle(tabInfo.tooltip);
+
+      setTabEnabled(tabInfo, false);
+    }
+    getTab(Tab.RELATED_CHANGES).setShowIndirectAncestors(true);
+    getTab(Tab.CHERRY_PICKS).setShowBranches(true);
+  }
+
+  void set(final ChangeInfo info, final String revision) {
+    if (info.status().isOpen()) {
+      setForOpenChange(info, revision);
     }
 
-    project = info.project();
+    StringBuilder cherryPicksQuery = new StringBuilder();
+    cherryPicksQuery.append(op("project", info.project()));
+    cherryPicksQuery.append(" ").append(op("change", info.change_id()));
+    cherryPicksQuery.append(" ").append(op("-change", info.legacy_id().get()));
+    cherryPicksQuery.append(" -is:abandoned");
+    ChangeList.query(cherryPicksQuery.toString(),
+        EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT),
+        new TabChangeListCallback(Tab.CHERRY_PICKS, info.project(), revision));
 
-    ChangeApi.revision(info.legacy_id().get(), revision)
-      .view("related")
-      .get(new AsyncCallback<RelatedInfo>() {
-        @Override
-        public void onSuccess(RelatedInfo result) {
-          render(revision, result.changes());
-        }
+    if (info.topic() != null && !"".equals(info.topic())) {
+      StringBuilder topicQuery = new StringBuilder();
+      topicQuery.append("status:open");
+      topicQuery.append(" ").append(op("project", info.project()));
+      topicQuery.append(" ").append(op("branch", info.branch()));
+      topicQuery.append(" ").append(op("topic", info.topic()));
+      topicQuery.append(" ").append(op("-change", info.legacy_id().get()));
+      ChangeList.query(topicQuery.toString(),
+          EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT),
+          new TabChangeListCallback(Tab.SAME_TOPIC, info.project(), revision));
+    }
+  }
 
-        @Override
-        public void onFailure(Throwable err) {
-          progress.setVisible(false);
-          scroll.setVisible(false);
-          UIObject.setVisible(error, true);
-          error.setInnerText(err.getMessage());
-        }
-      });
+  private void setForOpenChange(final ChangeInfo info, final String revision) {
+    ChangeApi.revision(info.legacy_id().get(), revision).view("related")
+        .get(new TabCallback<RelatedInfo>(Tab.RELATED_CHANGES, info.project(), revision) {
+              @Override
+              public JsArray<ChangeAndCommit> convert(RelatedInfo result) {
+                return result.changes();
+              }
+            });
+
+    if (info.mergeable()) {
+      StringBuilder conflictsQuery = new StringBuilder();
+      conflictsQuery.append("status:open");
+      conflictsQuery.append(" is:mergeable");
+      conflictsQuery.append(" ").append(op("conflicts", info.legacy_id().get()));
+      ChangeList.query(conflictsQuery.toString(),
+          EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT),
+          new TabChangeListCallback(Tab.CONFLICTING_CHANGES, info.project(), revision));
+    }
+  }
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    R.css().ensureInjected();
+  }
+
+  private RelatedChangesTab getTab(Tab tabInfo) {
+    return tabs.get(tabInfo.ordinal());
+  }
+
+  private void setTabTitle(Tab tabInfo, String title) {
+    getTabBar().setTabText(tabInfo.ordinal(), title);
+  }
+
+  private void setTabEnabled(Tab tabInfo, boolean enabled) {
+    getTabBar().setTabEnabled(tabInfo.ordinal(), enabled);
   }
 
   void setMaxHeight(int height) {
-    int h = height - header.getOffsetHeight();
-    scroll.setHeight(h + "px");
-  }
-
-  void registerKeys() {
-    register = true;
-
-    if (table != null) {
-      table.setRegisterKeys(true);
+    maxHeightWithHeader = height;
+    if (isVisible()) {
+      applyMaxHeight();
     }
   }
 
-  private void render(String revision, JsArray<ChangeAndCommit> list) {
-    if (0 < list.length()) {
-      DisplayCommand cmd = new DisplayCommand(revision, list);
-      if (cmd.execute()) {
-        Scheduler.get().scheduleIncremental(cmd);
-      }
-    } else {
-      progress.setVisible(false);
-      UIObject.setVisible(header, false);
-      UIObject.setVisible(none, true);
+  private void applyMaxHeight() {
+    int header = getTabBar().getOffsetHeight() + 2 /* padding */;
+    for (int i = 0; i < getTabBar().getTabCount(); i++) {
+      tabs.get(i).setMaxHeight(maxHeightWithHeader - header);
     }
   }
 
-  private void setTable(MyTable t) {
-    progress.setVisible(false);
-
-    scroll.clear();
-    scroll.add(t);
-    scroll.setVisible(true);
-    table = t;
-
-    if (register) {
-      table.setRegisterKeys(true);
-    }
-  }
-
-  private String url(ChangeAndCommit c) {
-    if (c.has_change_number() && c.has_revision_number()) {
-      PatchSet.Id id = c.patch_set_id();
-      return "#" + PageLinks.toChange(
-          id.getParentKey(),
-          String.valueOf(id.get()));
-    }
-
-    GitwebLink gw = Gerrit.getGitwebLink();
-    if (gw != null) {
-      return gw.toRevision(project, c.commit().commit());
-    }
-    return null;
-  }
-
-  private class MyTable extends NavigationTable<ChangeAndCommit> {
-    private final JsArray<ChangeAndCommit> list;
-
-    MyTable(JsArray<ChangeAndCommit> list) {
-      this.list = list;
-      table.setWidth("");
-
-      keysNavigation.setName(Gerrit.C.sectionNavigation());
-      keysNavigation.add(new PrevKeyCommand(0, 'K',
-          Resources.C.previousChange()));
-      keysNavigation.add(new NextKeyCommand(0, 'J', Resources.C.nextChange()));
-      keysNavigation.add(new OpenKeyCommand(0, 'O', Resources.C.openChange()));
-    }
-
-    @Override
-    protected Object getRowItemKey(ChangeAndCommit item) {
-      return item.id();
-    }
-
-    @Override
-    protected ChangeAndCommit getRowItem(int row) {
-      if (0 <= row && row <= list.length()) {
-        return list.get(row);
-      }
-      return null;
-    }
-
-    @Override
-    protected void onOpenRow(int row) {
-      if (0 <= row && row <= list.length()) {
-        ChangeAndCommit c = list.get(row);
-        String url = url(c);
-        if (url != null && url.startsWith("#")) {
-          Gerrit.display(url.substring(1));
-        } else if (url != null) {
-          Window.Location.assign(url);
-        }
-      }
-    }
-
-    void selectRow(int select) {
-      movePointerTo(select, true);
-    }
-  }
-
-  private final class DisplayCommand implements RepeatingCommand {
-    private final SafeHtmlBuilder sb = new SafeHtmlBuilder();
-    private final MyTable table;
+  private abstract class TabCallback<T> implements AsyncCallback<T> {
+    private final Tab tabInfo;
+    private final String project;
     private final String revision;
-    private final JsArray<ChangeAndCommit> list;
-    private boolean attached;
-    private int row;
-    private int select;
-    private double start;
 
-    private DisplayCommand(String revision, JsArray<ChangeAndCommit> list) {
-      this.table = new MyTable(list);
+    TabCallback(Tab tabInfo, String project, String revision) {
+      this.tabInfo = tabInfo;
+      this.project = project;
       this.revision = revision;
-      this.list = list;
+      outstandingCallbacks++;
     }
 
-    public boolean execute() {
-      boolean attachedNow = isAttached();
-      if (!attached && attachedNow) {
-        // Remember that we have been attached at least once. If
-        // later we find we aren't attached we should stop running.
-        attached = true;
-      } else if (attached && !attachedNow) {
-        // If the user navigated away, we aren't in the DOM anymore.
-        // Don't continue to render.
-        return false;
-      }
+    protected abstract JsArray<ChangeAndCommit> convert(T result);
 
-      start = System.currentTimeMillis();
-      while (row < list.length()) {
-        ChangeAndCommit info = list.get(row);
-        if (revision.equals(info.commit().commit())) {
-          select = row;
-        }
-        render(sb, row, info);
-        if ((++row % 10) == 0 && longRunning()) {
-          updateMeter();
-          return true;
+    @Override
+    public void onSuccess(T result) {
+      JsArray<ChangeAndCommit> changes = convert(result);
+      if (changes.length() > 0) {
+        setTabTitle(tabInfo, tabInfo.getTitle(changes.length()));
+        getTab(tabInfo).setChanges(project, revision, changes);
+      }
+      onDone(changes.length() > 0);
+    }
+
+    @Override
+    public void onFailure(Throwable err) {
+      setTabTitle(tabInfo, tabInfo.getTitle(Resources.C.notAvailable()));
+      getTab(tabInfo).setError(err.getMessage());
+      onDone(true);
+    }
+
+    private void onDone(boolean enabled) {
+      setTabEnabled(tabInfo, enabled);
+      outstandingCallbacks--;
+      if (outstandingCallbacks == 0 || (enabled && tabInfo == Tab.RELATED_CHANGES)) {
+        outstandingCallbacks = 0;  // Only execute this block once
+        for (int i = 0; i < getTabBar().getTabCount(); i++) {
+          if (getTabBar().isTabEnabled(i)) {
+            selectTab(i);
+            setVisible(true);
+            applyMaxHeight();
+            break;
+          }
         }
       }
-      table.resetHtml(sb);
-      setTable(table);
-      table.selectRow(select);
-      return false;
-    }
-
-    private void render(SafeHtmlBuilder sb, int row, ChangeAndCommit info) {
-      sb.openTr();
-      sb.openTd().setStyleName(FileTable.R.css().pointer()).closeTd();
-
-      sb.openTd().addStyleName(style.subject());
-      String url = url(info);
-      if (url != null) {
-        sb.openAnchor().setAttribute("href", url);
-        if (url.startsWith("#")) {
-          sb.setAttribute("onclick", OPEN + "(event," + row + ")");
-        }
-        sb.append(info.commit().subject());
-        sb.closeAnchor();
-      } else {
-        sb.append(info.commit().subject());
-      }
-      sb.closeTd();
-
-      sb.closeTr();
-    }
-
-    private void updateMeter() {
-      progress.setValue((100 * row) / list.length());
-    }
-
-    private boolean longRunning() {
-      return System.currentTimeMillis() - start > 200;
     }
   }
 
-  private static class RelatedInfo extends JavaScriptObject {
-    final native JsArray<ChangeAndCommit> changes() /*-{ return this.changes }-*/;
+  private class TabChangeListCallback extends TabCallback<ChangeList> {
+    TabChangeListCallback(Tab tabInfo, String project, String revision) {
+      super(tabInfo, project, revision);
+    }
+
+    @Override
+    protected JsArray<ChangeAndCommit> convert(ChangeList l) {
+      JsArray<ChangeAndCommit> arr = JavaScriptObject.createArray().cast();
+      for (ChangeInfo i : Natives.asList(l)) {
+        if (i.current_revision() != null && i.revisions().containsKey(i.current_revision())) {
+          RevisionInfo currentRevision = i.revision(i.current_revision());
+          ChangeAndCommit c = ChangeAndCommit.create();
+          c.set_id(i.id());
+          c.set_commit(currentRevision.commit());
+          c.set_change_number(i.legacy_id().get());
+          c.set_revision_number(currentRevision._number());
+          c.set_branch(i.branch());
+          arr.push(c);
+        }
+      }
+      return arr;
+    }
+  }
+
+  public static class RelatedInfo extends JavaScriptObject {
+    public final native JsArray<ChangeAndCommit> changes() /*-{ return this.changes }-*/;
     protected RelatedInfo() {
     }
   }
 
-  private static class ChangeAndCommit extends JavaScriptObject {
-    final native String id() /*-{ return this.change_id }-*/;
-    final native CommitInfo commit() /*-{ return this.commit }-*/;
+  public static class ChangeAndCommit extends JavaScriptObject {
+    static ChangeAndCommit create() {
+      return (ChangeAndCommit) createObject();
+    }
 
-    final Change.Id legacy_id() {
+    public final native String id() /*-{ return this.change_id }-*/;
+    public final native CommitInfo commit() /*-{ return this.commit }-*/;
+    final native String branch() /*-{ return this.branch }-*/;
+
+    final native void set_id(String i)
+    /*-{ if(i)this.change_id=i; }-*/;
+
+    final native void set_commit(CommitInfo c)
+    /*-{ if(c)this.commit=c; }-*/;
+
+    final native void set_branch(String b)
+    /*-{ if(b)this.branch=b; }-*/;
+
+    public final Change.Id legacy_id() {
       return has_change_number() ? new Change.Id(_change_number()) : null;
     }
 
-    final PatchSet.Id patch_set_id() {
+    public final PatchSet.Id patch_set_id() {
       return has_change_number() && has_revision_number()
           ? new PatchSet.Id(legacy_id(), _revision_number())
           : null;
     }
 
-    private final native boolean has_change_number()
+    public final native boolean has_change_number()
     /*-{ return this.hasOwnProperty('_change_number') }-*/;
 
-    private final native boolean has_revision_number()
+    final native boolean has_revision_number()
     /*-{ return this.hasOwnProperty('_revision_number') }-*/;
 
-    private final native int _change_number()
+    final native boolean has_current_revision_number()
+    /*-{ return this.hasOwnProperty('_current_revision_number') }-*/;
+
+    final native int _change_number()
     /*-{ return this._change_number }-*/;
 
-    private final native int _revision_number()
+    final native int _revision_number()
     /*-{ return this._revision_number }-*/;
 
+    final native int _current_revision_number()
+    /*-{ return this._current_revision_number }-*/;
+
+    final native void set_change_number(int n)
+    /*-{ this._change_number=n; }-*/;
+
+    final native void set_revision_number(int n)
+    /*-{ this._revision_number=n; }-*/;
+
+    final native void set_current_revision_number(int n)
+    /*-{ this._current_revision_number=n; }-*/;
+
     protected ChangeAndCommit() {
     }
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.ui.xml
deleted file mode 100644
index 7e02c18..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.ui.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Copyright (C) 2013 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-<ui:UiBinder
-    xmlns:ui='urn:ui:com.google.gwt.uibinder'
-    xmlns:g='urn:import:com.google.gwt.user.client.ui'
-    xmlns:x='urn:import:com.google.gwtexpui.progress.client'>
-  <ui:style type='com.google.gerrit.client.change.RelatedChanges.Style'>
-    .subject {
-      width: 355px;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      display: inline-block;
-    }
-    .header, .subject {
-      white-space: nowrap;
-    }
-  </ui:style>
-
-  <g:HTMLPanel>
-    <div ui:field='header' class='{style.header}'
-         title='Same branch changes connected by Git history'>
-      <ui:attribute name='title'/>
-      <ui:msg>Related Changes</ui:msg>
-    </div>
-    <g:ScrollPanel ui:field='scroll' visible='false'/>
-    <x:ProgressBar ui:field='progress'/>
-    <div ui:field='error' aria-hidden='true' style='display: NONE' class='{style.header}'/>
-    <div ui:field='none' aria-hidden='true' style='display: NONE' class='{style.header}'>
-      <ui:msg>No Related Changes</ui:msg>
-    </div>
-  </g:HTMLPanel>
-</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java
new file mode 100644
index 0000000..e1ce99d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java
@@ -0,0 +1,571 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.change;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.GitwebLink;
+import com.google.gerrit.client.change.RelatedChanges.ChangeAndCommit;
+import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.dom.client.AnchorElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.impl.HyperlinkImpl;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.globalkey.client.KeyCommand;
+import com.google.gwtexpui.globalkey.client.KeyCommandSet;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class RelatedChangesTab implements IsWidget {
+  private static final String OPEN = init(DOM.createUniqueId().replace('-', '_'));
+  private static final HyperlinkImpl LINK = GWT.create(HyperlinkImpl.class);
+  private static final SafeHtml POINTER_HTML =
+      AbstractImagePrototype.create(Gerrit.RESOURCES.arrowRight()).getSafeHtml();
+
+  private static final native String init(String o) /*-{
+    $wnd[o] = $entry(@com.google.gerrit.client.change.RelatedChangesTab::onOpen(Lcom/google/gwt/dom/client/NativeEvent;Lcom/google/gwt/dom/client/Element;));
+    return o + '(event,this)';
+  }-*/;
+
+  private static boolean onOpen(NativeEvent evt, Element e) {
+    if (LINK.handleAsClick(evt.<Event>cast())) {
+      Gerrit.display(e.getAttribute("href").substring(1));
+      evt.preventDefault();
+      return false;
+    }
+    return true;
+  }
+
+  private final SimplePanel panel;
+
+  private boolean showBranches;
+  private boolean showIndirectAncestors;
+  private boolean registerKeys;
+  private int maxHeight;
+
+  private String project;
+  private NavigationList view;
+
+  RelatedChangesTab() {
+    panel = new SimplePanel();
+  }
+
+  @Override
+  public Widget asWidget() {
+    return panel;
+  }
+
+  void setShowBranches(boolean showBranches) {
+    this.showBranches = showBranches;
+  }
+
+  void setShowIndirectAncestors(boolean showIndirectAncestors) {
+    this.showIndirectAncestors = showIndirectAncestors;
+  }
+
+  void setMaxHeight(int height) {
+    maxHeight = height;
+    if (view != null) {
+      view.setHeight(height + "px");
+      view.ensureRowMeasurements();
+      view.movePointerTo(view.selectedRow, true);
+    }
+  }
+
+  void registerKeys(boolean on) {
+    registerKeys = on;
+    if (view != null) {
+      view.setRegisterKeys(on);
+    }
+  }
+
+  void setError(String message) {
+    panel.setWidget(new InlineLabel(message));
+    view = null;
+    project = null;
+  }
+
+  void setChanges(String project, String revision, JsArray<ChangeAndCommit> changes) {
+    if (0 == changes.length()) {
+      setError(Resources.C.noChanges());
+      return;
+    }
+
+    this.project = project;
+    view = new NavigationList();
+    panel.setWidget(view);
+
+    DisplayCommand display = new DisplayCommand(revision, changes, view);
+    if (display.execute()) {
+      Scheduler.get().scheduleIncremental(display);
+    }
+  }
+
+  private final class DisplayCommand implements RepeatingCommand {
+    private final String revision;
+    private final JsArray<ChangeAndCommit> changes;
+    private final List<SafeHtml> rows;
+    private final Set<String> connected;
+    private final NavigationList navList;
+
+    private double start;
+    private int row;
+    private int connectedPos;
+    private int selected;
+
+    private DisplayCommand(String revision, JsArray<ChangeAndCommit> changes,
+        NavigationList navList) {
+      this.revision = revision;
+      this.changes = changes;
+      this.navList = navList;
+      rows = new ArrayList<>(changes.length());
+      connectedPos = changes.length() - 1;
+      connected = showIndirectAncestors
+          ? new HashSet<String>(Math.max(changes.length() * 4 / 3, 16))
+          : null;
+    }
+
+    private boolean computeConnected() {
+      // Since TOPO sorted, when can walk the list in reverse and find all
+      // the connections.
+      if (!connected.contains(revision)) {
+        while (connectedPos >= 0) {
+          CommitInfo c = changes.get(connectedPos).commit();
+          connected.add(c.commit());
+          if (longRunning(--connectedPos)) {
+            return true;
+          }
+          if (c.commit().equals(revision)) {
+            break;
+          }
+        }
+      }
+      while (connectedPos >= 0) {
+        CommitInfo c = changes.get(connectedPos).commit();
+        for (int j = 0; j < c.parents().length(); j++) {
+          if (connected.contains(c.parents().get(j).commit())) {
+            connected.add(c.commit());
+            break;
+          }
+        }
+        if (longRunning(--connectedPos)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    public boolean execute() {
+      if (navList != view || !panel.isAttached()) {
+        // If the user navigated away, we aren't in the DOM anymore.
+        // Don't continue to render.
+        return false;
+      }
+
+      start = System.currentTimeMillis();
+
+      if (connected != null && computeConnected()) {
+        return true;
+      }
+
+      while (row < changes.length()) {
+        ChangeAndCommit info = changes.get(row);
+        String commit = info.commit().commit();
+        rows.add(new RowSafeHtml(
+            info, connected != null && !connected.contains(commit)));
+        if (revision.equals(commit)) {
+          selected = row;
+        }
+        if (longRunning(++row)) {
+          return true;
+        }
+      }
+
+      navList.rows = rows;
+      navList.ensureRowMeasurements();
+      navList.movePointerTo(selected, true);
+      return false;
+    }
+
+    private boolean longRunning(int i) {
+      return (i % 10) == 0 && System.currentTimeMillis() - start > 50;
+    }
+  }
+
+  @SuppressWarnings("serial")
+  private class RowSafeHtml implements SafeHtml {
+    private String html;
+    private ChangeAndCommit info;
+    private final boolean notConnected;
+
+    RowSafeHtml(ChangeAndCommit info, boolean notConnected) {
+      this.info = info;
+      this.notConnected = notConnected;
+    }
+
+    @Override
+    public String asString() {
+      if (html == null) {
+        SafeHtmlBuilder sb = new SafeHtmlBuilder();
+        renderRow(sb);
+        html = sb.asString();
+        info = null;
+      }
+      return html;
+    }
+
+    private void renderRow(SafeHtmlBuilder sb) {
+      sb.openDiv().setStyleName(RelatedChanges.R.css().row());
+
+      sb.openSpan().setStyleName(RelatedChanges.R.css().pointer());
+      sb.append(POINTER_HTML);
+      sb.closeSpan();
+
+      sb.openSpan().setStyleName(RelatedChanges.R.css().subject());
+      String url = url();
+      if (url != null) {
+        sb.openAnchor().setAttribute("href", url);
+        if (url.startsWith("#")) {
+          sb.setAttribute("onclick", OPEN);
+        }
+        if (showBranches) {
+          sb.append(info.branch()).append(": ");
+        }
+        sb.append(info.commit().subject());
+        sb.closeAnchor();
+      } else {
+        sb.append(info.commit().subject());
+      }
+      sb.closeSpan();
+
+      sb.openSpan();
+      GitwebLink gw = Gerrit.getGitwebLink();
+      if (gw != null && (!info.has_change_number() || !info.has_revision_number())) {
+        sb.setStyleName(RelatedChanges.R.css().gitweb());
+        sb.setAttribute("title", gw.getLinkName());
+        sb.append('\u25CF');
+      } else if (notConnected) {
+        sb.setStyleName(RelatedChanges.R.css().indirect());
+        sb.setAttribute("title", Resources.C.indirectAncestor());
+        sb.append('~');
+      } else if (info.has_current_revision_number() && info.has_revision_number()
+          && info._current_revision_number() != info._revision_number()) {
+        sb.setStyleName(RelatedChanges.R.css().notCurrent());
+        sb.setAttribute("title", Util.C.notCurrent());
+        sb.append('\u25CF');
+      } else {
+        sb.setStyleName(RelatedChanges.R.css().current());
+      }
+      sb.closeSpan();
+
+      sb.closeDiv();
+    }
+
+    private String url() {
+      if (info.has_change_number() && info.has_revision_number()) {
+        PatchSet.Id id = info.patch_set_id();
+        return "#" + PageLinks.toChange(
+            id.getParentKey(),
+            String.valueOf(id.get()));
+      }
+
+      GitwebLink gw = Gerrit.getGitwebLink();
+      if (gw != null && project != null) {
+        return gw.toRevision(project, info.commit().commit());
+      }
+      return null;
+    }
+  }
+
+  private class NavigationList extends ScrollPanel
+      implements ClickHandler, DoubleClickHandler, ScrollHandler {
+    private final KeyCommandSet keysNavigation;
+    private final Element body;
+    private final Element surrogate;
+    private final Node fragment = createDocumentFragment();
+
+    List<SafeHtml> rows;
+    private HandlerRegistration regNavigation;
+    private int selectedRow;
+    private int startRow;
+    private int rowHeight;
+    private int rowWidth;
+
+    NavigationList() {
+      addDomHandler(this, ClickEvent.getType());
+      addDomHandler(this, DoubleClickEvent.getType());
+      addScrollHandler(this);
+
+      keysNavigation = new KeyCommandSet(Resources.C.relatedChanges());
+      keysNavigation.add(
+          new KeyCommand(0, 'K', Resources.C.previousChange()) {
+            @Override
+            public void onKeyPress(KeyPressEvent event) {
+              movePointerTo(selectedRow - 1, true);
+            }
+          },
+          new KeyCommand(0, 'J', Resources.C.nextChange()) {
+            @Override
+            public void onKeyPress(KeyPressEvent event) {
+              movePointerTo(selectedRow + 1, true);
+            }
+          });
+      keysNavigation.add(new KeyCommand(0, 'O', Resources.C.openChange()) {
+        @Override
+        public void onKeyPress(KeyPressEvent event) {
+          onOpenRow(getRow(selectedRow));
+        }
+      });
+
+      if (maxHeight > 0) {
+        setHeight(maxHeight + "px");
+      }
+
+      body = DOM.createDiv();
+      body.getStyle().setPosition(Style.Position.RELATIVE);
+      body.getStyle().setVisibility(Visibility.HIDDEN);
+      getContainerElement().appendChild(body);
+
+      surrogate = DOM.createDiv();
+      surrogate.getStyle().setVisibility(Visibility.HIDDEN);
+    }
+
+    private boolean ensureRowMeasurements() {
+      if (rowHeight == 0 && rows != null) {
+        surrogate.setInnerSafeHtml(rows.get(0));
+        getContainerElement().appendChild(surrogate);
+        rowHeight = surrogate.getOffsetHeight();
+        rowWidth = surrogate.getOffsetWidth();
+        getContainerElement().removeChild(surrogate);
+        getContainerElement().getStyle()
+            .setHeight(rowHeight * rows.size(), Style.Unit.PX);
+        return true;
+      }
+      return false;
+    }
+
+    public void movePointerTo(int row, boolean scroll) {
+      if (rows != null && 0 <= row && row < rows.size()) {
+        renderSelected(selectedRow, false);
+        selectedRow = row;
+
+        if (scroll && rowHeight != 0) {
+          // Position the selected row in the middle.
+          setVerticalScrollPosition(
+              Math.max(rowHeight * selectedRow - maxHeight / 2, 0));
+          render();
+        }
+        renderSelected(selectedRow, true);
+      }
+    }
+
+    private void renderSelected(int row, boolean selected) {
+      Element e = getRow(row);
+      if (e != null) {
+        if (selected) {
+          e.addClassName(RelatedChanges.R.css().activeRow());
+        } else {
+          e.removeClassName(RelatedChanges.R.css().activeRow());
+        }
+      }
+    }
+
+    private void render() {
+      if (rows == null || rowHeight == 0) {
+        return;
+      }
+
+      int currStart = startRow;
+      int currEnd = startRow + body.getChildCount();
+
+      int vpos = getVerticalScrollPosition();
+      int start = Math.max(vpos / rowHeight - 5, 0);
+      int end = Math.min((vpos + maxHeight) / rowHeight + 5, rows.size());
+      if (currStart <= start && end <= currEnd) {
+        return; // All of the required nodes are already in the DOM.
+      }
+
+      if (end <= currStart) {
+        renderRange(start, end, true, true);
+      } else if (start < currStart) {
+        renderRange(start, currStart, false, true);
+      } else if (start >= currEnd) {
+        renderRange(start, end, true, false);
+      } else if (end > currEnd) {
+        renderRange(currEnd, end, false, false);
+      }
+
+      renderSelected(selectedRow, true);
+
+      if (currEnd == 0) {
+        // Account for the scroll bars
+        int width = body.getOffsetWidth();
+        if (rowWidth > width) {
+          int w = 2 * rowWidth - width;
+          setWidth(w + "px");
+        }
+        body.getStyle().clearVisibility();
+      }
+    }
+
+    private void renderRange(int start, int end, boolean removeAll, boolean insertFirst) {
+      SafeHtmlBuilder sb = new SafeHtmlBuilder();
+      for (int i = start; i < end; i++) {
+        sb.append(rows.get(i));
+      }
+
+      if (removeAll) {
+        body.setInnerSafeHtml(sb);
+      } else {
+        surrogate.setInnerSafeHtml(sb);
+        for (int cnt = surrogate.getChildCount(); cnt > 0; cnt--) {
+          fragment.appendChild(surrogate.getFirstChild());
+        }
+        if (insertFirst) {
+          body.insertFirst(fragment);
+        } else {
+          body.appendChild(fragment);
+        }
+      }
+
+      if (insertFirst || removeAll) {
+        startRow = start;
+        body.getStyle().setTop(start * rowHeight, Style.Unit.PX);
+      }
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+      Element row = getRow(event.getNativeEvent().getEventTarget().<Element>cast());
+      if (row != null) {
+        movePointerTo(startRow + DOM.getChildIndex(body, row), false);
+        event.stopPropagation();
+      }
+    }
+
+    @Override
+    public void onDoubleClick(DoubleClickEvent event) {
+      Element row = getRow(event.getNativeEvent().getEventTarget().<Element>cast());
+      if (row != null) {
+        movePointerTo(startRow + DOM.getChildIndex(body, row), false);
+        onOpenRow(row);
+        event.stopPropagation();
+      }
+    }
+
+    @Override
+    public void onScroll(ScrollEvent event) {
+      render();
+    }
+
+    private Element getRow(Element e) {
+      for (Element prev = e; e != null; prev = e) {
+        if ((e = DOM.getParent(e)) == body) {
+          return prev;
+        }
+      }
+      return null;
+    }
+
+    private Element getRow(int row) {
+      if (startRow <= row && row < startRow + body.getChildCount()) {
+        return body.getChild(row - startRow).cast();
+      }
+      return null;
+    }
+
+    private void onOpenRow(Element row) {
+      // Find the first HREF of the anchor of the select row (if any)
+      if (row != null) {
+        NodeList<Element> nodes =
+            row.getElementsByTagName(AnchorElement.TAG);
+        for (int i = 0; i < nodes.getLength(); i++) {
+          String url = nodes.getItem(i).getAttribute("href");
+          if (!url.isEmpty()) {
+            if (url.startsWith("#")) {
+              Gerrit.display(url.substring(1));
+            } else {
+              Window.Location.assign(url);
+            }
+            break;
+          }
+        }
+      }
+    }
+
+    @Override
+    protected void onLoad() {
+      super.onLoad();
+      setRegisterKeys(registerKeys);
+    }
+
+    @Override
+    protected void onUnload() {
+      setRegisterKeys(false);
+      super.onUnload();
+    }
+
+    public void setRegisterKeys(boolean on) {
+      if (on && isAttached()) {
+        if (regNavigation == null) {
+          regNavigation = GlobalKey.add(this, keysNavigation);
+        }
+        if (view.ensureRowMeasurements()) {
+          view.movePointerTo(view.selectedRow, true);
+        }
+      } else if (regNavigation != null) {
+        regNavigation.removeHandler();
+        regNavigation = null;
+      }
+    }
+  }
+
+  private static final native Node createDocumentFragment() /*-{
+    return $doc.createDocumentFragment();
+  }-*/;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reload.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reload.java
deleted file mode 100644
index 7b88b2b..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reload.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.client.change;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeInfo;
-import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.MouseOutEvent;
-import com.google.gwt.event.dom.client.MouseOutHandler;
-import com.google.gwt.event.dom.client.MouseOverEvent;
-import com.google.gwt.event.dom.client.MouseOverHandler;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.impl.HyperlinkImpl;
-
-class Reload extends Image implements ClickHandler,
-    MouseOverHandler, MouseOutHandler {
-  private static final HyperlinkImpl link = GWT.create(HyperlinkImpl.class);
-  private Change.Id changeId;
-  private boolean in;
-
-  Reload() {
-    setResource(Resources.I.reload_black());
-    addClickHandler(this);
-    addMouseOverHandler(this);
-    addMouseOutHandler(this);
-  }
-
-  void set(ChangeInfo info) {
-    changeId = info.legacy_id();
-  }
-
-  void reload() {
-    Gerrit.display(PageLinks.toChange(changeId));
-  }
-
-  @Override
-  public void onMouseOver(MouseOverEvent event) {
-    if (!in) {
-      in = true;
-      setResource(Resources.I.reload_white());
-    }
-  }
-
-  @Override
-  public void onMouseOut(MouseOutEvent event) {
-    if (in) {
-      in = false;
-      setResource(Resources.I.reload_black());
-    }
-  }
-
-  @Override
-  public void onClick(ClickEvent e) {
-    if (link.handleAsClick(e.getNativeEvent().<Event> cast())) {
-      e.preventDefault();
-      e.stopPropagation();
-      reload();
-    }
-  }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
index d320df4..aaa301c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyAction.java
@@ -16,11 +16,14 @@
 
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
 import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.event.logical.shared.CloseEvent;
 import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
@@ -30,6 +33,7 @@
   private final PatchSet.Id psId;
   private final String revision;
   private final ChangeScreen2.Style style;
+  private final CommentLinkProcessor clp;
   private final Widget replyButton;
 
   private NativeMap<LabelInfo> allLabels;
@@ -42,12 +46,14 @@
       ChangeInfo info,
       String revision,
       ChangeScreen2.Style style,
+      CommentLinkProcessor clp,
       Widget replyButton) {
     this.psId = new PatchSet.Id(
         info.legacy_id(),
         info.revisions().get(revision)._number());
     this.revision = revision;
     this.style = style;
+    this.clp = clp;
     this.replyButton = replyButton;
 
     boolean current = revision.equals(info.current_revision());
@@ -57,7 +63,18 @@
         : NativeMap.<JsArrayString> create();
   }
 
-  void onReply() {
+  String getMessage() {
+    return replyBox != null ? replyBox.getMessage() : null;
+  }
+
+  void hide() {
+    if (popup != null) {
+      popup.hide();
+    }
+    return;
+  }
+
+  void onReply(MessageInfo msg) {
     if (popup != null) {
       popup.hide();
       return;
@@ -65,6 +82,7 @@
 
     if (replyBox == null) {
       replyBox = new ReplyBox(
+          clp,
           psId,
           revision,
           allLabels,
@@ -72,9 +90,14 @@
       allLabels = null;
       permittedLabels = null;
     }
+    if (msg != null) {
+      replyBox.replyTo(msg);
+    }
 
-    final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+    final PluginSafePopupPanel p = new PluginSafePopupPanel(true, false);
     p.setStyleName(style.replyBox());
+    p.setGlassEnabled(true);
+    p.setGlassStyleName("");
     p.addAutoHidePartner(replyButton.getElement());
     p.addCloseHandler(new CloseHandler<PopupPanel>() {
       @Override
@@ -85,6 +108,7 @@
       }
     });
     p.add(replyBox);
+    Window.scrollTo(0, 0);
     p.showRelativeTo(replyButton);
     GlobalKey.dialog(p);
     popup = p;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
index c8eb13a..64dcd28 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -14,40 +14,60 @@
 
 package com.google.gerrit.client.change;
 
+import static com.google.gwt.event.dom.client.KeyCodes.KEY_ENTER;
+
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
 import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
+import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
+import com.google.gerrit.client.changes.CommentApi;
+import com.google.gerrit.client.changes.CommentInfo;
 import com.google.gerrit.client.changes.ReviewInput;
+import com.google.gerrit.client.changes.Util;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.LabelValue;
+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.core.client.JsArray;
 import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.dom.client.MouseOverHandler;
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.event.logical.shared.ValueChangeHandler;
 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.uibinder.client.UiHandler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.TextArea;
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtexpui.globalkey.client.NpTextArea;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -56,73 +76,113 @@
 import java.util.TreeSet;
 
 class ReplyBox extends Composite {
+  private static final String CODE_REVIEW = "Code-Review";
+
   interface Binder extends UiBinder<HTMLPanel, ReplyBox> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
   interface Styles extends CssResource {
     String label_name();
     String label_value();
+    String label_help();
   }
 
+  private final CommentLinkProcessor clp;
   private final PatchSet.Id psId;
   private final String revision;
   private ReviewInput in = ReviewInput.create();
-  private List<Runnable> lgtm;
+  private int labelHelpColumn;
+  private Runnable lgtm;
 
   @UiField Styles style;
-  @UiField NpTextArea message;
+  @UiField TextArea message;
   @UiField Element labelsParent;
   @UiField Grid labelsTable;
-  @UiField Button send;
+  @UiField Button post;
   @UiField CheckBox email;
   @UiField Button cancel;
+  @UiField ScrollPanel commentsPanel;
+  @UiField FlowPanel comments;
 
   ReplyBox(
+      CommentLinkProcessor clp,
       PatchSet.Id psId,
       String revision,
       NativeMap<LabelInfo> all,
       NativeMap<JsArrayString> permitted) {
+    this.clp = clp;
     this.psId = psId;
     this.revision = revision;
     initWidget(uiBinder.createAndBindUi(this));
 
-    List<String> names = new ArrayList<String>(permitted.keySet());
+    List<String> names = new ArrayList<>(permitted.keySet());
     if (names.isEmpty()) {
       UIObject.setVisible(labelsParent, false);
     } else {
       Collections.sort(names);
-      lgtm = new ArrayList<Runnable>(names.size());
       renderLabels(names, all, permitted);
     }
+
+    addDomHandler(
+      new KeyPressHandler() {
+        @Override
+        public void onKeyPress(KeyPressEvent e) {
+          e.stopPropagation();
+          if ((e.getCharCode() == '\n' || e.getCharCode() == KEY_ENTER)
+              && e.isControlKeyDown()) {
+            e.preventDefault();
+            if (post.isEnabled()) {
+              onPost(null);
+            }
+          }
+        }
+      },
+      KeyPressEvent.getType());
   }
 
   @Override
   protected void onLoad() {
+    commentsPanel.setVisible(false);
+    post.setEnabled(false);
+    CommentApi.drafts(psId, new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
+      @Override
+      public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+        attachComments(result);
+        displayComments(result);
+        post.setEnabled(true);
+      }
+
+      @Override
+      public void onFailure(Throwable caught) {
+        post.setEnabled(true);
+      }
+    });
+
     Scheduler.get().scheduleDeferred(new ScheduledCommand() {
       @Override
       public void execute() {
         message.setFocus(true);
       }});
+    Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
+      @Override
+      public boolean execute() {
+        String t = message.getText();
+        if (t != null) {
+          message.setCursorPos(t.length());
+        }
+        return false;
+      }}, 0);
   }
 
   @UiHandler("message")
   void onMessageKey(KeyPressEvent event) {
-    if ((event.getCharCode() == '\n' || event.getCharCode() == KeyCodes.KEY_ENTER)
-        && event.isControlKeyDown()) {
-      event.preventDefault();
-      event.stopPropagation();
-      onSend(null);
-    } else if (lgtm != null
+    if (lgtm != null
         && event.getCharCode() == 'M'
         && message.getValue().equals("LGT")) {
       Scheduler.get().scheduleDeferred(new ScheduledCommand() {
         @Override
         public void execute() {
-          if (message.getValue().startsWith("LGTM")) {
-            for (Runnable r : lgtm) {
-              r.run();
-            }
-          }
+          lgtm.run();
         }
       });
     }
@@ -137,9 +197,10 @@
     }
   }
 
-  @UiHandler("send")
-  void onSend(ClickEvent e) {
-    in.message(message.getText().trim());
+  @UiHandler("post")
+  void onPost(ClickEvent e) {
+    in.message(getMessage());
+    in.prePost();
     ChangeApi.revision(psId.getParentKey().get(), revision)
       .view("review")
       .post(in, new GerritCallback<ReviewInput>() {
@@ -153,11 +214,60 @@
     hide();
   }
 
+  String getMessage() {
+    return message.getText().trim();
+  }
+
   @UiHandler("cancel")
   void onCancel(ClickEvent e) {
     hide();
   }
 
+  void replyTo(MessageInfo msg) {
+    if (msg.message() != null) {
+      String t = message.getText();
+      String m = quote(msg);
+      if (t == null || t.isEmpty()) {
+        t = m;
+      } else if (t.endsWith("\n\n")) {
+        t += m;
+      } else if (t.endsWith("\n")) {
+        t += "\n" + m;
+      } else {
+        t += "\n\n" + m;
+      }
+      message.setText(t + "\n\n");
+    }
+  }
+
+  private static String quote(MessageInfo msg) {
+    String m = msg.message().trim();
+    if (m.startsWith("Patch Set ")) {
+      int i = m.indexOf('\n');
+      if (i > 0) {
+        m = m.substring(i + 1).trim();
+      }
+    }
+    StringBuilder quotedMsg = new StringBuilder();
+    for (String line : m.split("\\n")) {
+      line = line.trim();
+      while (line.length() > 67) {
+        int i = line.lastIndexOf(' ', 67);
+        if (i < 50) {
+          i = line.indexOf(' ', 67);
+        }
+        if (i > 0) {
+          quotedMsg.append(" > ").append(line.substring(0, i)).append("\n");
+          line = line.substring(i + 1);
+        } else {
+          break;
+        }
+      }
+      quotedMsg.append(" > ").append(line).append("\n");
+    }
+    return quotedMsg.toString().substring(0, quotedMsg.length() - 1); // remove last '\n'
+  }
+
   private void hide() {
     for (Widget w = getParent(); w != null; w = w.getParent()) {
       if (w instanceof PopupPanel) {
@@ -171,92 +281,94 @@
       List<String> names,
       NativeMap<LabelInfo> all,
       NativeMap<JsArrayString> permitted) {
-    TreeSet<Short> values = new TreeSet<Short>();
+    TreeSet<Short> values = new TreeSet<>();
+    List<LabelAndValues> labels = new ArrayList<>(permitted.size());
     for (String id : names) {
       JsArrayString p = permitted.get(id);
       if (p != null) {
+        Set<Short> a = new TreeSet<>();
         for (int i = 0; i < p.length(); i++) {
-          values.add(LabelInfo.parseValue(p.get(i)));
+          a.add(LabelInfo.parseValue(p.get(i)));
         }
+        labels.add(new LabelAndValues(all.get(id), a));
+        values.addAll(a);
       }
     }
-    List<Short> columns = new ArrayList<Short>(values);
+    List<Short> columns = new ArrayList<>(values);
 
-    labelsTable.resize(1 + permitted.size(), 1 + values.size());
+    labelsTable.resize(1 + labels.size(), 2 + values.size());
     for (int c = 0; c < columns.size(); c++) {
       labelsTable.setText(0, 1 + c, LabelValue.formatValue(columns.get(c)));
       labelsTable.getCellFormatter().setStyleName(0, 1 + c, style.label_value());
     }
 
-    List<String> checkboxes = new ArrayList<String>(permitted.size());
+    List<LabelAndValues> checkboxes = new ArrayList<>(labels.size());
     int row = 1;
-    for (String id : names) {
-      Set<Short> vals = all.get(id).value_set();
-      if (isCheckBox(vals)) {
-        checkboxes.add(id);
+    for (LabelAndValues lv : labels) {
+      if (isCheckBox(lv.info.value_set())) {
+        checkboxes.add(lv);
       } else {
-        renderRadio(row++, id, columns, vals, all.get(id));
+        renderRadio(row++, columns, lv);
       }
     }
-    for (String id : checkboxes) {
-      renderCheckBox(row++, id, all.get(id));
+    for (LabelAndValues lv : checkboxes) {
+      renderCheckBox(row++, lv);
     }
   }
 
-  private void renderRadio(int row, final String id,
+  private void renderRadio(int row,
       List<Short> columns,
-      Set<Short> values,
-      LabelInfo info) {
+      LabelAndValues lv) {
+    String id = lv.info.name();
+
+    labelHelpColumn = 1 + columns.size();
     labelsTable.setText(row, 0, id);
-    labelsTable.getCellFormatter().setStyleName(row, 0, style.label_name());
+
+    CellFormatter fmt = labelsTable.getCellFormatter();
+    fmt.setStyleName(row, 0, style.label_name());
+    fmt.setStyleName(row, labelHelpColumn, style.label_help());
 
     ApprovalInfo self = Gerrit.isSignedIn()
-        ? info.for_user(Gerrit.getUserAccount().getId().get())
+        ? lv.info.for_user(Gerrit.getUserAccount().getId().get())
         : null;
 
-    final List<RadioButton> group = new ArrayList<RadioButton>(values.size());
+    final LabelRadioGroup group =
+        new LabelRadioGroup(row, id, lv.permitted.size());
     for (int i = 0; i < columns.size(); i++) {
-      final Short v = columns.get(i);
-      if (values.contains(v)) {
-        RadioButton b = new RadioButton(id);
-        b.setTitle(info.value_text(LabelValue.formatValue(v)));
+      Short v = columns.get(i);
+      if (lv.permitted.contains(v)) {
+        String text = lv.info.value_text(LabelValue.formatValue(v));
+        LabelRadioButton b = new LabelRadioButton(group, text, v);
         if ((self != null && v == self.value()) || (self == null && v == 0)) {
           b.setValue(true);
+          group.select(b);
+          labelsTable.setText(row, labelHelpColumn, b.text);
         }
-        b.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
-          @Override
-          public void onValueChange(ValueChangeEvent<Boolean> event) {
-            if (event.getValue()) {
-              in.label(id, v);
-            }
-          }
-        });
-        group.add(b);
+        group.buttons.add(b);
         labelsTable.setWidget(row, 1 + i, b);
       }
     }
 
-    if (!group.isEmpty()) {
-      lgtm.add(new Runnable() {
+    if (CODE_REVIEW.equalsIgnoreCase(id) && !group.buttons.isEmpty()) {
+      lgtm = new Runnable() {
         @Override
         public void run() {
-          for (int i = 0; i < group.size() - 1; i++) {
-            group.get(i).setValue(false, false);
-          }
-          group.get(group.size() - 1).setValue(true, true);
+          group.selectMax();
         }
-      });
+      };
     }
   }
 
-  private void renderCheckBox(int row, final String id, LabelInfo info) {
+  private void renderCheckBox(int row, LabelAndValues lv) {
     ApprovalInfo self = Gerrit.isSignedIn()
-        ? info.for_user(Gerrit.getUserAccount().getId().get())
+        ? lv.info.for_user(Gerrit.getUserAccount().getId().get())
         : null;
 
+    final String id = lv.info.name();
     final CheckBox b = new CheckBox();
     b.setText(id);
-    b.setTitle(info.value_text("+1"));
+    b.setTitle(lv.info.value_text("+1"));
+    b.setEnabled(lv.permitted.contains((short) 1));
     if (self != null && self.value() == 1) {
       b.setValue(true);
     }
@@ -269,12 +381,14 @@
     b.setStyleName(style.label_name());
     labelsTable.setWidget(row, 0, b);
 
-    lgtm.add(new Runnable() {
-      @Override
-      public void run() {
-        b.setValue(true, true);
-      }
-    });
+    if (CODE_REVIEW.equalsIgnoreCase(id)) {
+      lgtm = new Runnable() {
+        @Override
+        public void run() {
+          b.setValue(true, true);
+        }
+      };
+    }
   }
 
   private static boolean isCheckBox(Set<Short> values) {
@@ -282,4 +396,124 @@
         && values.contains((short) 0)
         && values.contains((short) 1);
   }
+
+  private void attachComments(NativeMap<JsArray<CommentInfo>> result) {
+    in.drafts(ReviewInput.DraftHandling.KEEP);
+    in.comments(result);
+  }
+
+  private void displayComments(NativeMap<JsArray<CommentInfo>> m) {
+    comments.clear();
+
+    JsArray<CommentInfo> l = m.get(Patch.COMMIT_MSG);
+    if (l != null) {
+      comments.add(new FileComments(clp, psId,
+          Util.C.commitMessage(), copyPath(Patch.COMMIT_MSG, l)));
+    }
+
+    List<String> paths = new ArrayList<>(m.keySet());
+    Collections.sort(paths);
+
+    for (String path : paths) {
+      if (!path.equals(Patch.COMMIT_MSG)) {
+        comments.add(new FileComments(clp, psId,
+            path, copyPath(path, m.get(path))));
+      }
+    }
+
+    commentsPanel.setVisible(comments.getWidgetCount() > 0);
+  }
+
+  private static List<CommentInfo> copyPath(String path, JsArray<CommentInfo> l) {
+    for (int i = 0; i < l.length(); i++) {
+      l.get(i).path(path);
+    }
+    return Natives.asList(l);
+  }
+
+  private static class LabelAndValues {
+    final LabelInfo info;
+    final Set<Short> permitted;
+
+    LabelAndValues(LabelInfo info, Set<Short> permitted) {
+      this.info = info;
+      this.permitted = permitted;
+    }
+  }
+
+  private class LabelRadioGroup {
+    final int row;
+    final String label;
+    final List<LabelRadioButton> buttons;
+    LabelRadioButton selected;
+
+    LabelRadioGroup(int row, String label, int cnt) {
+      this.row = row;
+      this.label = label;
+      this.buttons = new ArrayList<>(cnt);
+    }
+
+    void select(LabelRadioButton b) {
+      selected = b;
+      labelsTable.setText(row, labelHelpColumn, b.value != 0 ? b.text : "");
+    }
+
+    void selectMax() {
+      for (int i = 0; i < buttons.size() - 1; i++) {
+        buttons.get(i).setValue(false, false);
+      }
+
+      LabelRadioButton max = buttons.get(buttons.size() - 1);
+      max.setValue(true, true);
+      max.select();
+    }
+  }
+
+  private class LabelRadioButton extends RadioButton implements
+      ValueChangeHandler<Boolean>, ClickHandler, MouseOverHandler,
+      MouseOutHandler {
+    private final LabelRadioGroup group;
+    private final String text;
+    private final short value;
+
+    LabelRadioButton(LabelRadioGroup group, String text, short value) {
+      super(group.label);
+      this.group = group;
+      this.text = text;
+      this.value = value;
+      addValueChangeHandler(this);
+      addClickHandler(this);
+      addMouseOverHandler(this);
+      addMouseOutHandler(this);
+    }
+
+    @Override
+    public void onValueChange(ValueChangeEvent<Boolean> event) {
+      if (event.getValue()) {
+        select();
+      }
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+      select();
+    }
+
+    void select() {
+      group.select(this);
+      in.label(group.label, value);
+    }
+
+    @Override
+    public void onMouseOver(MouseOverEvent event) {
+      labelsTable.setText(group.row, labelHelpColumn, text);
+    }
+
+    @Override
+    public void onMouseOut(MouseOutEvent event) {
+      LabelRadioButton b = group.selected;
+      String s = b != null && b.value != 0 ? b.text : "";
+      labelsTable.setText(group.row, labelHelpColumn, s);
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
index 0a7a728..1b122c5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.ui.xml
@@ -16,36 +16,40 @@
 -->
 <ui:UiBinder
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
-    xmlns:g='urn:import:com.google.gwt.user.client.ui'
-    xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'>
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
   <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
   <ui:style type='com.google.gerrit.client.change.ReplyBox.Styles'>
     .replyBox {
-      max-height: 260px;
     }
-
     .label_name {
       font-weight: bold;
       text-align: left;
+      white-space: nowrap;
     }
     .label_name input { margin-left: 0; }
-
+    .label_help {
+      padding-left: 5px;
+      white-space: nowrap;
+    }
     .label_value {
       text-align: center;
     }
-    .email {
-      display: inline-block;
-      margin-left: 2em;
-    }
     .cancel {
       position: absolute;
       bottom: 5px;
       right: 5px;
     }
+    .comments {
+      max-height: 275px;
+      width: 526px;
+    }
+    .comments p {
+      margin: 5px 0 5px 0;
+    }
   </ui:style>
   <g:HTMLPanel styleName='{style.replyBox}'>
     <div class='{res.style.section}'>
-      <c:NpTextArea
+      <g:TextArea
          visibleLines='5'
          characterWidth='70'
          ui:field='message'/>
@@ -53,17 +57,18 @@
     <div class='{res.style.section}' ui:field='labelsParent'>
       <g:Grid ui:field='labelsTable'/>
     </div>
+    <g:ScrollPanel ui:field='commentsPanel'
+        styleName='{style.comments}'
+        addStyleNames='{res.style.section}'>
+      <g:FlowPanel ui:field='comments'/>
+    </g:ScrollPanel>
     <div class='{res.style.section}' style='position: relative'>
-      <g:Button ui:field='send'
-          title='Send reply (Shortcut: Ctrl-Enter)'
+      <ui:msg><g:Button ui:field='post'
+          title='Post reply (Shortcut: Ctrl-Enter)'
           styleName='{res.style.button}'>
         <ui:attribute name='title'/>
-        <div><ui:msg>Send</ui:msg></div>
-      </g:Button>
-
-      <div class='{style.email}'>
-        <ui:msg>and <g:CheckBox ui:field='email' value='true'>send email</g:CheckBox></ui:msg>
-      </div>
+        <div>Post</div>
+      </g:Button> and <g:CheckBox ui:field='email' value='true'>send email</g:CheckBox></ui:msg>
 
       <g:Button ui:field='cancel'
           title='Close reply form (Shortcut: Esc)'
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java
index a67ba8a..cbbb5b9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Resources.java
@@ -26,8 +26,6 @@
 
   @Source("star_open.png") ImageResource star_open();
   @Source("star_filled.png") ImageResource star_filled();
-  @Source("reload_black.png") ImageResource reload_black();
-  @Source("reload_white.png") ImageResource reload_white();
   @Source("remove_reviewer.png") ImageResource remove_reviewer();
   @Source("common.css") Style style();
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java
index 7575869..b5a3023 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RestReviewerSuggestOracle.java
@@ -42,7 +42,7 @@
           @Override
           public void onSuccess(JsArray<SuggestReviewerInfo> result) {
             final List<RestReviewerSuggestion> r =
-                new ArrayList<RestReviewerSuggestion>(result.length());
+                new ArrayList<>(result.length());
             for (final SuggestReviewerInfo reviewer : Natives.asList(result)) {
               r.add(new RestReviewerSuggestion(reviewer));
             }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
index 1902d97..57ba4b9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
@@ -18,18 +18,20 @@
 import com.google.gerrit.client.ConfirmationDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.account.AccountInfo;
-import com.google.gerrit.client.changes.ApprovalTable.PostInput;
-import com.google.gerrit.client.changes.ApprovalTable.PostResult;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
 import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
 import com.google.gerrit.client.changes.Util;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.HintTextBox;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.KeyCodes;
@@ -57,11 +59,11 @@
 import java.util.Set;
 
 /** Add reviewers. */
-class Reviewers extends Composite {
+public class Reviewers extends Composite {
   interface Binder extends UiBinder<HTMLPanel, Reviewers> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
-  @UiField Element ccText;
+  @UiField Element reviewersText;
   @UiField Button openForm;
   @UiField Element form;
   @UiField Element error;
@@ -69,7 +71,7 @@
   SuggestBox suggestBox;
 
   private ChangeScreen2.Style style;
-  private Element reviewersText;
+  private Element ccText;
 
   private RestReviewerSuggestOracle reviewerSuggestOracle;
   private HintTextBox nameTxtBox;
@@ -112,9 +114,9 @@
     });
   }
 
-  void init(ChangeScreen2.Style style, Element reviewersText) {
+  void init(ChangeScreen2.Style style, Element ccText) {
     this.style = style;
-    this.reviewersText = reviewersText;
+    this.ccText = ccText;
   }
 
   void set(ChangeInfo info) {
@@ -209,8 +211,8 @@
   }
 
   private void display(ChangeInfo info) {
-    Map<Integer, AccountInfo> r = new HashMap<Integer, AccountInfo>();
-    Map<Integer, AccountInfo> cc = new HashMap<Integer, AccountInfo>();
+    Map<Integer, AccountInfo> r = new HashMap<>();
+    Map<Integer, AccountInfo> cc = new HashMap<>();
     for (LabelInfo label : Natives.asList(info.all_labels().values())) {
       if (label.all() != null) {
         for (ApprovalInfo ai : Natives.asList(label.all())) {
@@ -224,17 +226,81 @@
     r.remove(info.owner()._account_id());
     cc.remove(info.owner()._account_id());
 
-    Set<Integer> removable = new HashSet<Integer>();
+    Set<Integer> removable = new HashSet<>();
     if (info.removable_reviewers() != null) {
       for (AccountInfo a : Natives.asList(info.removable_reviewers())) {
         removable.add(a._account_id());
       }
     }
 
-    SafeHtml rHtml = Labels.formatUserList(style, r.values(), removable);
-    SafeHtml ccHtml = Labels.formatUserList(style, cc.values(), removable);
+    Map<Integer, VotableInfo> votable = votable(info);
+
+    SafeHtml rHtml = Labels.formatUserList(style,
+        r.values(), removable, votable);
+    SafeHtml ccHtml = Labels.formatUserList(style,
+        cc.values(), removable, votable);
 
     reviewersText.setInnerSafeHtml(rHtml);
     ccText.setInnerSafeHtml(ccHtml);
   }
+
+  private static Map<Integer, VotableInfo> votable(ChangeInfo change) {
+    Map<Integer, VotableInfo> d = new HashMap<>();
+    for (String name : change.labels()) {
+      LabelInfo label = change.label(name);
+      if (label.all() != null) {
+        for (ApprovalInfo ai : Natives.asList(label.all())) {
+          int id = ai._account_id();
+          VotableInfo ad = d.get(id);
+          if (ad == null) {
+            ad = new VotableInfo();
+            d.put(id, ad);
+          }
+          if (ai.has_value()) {
+            ad.votable(name);
+          }
+        }
+      }
+    }
+    return d;
+  }
+
+
+  public static class PostInput extends JavaScriptObject {
+    public static PostInput create(String reviewer, boolean confirmed) {
+      PostInput input = createObject().cast();
+      input.init(reviewer, confirmed);
+      return input;
+    }
+
+    private native void init(String reviewer, boolean confirmed) /*-{
+      this.reviewer = reviewer;
+      if (confirmed) {
+        this.confirmed = true;
+      }
+    }-*/;
+
+    protected PostInput() {
+    }
+  }
+
+  public static class ReviewerInfo extends AccountInfo {
+    final Set<String> approvals() {
+      return Natives.keys(_approvals());
+    }
+    final native String approval(String l) /*-{ return this.approvals[l]; }-*/;
+    private final native NativeMap<NativeString> _approvals() /*-{ return this.approvals; }-*/;
+
+    protected ReviewerInfo() {
+    }
+  }
+
+  public static class PostResult extends JavaScriptObject {
+    public final native JsArray<ReviewerInfo> reviewers() /*-{ return this.reviewers; }-*/;
+    public final native boolean confirm() /*-{ return this.confirm || false; }-*/;
+    public final native String error() /*-{ return this.error; }-*/;
+
+    protected PostResult() {
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
index 9aed0d7..dd2ef78 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.ui.xml
@@ -20,13 +20,22 @@
     xmlns:g='urn:import:com.google.gwt.user.client.ui'>
   <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
   <ui:style>
-    .openAdd {
-      cursor: pointer;
+    button.openAdd {
+      margin: 3px 3px 0 0;
       float: right;
-      padding: 0;
-      margin: 0;
-      border: 0;
-      background-color: transparent;
+      color: #444;
+      background-color: #f5f5f5;
+      background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+      -webkit-border-radius: 2px;
+      -moz-border-radius: 2px;
+      border-radius: 2px;
+      -webkit-box-sizing: content-box;
+      -moz-box-sizing: content-box;
+      box-sizing: content-box;
+    }
+    button.openAdd div {
+      width: auto;
+      color: #444;
     }
 
     .suggestBox {
@@ -44,13 +53,14 @@
   </ui:style>
   <g:HTMLPanel>
     <div>
-      <span ui:field='ccText'/>
+      <span ui:field='reviewersText'/>
       <g:Button ui:field='openForm'
          title='Add reviewers to this change'
-         styleName='{style.openAdd}'
+         styleName='{res.style.button}'
+         addStyleNames='{style.openAdd}'
          visible='false'>
        <ui:attribute name='title'/>
-       <div>[+]</div>
+       <div><ui:msg>Add&#8230;</ui:msg></div>
       </g:Button>
     </div>
     <div ui:field='form' style='display: none' aria-hidden='true'>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java
index 8a3aae5..4ca992b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/SubmitAction.java
@@ -15,7 +15,10 @@
 package com.google.gerrit.client.change;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.api.ChangeGlue;
 import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
 import com.google.gerrit.client.changes.SubmitFailureDialog;
 import com.google.gerrit.client.changes.SubmitInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
@@ -23,25 +26,29 @@
 import com.google.gerrit.reviewdb.client.Change;
 
 class SubmitAction {
-  static void call(final Change.Id id, String revision) {
-    ChangeApi.submit(id.get(), revision,
-      new GerritCallback<SubmitInfo>() {
-        public void onSuccess(SubmitInfo result) {
-          redisplay();
-        }
-
-        public void onFailure(Throwable err) {
-          if (SubmitFailureDialog.isConflict(err)) {
-            new SubmitFailureDialog(err.getMessage()).center();
+  static void call(ChangeInfo changeInfo, RevisionInfo revisionInfo) {
+    if (ChangeGlue.onSubmitChange(changeInfo, revisionInfo)) {
+      final Change.Id changeId = changeInfo.legacy_id();
+      ChangeApi.submit(
+        changeId.get(), revisionInfo.name(),
+        new GerritCallback<SubmitInfo>() {
+          public void onSuccess(SubmitInfo result) {
             redisplay();
-          } else {
-            super.onFailure(err);
           }
-        }
 
-        private void redisplay() {
-          Gerrit.display(PageLinks.toChange(id));
-        }
-      });
+          public void onFailure(Throwable err) {
+            if (SubmitFailureDialog.isConflict(err)) {
+              new SubmitFailureDialog(err.getMessage()).center();
+            } else {
+              super.onFailure(err);
+            }
+            redisplay();
+          }
+
+          private void redisplay() {
+            Gerrit.display(PageLinks.toChange(changeId));
+          }
+        });
+    }
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java
index 4182da7..e9f07c1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.java
@@ -18,6 +18,8 @@
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.BranchLink;
+import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
@@ -31,12 +33,9 @@
 import com.google.gwt.uibinder.client.UiHandler;
 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.HTMLPanel;
 import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.InlineLabel;
 import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwtexpui.globalkey.client.NpTextArea;
 import com.google.gwtexpui.globalkey.client.NpTextBox;
 
 /** Displays (and edits) the change topic string. */
@@ -47,19 +46,18 @@
   private PatchSet.Id psId;
   private boolean canEdit;
 
-  @UiField FlowPanel show;
-  @UiField InlineLabel text;
+  @UiField Element show;
+  @UiField InlineHyperlink text;
   @UiField Image editIcon;
 
   @UiField Element form;
   @UiField NpTextBox input;
-  @UiField NpTextArea message;
   @UiField Button save;
   @UiField Button cancel;
 
   Topic() {
     initWidget(uiBinder.createAndBindUi(this));
-    show.addDomHandler(
+    editIcon.addDomHandler(
       new ClickHandler() {
         @Override
         public void onClick(ClickEvent event) {
@@ -78,20 +76,30 @@
         info.legacy_id(),
         info.revisions().get(revision)._number());
 
-    text.setText(info.topic());
+    initTopicLink(info);
     editIcon.setVisible(canEdit);
     if (!canEdit) {
       show.setTitle(null);
     }
   }
 
+  private void initTopicLink(ChangeInfo info) {
+    text.setText(info.topic());
+    text.setTargetHistoryToken(
+        PageLinks.toChangeQuery(
+            BranchLink.query(
+                info.project_name_key(),
+                info.status(),
+                info.branch(),
+                info.topic())));
+  }
+
   boolean canEdit() {
     return canEdit;
   }
 
   void onEdit() {
     if (canEdit) {
-      show.setVisible(false);
       UIObject.setVisible(form, true);
 
       input.setText(text.getText());
@@ -102,7 +110,6 @@
   @UiHandler("cancel")
   void onCancel(ClickEvent e) {
     input.setFocus(false);
-    show.setVisible(true);
     UIObject.setVisible(form, false);
   }
 
@@ -116,22 +123,11 @@
     }
   }
 
-  @UiHandler("message")
-  void onKeyDownMessage(KeyDownEvent e) {
-    if (e.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
-      onCancel(null);
-    } else if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER
-        && e.isControlKeyDown()) {
-      onSave(null);
-    }
-  }
-
   @UiHandler("save")
   void onSave(ClickEvent e) {
     ChangeApi.topic(
         psId.getParentKey().get(),
         input.getValue().trim(),
-        message.getValue().trim(),
         new GerritCallback<String>() {
           @Override
           public void onSuccess(String result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.ui.xml
index 2f4751c..4031ba9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Topic.ui.xml
@@ -17,7 +17,8 @@
 <ui:UiBinder
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
-    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:x='urn:import:com.google.gerrit.client.ui'>
   <ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
   <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
   <ui:style>
@@ -25,26 +26,20 @@
     .edit, .cancel { float: right; }
   </ui:style>
   <g:HTMLPanel>
-    <g:FlowPanel ui:field='show'
-        styleName='{style.show}'
-        title='Click to edit topic (Shortcut: t)'>
-      <ui:attribute name='title'/>
-      <g:InlineLabel ui:field='text'/>
+    <div ui:field='show' styleName='{style.show}'>
+      <x:InlineHyperlink ui:field='text'
+          title='Search for changes on this topic'/>
       <g:Image ui:field='editIcon'
           resource='{ico.edit}'
-          styleName='{style.edit}'/>
-    </g:FlowPanel>
+          styleName='{style.edit}'
+          title='Click to edit topic (Shortcut: t)'/>
+    </div>
 
     <div ui:field='form' style='display: none' aria-hidden='true'>
       <div>
         <c:NpTextBox ui:field='input' visibleLength='55'/>
       </div>
       <div>
-        <c:NpTextArea ui:field='message'
-            visibleLines='3'
-            characterWidth='45'/>
-      </div>
-      <div>
         <g:Button ui:field='save' styleName='{res.style.button}'>
           <div>Update</div>
         </g:Button>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateAvailableBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateAvailableBar.java
index 1dece8b..2b6f418 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateAvailableBar.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/UpdateAvailableBar.java
@@ -45,7 +45,7 @@
   }
 
   void set(List<MessageInfo> newMessages, Timestamp newTime) {
-    HashSet<Integer> seen = new HashSet<Integer>();
+    HashSet<Integer> seen = new HashSet<>();
     StringBuilder r = new StringBuilder();
     for (MessageInfo m : newMessages) {
       int a = m.author() != null ? m.author()._account_id() : 0;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/VotableInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/VotableInfo.java
new file mode 100644
index 0000000..056c9a2
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/VotableInfo.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.change;
+
+import java.util.HashSet;
+import java.util.Set;
+
+class VotableInfo {
+  private Set<String> votable;
+
+  void votable(String label) {
+    if (votable == null) {
+      votable = new HashSet<>();
+    }
+    votable.add(label);
+  }
+
+  Set<String> votableLabels() {
+    Set<String> s = new HashSet<>();
+    if (votable != null) {
+      s.addAll(votable);
+    }
+    return s;
+  }
+}
+
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
index a9d5a38..4b695d2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/file_table.css
@@ -19,6 +19,16 @@
   vertical-align: top;
 }
 
+.table tr {
+  vertical-align: top;
+}
+.table tr:hover {
+  background: rgba(209, 245, 248, 0.32);
+}
+.table tr.nohover:hover {
+  background: transparent;
+}
+
 .status {
   padding-right: 4px;
   color: #888;
@@ -34,6 +44,10 @@
 .commonPrefix {
   color: #888;
 }
+.renameCopySource {
+  color: #888;
+  font-size: smaller;
+}
 
 .draftColumn,
 .newColumn,
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/more_less.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/more_less.png
new file mode 100644
index 0000000..298514f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/more_less.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/related_changes.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/related_changes.css
new file mode 100644
index 0000000..2e62b98
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/related_changes.css
@@ -0,0 +1,89 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@external .gwt-TabBarItem;
+@external .gwt-TabBarItem-disabled;
+@external .gwt-TabBarItem-selected;
+@external .gwt-TabBarRest;
+@external .gwt-TabPanelBottom;
+@eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
+
+.row {
+  white-space: nowrap;
+}
+
+.activeRow {
+  background-color: selectionColor !important;
+}
+
+.activeRow .pointer {
+  visibility: visible;
+}
+
+.pointer {
+  display: inline-block;
+  vertical-align: top;
+  visibility: hidden;
+}
+
+.subject {
+  display: inline-block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  width: 355px;
+}
+
+.tabPanel .gwt-TabBarItem,
+.tabPanel .gwt-TabBarItem-selected,
+.tabPanel .gwt-TabBarRest {
+  background: none repeat scroll 0 0 #FFF !important;
+}
+
+.tabPanel .gwt-TabPanelBottom {
+  padding: 2px 0 0 0;
+}
+
+.tabPanel .gwt-TabBarItem-selected {
+  border-bottom-color: #000 !important;
+  color: #000 !important;
+}
+
+.tabPanel .gwt-TabBarItem-disabled {
+  display: none;
+}
+
+.current,
+.gitweb,
+.indirect,
+.notCurrent {
+  display: inline-block;
+  text-align: center;
+  vertical-align: top;
+  width: 12px;
+}
+
+.gitweb {
+  color: #000;
+}
+
+.indirect {
+  color: #090;      /* green */
+  font-weight: bold;
+}
+
+.notCurrent {
+  color: #FFA62F;   /* orange */
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_black.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_black.png
deleted file mode 100644
index 13f3a5d..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_black.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_white.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_white.png
deleted file mode 100644
index 8f5a311..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/reload_white.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/remove_reviewer.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/remove_reviewer.png
index 9e494dd..5a3e6f0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/remove_reviewer.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/remove_reviewer.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png
index 39bddb1..db1e24e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/star_filled.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
index 5f89bf0..f312d11 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
@@ -19,7 +19,7 @@
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.Screen;
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.event.dom.client.KeyPressEvent;
@@ -90,7 +90,7 @@
           : EnumSet.noneOf(ListChangesOption.class),
         "is:open owner:" + who,
         "is:open reviewer:" + who + " -owner:" + who,
-        "is:closed owner:" + who + " -age:4w limit:10");
+        "is:closed (owner:" + who + " OR reviewer:" + who + ") -age:4w limit:10");
   }
 
   @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
index 404de96..fbbff76 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ApprovalTable.java
@@ -21,11 +21,11 @@
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.change.Reviewers.PostInput;
+import com.google.gerrit.client.change.Reviewers.PostResult;
 import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
 import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.rpc.NativeMap;
-import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.AccountLinkPanel;
 import com.google.gerrit.client.ui.AddMemberBox;
@@ -35,11 +35,10 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
@@ -71,13 +70,13 @@
   private Map<Integer, Integer> rows;
 
   public ApprovalTable() {
-    rows = new HashMap<Integer, Integer>();
+    rows = new HashMap<>();
     table = new Grid(1, 3);
     table.addStyleName(Gerrit.RESOURCES.css().infoTable());
 
     missing = new Widget() {
       {
-        setElement(DOM.createElement("ul"));
+        setElement((Element)(DOM.createElement("ul")));
       }
     };
     missing.setStyleName(Gerrit.RESOURCES.css().missingApprovalList());
@@ -138,10 +137,8 @@
   void display(ChangeInfo change) {
     lastChange = change;
     reviewerSuggestOracle.setChange(change.legacy_id());
-    Map<Integer, ApprovalDetail> byUser =
-        new LinkedHashMap<Integer, ApprovalDetail>();
-    Map<Integer, AccountInfo> accounts =
-        new LinkedHashMap<Integer, AccountInfo>();
+    Map<Integer, ApprovalDetail> byUser = new LinkedHashMap<>();
+    Map<Integer, AccountInfo> accounts = new LinkedHashMap<>();
     List<String> missingLabels = initLabels(change, accounts, byUser);
 
     removeAllChildren(missing.getElement());
@@ -152,7 +149,7 @@
     if (byUser.isEmpty()) {
       table.setVisible(false);
     } else {
-      List<String> labels = new ArrayList<String>(change.labels());
+      List<String> labels = new ArrayList<>(change.labels());
       Collections.sort(labels);
       displayHeader(labels);
       table.resizeRows(1 + byUser.size());
@@ -164,8 +161,7 @@
       table.setVisible(true);
     }
 
-    if (Gerrit.getConfig().testChangeMerge()
-        && change.status() != Change.Status.MERGED
+    if (change.status() != Change.Status.MERGED
         && !change.mergeable()) {
       addMissingLabel(Util.C.messageNeedsRebaseOrHasDependency());
     }
@@ -188,7 +184,7 @@
 
   private Set<Integer> removableReviewers(ChangeInfo change) {
     Set<Integer> result =
-        new HashSet<Integer>(change.removable_reviewers().length());
+        new HashSet<>(change.removable_reviewers().length());
     for (int i = 0; i < change.removable_reviewers().length(); i++) {
       result.add(change.removable_reviewers().get(i)._account_id());
     }
@@ -199,7 +195,7 @@
       Map<Integer, AccountInfo> accounts,
       Map<Integer, ApprovalDetail> byUser) {
     Set<Integer> removableReviewers = removableReviewers(change);
-    List<String> missing = new ArrayList<String>();
+    List<String> missing = new ArrayList<>();
     for (String name : change.labels()) {
       LabelInfo label = change.label(name);
 
@@ -254,44 +250,6 @@
     }
   }
 
-  public static class PostInput extends JavaScriptObject {
-    public static PostInput create(String reviewer, boolean confirmed) {
-      PostInput input = createObject().cast();
-      input.init(reviewer, confirmed);
-      return input;
-    }
-
-    private native void init(String reviewer, boolean confirmed) /*-{
-      this.reviewer = reviewer;
-      if (confirmed) {
-        this.confirmed = true;
-      }
-    }-*/;
-
-    protected PostInput() {
-    }
-  }
-
-  public static class ReviewerInfo extends AccountInfo {
-    final Set<String> approvals() {
-      return Natives.keys(_approvals());
-    }
-    final native String approval(String l) /*-{ return this.approvals[l]; }-*/;
-    private final native NativeMap<NativeString> _approvals() /*-{ return this.approvals; }-*/;
-
-    protected ReviewerInfo() {
-    }
-  }
-
-  public static class PostResult extends JavaScriptObject {
-    public final native JsArray<ReviewerInfo> reviewers() /*-{ return this.reviewers; }-*/;
-    public final native boolean confirm() /*-{ return this.confirm || false; }-*/;
-    public final native String error() /*-{ return this.error; }-*/;
-
-    protected PostResult() {
-    }
-  }
-
   private void addReviewer(final String reviewer, boolean confirmed) {
     ChangeApi.reviewers(lastChange.legacy_id().get()).post(
         PostInput.create(reviewer, confirmed),
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index 97b0166..406ffd9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -48,14 +48,12 @@
   }
 
   /** Update the topic of a change. */
-  public static void topic(int id, String topic, String msg, AsyncCallback<String> cb) {
+  public static void topic(int id, String topic, AsyncCallback<String> cb) {
     RestApi call = call(id, "topic");
     topic = emptyToNull(topic);
-    msg = emptyToNull(msg);
-    if (topic != null || msg != null) {
+    if (topic != null) {
       Input input = Input.create();
       input.topic(topic);
-      input.message(msg);
       call.put(input, NativeString.unwrap(cb));
     } else {
       call.delete(NativeString.unwrap(cb));
@@ -171,7 +169,7 @@
 
     protected CherryPickInput() {
     }
-  };
+  }
 
   private static class SubmitInput extends JavaScriptObject {
     final native void wait_for_merge(boolean b) /*-{ this.wait_for_merge=b; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
index 5a9da1a..7fd5290 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
@@ -23,8 +23,7 @@
 
 /** A Cache to store common client side data by change */
 public class ChangeCache {
-  private static Map<Change.Id, ChangeCache> caches =
-    new HashMap<Change.Id, ChangeCache>();
+  private static Map<Change.Id, ChangeCache> caches = new HashMap<>();
 
   public static ChangeCache get(Change.Id chg) {
     ChangeCache cache = caches.get(chg);
@@ -56,7 +55,7 @@
 
   public ListenableValue<ChangeInfo> getChangeInfoCache() {
     if (info == null) {
-      info = new ListenableValue<ChangeInfo>();
+      info = new ListenableValue<>();
     }
     return info;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index 5164251..b4f3e17 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -40,6 +40,7 @@
   String allMergedChanges();
 
   String changeTableColumnSubject();
+  String changeTableColumnSize();
   String changeTableColumnStatus();
   String changeTableColumnOwner();
   String changeTableColumnReviewers();
@@ -47,6 +48,7 @@
   String changeTableColumnBranch();
   String changeTableColumnLastUpdate();
   String changeTableNone();
+  String changeTableNotMergeable();
 
   String changeItemHelp();
   String changeTableStar();
@@ -57,6 +59,8 @@
   String previousPatchSet();
   String nextPatchSet();
   String keyReloadChange();
+  String keyNextPatchSet();
+  String keyPreviousPatchSet();
   String keyReloadSearch();
   String keyPublishComments();
   String keyEditTopic();
@@ -168,7 +172,6 @@
 
   String buttonReview();
   String buttonPublishCommentsSend();
-  String buttonPublishSubmitSend();
   String buttonPublishCommentsCancel();
   String headingCoverMessage();
   String headingPatchComments();
@@ -208,4 +211,6 @@
   String oneWeekAgo();
   String oneMonthAgo();
   String oneYearAgo();
+
+  String votable();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index c7f72d7..fb713b6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -20,6 +20,7 @@
 allMergedChanges = All merged changes
 
 changeTableColumnSubject = Subject
+changeTableColumnSize = Size
 changeTableColumnStatus = Status
 changeTableColumnOwner = Owner
 changeTableColumnReviewers = Reviewers
@@ -27,6 +28,7 @@
 changeTableColumnBranch = Branch
 changeTableColumnLastUpdate = Updated
 changeTableNone = (None)
+changeTableNotMergeable = Merge Conflict
 
 changeItemHelp = change
 changeTableStar = Star (or unstar) change
@@ -37,6 +39,8 @@
 previousPatchSet = Previous patch set
 nextPatchSet = Next patch set
 keyReloadChange = Reload change
+keyNextPatchSet = Next patch set
+keyPreviousPatchSet = Previous patch set
 keyReloadSearch = Reload change list
 keyPublishComments = Review and publish comments
 keyEditTopic = Edit change topic
@@ -151,7 +155,6 @@
 
 buttonReview = Review
 buttonPublishCommentsSend = Publish Comments
-buttonPublishSubmitSend = Publish and Submit
 buttonPublishCommentsCancel = Cancel
 headingCoverMessage = Cover Message:
 headingPatchComments = Patch Comments:
@@ -190,3 +193,5 @@
 oneWeekAgo = 1 week ago
 oneMonthAgo = 1 month ago
 oneYearAgo = 1 year ago
+
+votable = Votable:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
index 6b13ba0a..8f2642c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDescriptionBlock.java
@@ -42,7 +42,7 @@
       SubmitTypeRecord submitTypeRecord,
       CommentLinkProcessor commentLinkProcessor) {
     infoBlock.display(changeDetail, acc, submitTypeRecord);
-    messageBlock.display(changeDetail.getChange().currentPatchSetId(), starred,
+    messageBlock.display(changeDetail.getChange().currentPatchSetId(), info.getRevId(), starred,
         canEditCommitMessage, info.getMessage(), commentLinkProcessor);
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
index 9c19d50..b23403b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
@@ -14,47 +14,56 @@
 
 package com.google.gerrit.client.changes;
 
+import com.google.gerrit.client.actions.ActionInfo;
+import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.changes.ChangeInfo.GitPerson;
+import com.google.gerrit.client.changes.ChangeInfo.MessageInfo;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.ui.ListenableValue;
+import com.google.gerrit.common.data.AccountInfo;
+import com.google.gerrit.common.data.AccountInfoCache;
 import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.data.PatchSetDetail;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.common.data.UiCommandDetail;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
-
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.client.PatchSetInfo.ParentInfo;
+import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.client.UserIdentity;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwt.user.client.ui.FocusWidget;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 
 public class ChangeDetailCache extends ListenableValue<ChangeDetail> {
-  public static class GerritCallback extends
-      com.google.gerrit.client.rpc.GerritCallback<ChangeDetail> {
+  public static class NewGerritCallback extends
+      com.google.gerrit.client.rpc.GerritCallback<ChangeInfo> {
     @Override
-    public void onSuccess(ChangeDetail detail) {
-      setChangeDetail(detail);
-    }
-  }
-
-  /*
-   * GerritCallback which will re-enable a FocusWidget
-   * {@link com.google.gwt.user.client.ui.FocusWidget} if we are returning
-   * with a failed result.
-   *
-   * It is up to the caller to handle the original disabling of the Widget.
-   */
-  public static class GerritWidgetCallback extends GerritCallback {
-    private FocusWidget widget;
-
-    public GerritWidgetCallback(FocusWidget widget) {
-      this.widget = widget;
-    }
-
-    @Override
-    public void onFailure(Throwable caught) {
-      widget.setEnabled(true);
-      super.onFailure(caught);
+    public void onSuccess(ChangeInfo detail) {
+      setChangeDetail(reverse(detail));
     }
   }
 
   public static class IgnoreErrorCallback implements AsyncCallback<ChangeDetail> {
     @Override
-    public void onSuccess(ChangeDetail detail) {
-      setChangeDetail(detail);
+    public void onSuccess(ChangeDetail info) {
+      setChangeDetail(info);
     }
 
     @Override
@@ -62,6 +71,173 @@
     }
   }
 
+  public static ChangeDetail reverse(ChangeInfo info) {
+    info.revisions().copyKeysIntoChildren("name");
+    RevisionInfo rev = current(info);
+
+    ChangeDetail r = new ChangeDetail();
+    r.setAllowsAnonymous(rev.has_fetch() && rev.fetch().containsKey("http"));
+    r.setCanAbandon(can(info.actions(), "abandon"));
+    r.setCanEditCommitMessage(can(rev.actions(), "message"));
+    r.setCanCherryPick(can(rev.actions(), "cherrypick"));
+    r.setCanPublish(can(rev.actions(), "publish"));
+    r.setCanRebase(can(rev.actions(), "rebase"));
+    r.setCanRestore(can(info.actions(), "restore"));
+    r.setCanRevert(can(info.actions(), "revert"));
+    r.setCanDeleteDraft(can(rev.actions(), "/"));
+    r.setCanEditTopicName(can(info.actions(), "topic"));
+    r.setCanSubmit(can(rev.actions(), "submit"));
+    r.setCanEdit(true);
+    r.setChange(toChange(info));
+    r.setStarred(info.starred());
+    r.setPatchSets(toPatchSets(info));
+    r.setMessages(toMessages(info));
+    r.setAccounts(users(info));
+    r.setCurrentPatchSetId(new PatchSet.Id(info.legacy_id(), rev._number()));
+    r.setCurrentPatchSetDetail(toPatchSetDetail(info));
+    r.setSubmitRecords(new ArrayList<SubmitRecord>());
+
+    // Obtained later in ChangeScreen.
+    r.setSubmitTypeRecord(new SubmitTypeRecord());
+    r.getSubmitTypeRecord().status = SubmitTypeRecord.Status.RULE_ERROR;
+    r.setPatchSetsWithDraftComments(new HashSet<PatchSet.Id>());
+    r.setDependsOn(new ArrayList<com.google.gerrit.common.data.ChangeInfo>());
+    r.setNeededBy(new ArrayList<com.google.gerrit.common.data.ChangeInfo>());
+    return r;
+  }
+
+  private static PatchSetDetail toPatchSetDetail(ChangeInfo info) {
+    RevisionInfo rev = current(info);
+    PatchSetDetail p = new PatchSetDetail();
+    p.setPatchSet(toPatchSet(info, rev));
+    p.setProject(info.project_name_key());
+    p.setInfo(new PatchSetInfo(p.getPatchSet().getId()));
+    p.getInfo().setRevId(rev.name());
+    p.getInfo().setParents(new ArrayList<ParentInfo>());
+    p.getInfo().setAuthor(toUser(rev.commit().author()));
+    p.getInfo().setCommitter(toUser(rev.commit().committer()));
+    p.getInfo().setSubject(rev.commit().subject());
+    p.getInfo().setMessage(rev.commit().message());
+    if (rev.commit().parents() != null) {
+      for (CommitInfo c : Natives.asList(rev.commit().parents())) {
+        p.getInfo().getParents().add(new ParentInfo(
+            new RevId(c.commit()),
+            c.subject()));
+      }
+    }
+    p.setPatches(new ArrayList<Patch>());
+    p.setCommands(new ArrayList<UiCommandDetail>());
+
+    rev.files();
+    return p;
+  }
+
+  private static UserIdentity toUser(GitPerson p) {
+    UserIdentity u = new UserIdentity();
+    u.setName(p.name());
+    u.setEmail(p.email());
+    u.setDate(p.date());
+    return u;
+  }
+
+  public static AccountInfoCache users(ChangeInfo info) {
+    Map<Integer, AccountInfo> r = new HashMap<>();
+    add(r, info.owner());
+    if (info.messages() != null) {
+      for (MessageInfo m : Natives.asList(info.messages())) {
+        add(r, m.author());
+      }
+    }
+    return new AccountInfoCache(r.values());
+  }
+
+  private static void add(Map<Integer, AccountInfo> r,
+      com.google.gerrit.client.account.AccountInfo user) {
+    if (user != null && !r.containsKey(user._account_id())) {
+      AccountInfo a = new AccountInfo(new Account.Id(user._account_id()));
+      a.setPreferredEmail(user.email());
+      a.setFullName(user.name());
+      r.put(user._account_id(), a);
+    }
+  }
+
+  private static boolean can(NativeMap<ActionInfo> m, String n) {
+    return m != null && m.containsKey(n) && m.get(n).enabled();
+  }
+
+  private static List<ChangeMessage> toMessages(ChangeInfo info) {
+    List<ChangeMessage> msgs = new ArrayList<>();
+    for (MessageInfo m : Natives.asList(info.messages())) {
+      ChangeMessage o = new ChangeMessage(
+          new ChangeMessage.Key(
+              info.legacy_id(),
+              m.date().toString()),
+          m.author() != null
+            ? new Account.Id(m.author()._account_id())
+            : null,
+          m.date(),
+          m._revisionNumber() > 0
+            ? new PatchSet.Id(info.legacy_id(), m._revisionNumber())
+            : null);
+      o.setMessage(m.message());
+      msgs.add(o);
+    }
+    return msgs;
+  }
+
+  private static List<PatchSet> toPatchSets(ChangeInfo info) {
+    JsArray<RevisionInfo> all = info.revisions().values();
+    RevisionInfo.sortRevisionInfoByNumber(all);
+
+    List<PatchSet> r = new ArrayList<>(all.length());
+    for (RevisionInfo rev : Natives.asList(all)) {
+      r.add(toPatchSet(info, rev));
+    }
+    return r;
+  }
+
+  private static PatchSet toPatchSet(ChangeInfo info, RevisionInfo rev) {
+    PatchSet p = new PatchSet(
+        new PatchSet.Id(info.legacy_id(), rev._number()));
+    p.setCreatedOn(rev.commit().committer().date());
+    p.setDraft(rev.draft());
+    p.setRevision(new RevId(rev.name()));
+    return p;
+  }
+
+  public static Change toChange(ChangeInfo info) {
+    RevisionInfo rev = current(info);
+    PatchSetInfo p = new PatchSetInfo(
+      new PatchSet.Id(
+          info.legacy_id(),
+          rev._number()));
+    p.setSubject(info.subject());
+    Change c = new Change(
+        new Change.Key(info.change_id()),
+        info.legacy_id(),
+        new Account.Id(info.owner()._account_id()),
+        new Branch.NameKey(
+            info.project_name_key(),
+            info.branch()),
+        info.created());
+    c.setTopic(info.topic());
+    c.setStatus(info.status());
+    c.setCurrentPatchSet(p);
+    c.setLastUpdatedOn(info.updated());
+    c.setMergeable(info.mergeable());
+    return c;
+  }
+
+  private static RevisionInfo current(ChangeInfo info) {
+    RevisionInfo rev = info.revision(info.current_revision());
+    if (rev == null) {
+      JsArray<RevisionInfo> all = info.revisions().values();
+      RevisionInfo.sortRevisionInfoByNumber(all);
+      rev = all.get(all.length() - 1);
+    }
+    return rev;
+  }
+
   public static void setChangeDetail(ChangeDetail detail) {
     Change.Id chgId = detail.getChange().getId();
     ChangeCache.get(chgId).getChangeDetailCache().set(detail);
@@ -75,6 +251,11 @@
   }
 
   public void refresh() {
-    Util.DETAIL_SVC.changeDetail(changeId, new GerritCallback());
+    RestApi call = ChangeApi.detail(changeId.get());
+    ChangeList.addOptions(call, EnumSet.of(
+      ListChangesOption.CURRENT_ACTIONS,
+      ListChangesOption.ALL_REVISIONS,
+      ListChangesOption.ALL_COMMITS));
+    call.get(new NewGerritCallback());
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 1066c93..bfe70b8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -85,6 +85,8 @@
   public final native String topic() /*-{ return this.topic; }-*/;
   public final native String change_id() /*-{ return this.change_id; }-*/;
   public final native boolean mergeable() /*-{ return this.mergeable; }-*/;
+  public final native int insertions() /*-{ return this.insertions; }-*/;
+  public final native int deletions() /*-{ return this.deletions; }-*/;
   private final native String statusRaw() /*-{ return this.status; }-*/;
   public final native String subject() /*-{ return this.subject; }-*/;
   public final native AccountInfo owner() /*-{ return this.owner; }-*/;
@@ -172,7 +174,7 @@
     }
 
     public final SortedSet<Short> value_set() {
-      SortedSet<Short> values = new TreeSet<Short>();
+      SortedSet<Short> values = new TreeSet<>();
       for (String v : values()) {
         values.add(parseValue(v));
       }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
index b4ae2f3..8fe7d56 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfoBlock.java
@@ -60,11 +60,7 @@
   private final Grid table;
 
   public ChangeInfoBlock() {
-    if (Gerrit.getConfig().testChangeMerge()) {
-      table = new Grid(R_CNT, 2);
-    } else {
-      table = new Grid(R_CNT - 1, 2);
-    }
+    table = new Grid(R_CNT, 2);
     table.setStyleName(Gerrit.RESOURCES.css().infoBlock());
     table.addStyleName(Gerrit.RESOURCES.css().changeInfoBlock());
 
@@ -77,9 +73,7 @@
     initRow(R_UPDATED, Util.C.changeInfoBlockUpdated());
     initRow(R_STATUS, Util.C.changeInfoBlockStatus());
     initRow(R_SUBMIT_TYPE, Util.C.changeInfoBlockSubmitType());
-    if (Gerrit.getConfig().testChangeMerge()) {
-      initRow(R_MERGE_TEST, Util.C.changeInfoBlockCanMerge());
-    }
+    initRow(R_MERGE_TEST, Util.C.changeInfoBlockCanMerge());
 
     final CellFormatter fmt = table.getCellFormatter();
     fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
@@ -128,14 +122,13 @@
     }
     table.setText(R_SUBMIT_TYPE, 1, submitType);
     final Change.Status status = chg.getStatus();
-    if (Gerrit.getConfig().testChangeMerge()) {
-      if (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) {
-        table.getRowFormatter().setVisible(R_MERGE_TEST, true);
-        table.setText(R_MERGE_TEST, 1, chg.isMergeable() ? Util.C
-            .changeInfoBlockCanMergeYes() : Util.C.changeInfoBlockCanMergeNo());
-      } else {
-        table.getRowFormatter().setVisible(R_MERGE_TEST, false);
-      }
+    if (Gerrit.getConfig().getNewFeatures()
+        && (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT))) {
+      table.getRowFormatter().setVisible(R_MERGE_TEST, true);
+      table.setText(R_MERGE_TEST, 1, chg.isMergeable() ? Util.C
+          .changeInfoBlockCanMergeYes() : Util.C.changeInfoBlockCanMergeNo());
+    } else {
+      table.getRowFormatter().setVisible(R_MERGE_TEST, false);
     }
 
     if (status.isClosed()) {
@@ -181,6 +174,7 @@
       super(Util.C.alterTopicTitle(), Util.C.headingAlterTopicMessage(),
           new ChangeDetailCache.IgnoreErrorCallback());
       change = chg;
+      message.setVisible(false);
 
       newTopic = new TextBox();
       newTopic.addKeyPressHandler(this);
@@ -197,7 +191,7 @@
 
     private void doTopicEdit() {
       String topic = newTopic.getText();
-      ChangeApi.topic(change.getId().get(), topic, getMessageText(),
+      ChangeApi.topic(change.getId().get(), topic,
         new GerritCallback<String>() {
         @Override
         public void onSuccess(String result) {
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 773313a..97ba3b1 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
@@ -15,7 +15,7 @@
 package com.google.gerrit.client.changes;
 
 import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwtorm.client.KeyUtil;
@@ -43,30 +43,24 @@
     call.get(callback);
   }
 
-  public static void prev(String query,
-      int limit, String sortkey,
+  public static void query(String query,
+      EnumSet<ListChangesOption> options,
       AsyncCallback<ChangeList> callback) {
     RestApi call = newQuery(query);
-    if (limit > 0) {
-      call.addParameter("n", limit);
-    }
-    addOptions(call, EnumSet.of(ListChangesOption.LABELS));
-    if (!PagedSingleListScreen.MIN_SORTKEY.equals(sortkey)) {
-      call.addParameter("P", sortkey);
-    }
+    addOptions(call, options);
     call.get(callback);
   }
 
   public static void next(String query,
-      int limit, String sortkey,
+      int start, int limit,
       AsyncCallback<ChangeList> callback) {
     RestApi call = newQuery(query);
     if (limit > 0) {
       call.addParameter("n", limit);
     }
     addOptions(call, EnumSet.of(ListChangesOption.LABELS));
-    if (!PagedSingleListScreen.MAX_SORTKEY.equals(sortkey)) {
-      call.addParameter("N", sortkey);
+    if (start != 0) {
+      call.addParameter("S", start);
     }
     call.get(callback);
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
index 4d1ec7e..d435720 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.java
@@ -69,4 +69,6 @@
   String years0MonthsAgo(long years, String yearLabel);
   String yearsMonthsAgo(long years, String yearLabel, long months,
       String monthLabel);
+
+  String insertionsAndDeletions(int insertions, int deletions);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
index f7dee85..fa942fd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeMessages.properties
@@ -51,3 +51,5 @@
 years0MonthsAgo = {0} {1} ago
 yearsMonthsAgo = {0} {1}, {2} {3} ago
 yearsAgo = {0} years ago
+
+insertionsAndDeletions = +{0}, -{1}
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 f2b74d6..1d760f9 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
@@ -18,9 +18,18 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.change.RelatedChanges;
+import com.google.gerrit.client.change.RelatedChanges.ChangeAndCommit;
+import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
+import com.google.gerrit.client.diff.DiffApi;
+import com.google.gerrit.client.diff.FileInfo;
 import com.google.gerrit.client.projects.ConfigInfoCache;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.CommentPanel;
 import com.google.gerrit.client.ui.ComplexDisclosurePanel;
@@ -31,11 +40,19 @@
 import com.google.gerrit.common.data.AccountInfoCache;
 import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.ChangeInfo;
+import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.Patch.ChangeType;
+import com.google.gerrit.reviewdb.client.Patch.PatchType;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.event.dom.client.ChangeEvent;
 import com.google.gwt.event.dom.client.ChangeHandler;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -45,6 +62,7 @@
 import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.DisclosurePanel;
 import com.google.gwt.user.client.ui.FlowPanel;
@@ -59,7 +77,13 @@
 import com.google.gwtexpui.globalkey.client.KeyCommandSet;
 
 import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 
 public class ChangeScreen extends Screen
@@ -273,10 +297,211 @@
       // happen sequentially after the ChangeDetail lookup, because we can't
       // start an async get at the source of every call that might trigger a
       // value change.
-      CallbackGroup cbs = new CallbackGroup();
+      CallbackGroup cbs1 = new CallbackGroup();
+      final CallbackGroup cbs2 = new CallbackGroup();
+      final PatchSet.Id psId = event.getValue().getCurrentPatchSet().getId();
+      final Map<String, Patch> patches = new HashMap<String, Patch>();
+      String revId =
+          event.getValue().getCurrentPatchSetDetail().getInfo().getRevId();
+
+      if (event.getValue().getChange().getStatus().isOpen()) {
+        ChangeApi.revision(changeId.get(), "current")
+          .view("submit_type")
+          .get(cbs1.add(new GerritCallback<NativeString>() {
+            @Override
+            public void onSuccess(NativeString result) {
+              event.getValue().setSubmitTypeRecord(SubmitTypeRecord.OK(
+                  Project.SubmitType.valueOf(result.asString())));
+            }
+            public void onFailure(Throwable caught) {}
+          }));
+      }
+      if (Gerrit.isSignedIn()) {
+        ChangeApi.revision(changeId.get(), "" + psId.get())
+          .view("related")
+          .get(cbs1.add(new AsyncCallback<RelatedChanges.RelatedInfo>() {
+              @Override
+              public void onSuccess(RelatedChanges.RelatedInfo info) {
+                if (info.changes() != null) {
+                  dependsOn(info);
+                  neededBy(info);
+                }
+              }
+
+              private void dependsOn(RelatedChanges.RelatedInfo info) {
+                ChangeAndCommit self = null;
+                Map<String, ChangeAndCommit> m = new HashMap<String, ChangeAndCommit>();
+                for (int i = 0; i < info.changes().length(); i++) {
+                  ChangeAndCommit c = info.changes().get(i);
+                  if (changeId.equals(c.legacy_id())) {
+                    self = c;
+                  }
+                  if (c.commit() != null && c.commit().commit() != null) {
+                    m.put(c.commit().commit(), c);
+                  }
+                }
+                if (self != null && self.commit() != null
+                    && self.commit().parents() != null) {
+                  List<ChangeInfo> d = new ArrayList<ChangeInfo>();
+                  for (CommitInfo p : Natives.asList(self.commit().parents())) {
+                    ChangeAndCommit pc = m.get(p.commit());
+                    if (pc != null && pc.has_change_number()) {
+                      ChangeInfo i = new ChangeInfo();
+                      load(pc, i);
+                      d.add(i);
+                    }
+                  }
+                  event.getValue().setDependsOn(d);
+                }
+              }
+
+              private void neededBy(RelatedChanges.RelatedInfo info) {
+                Set<String> mine = new HashSet<String>();
+                for (PatchSet ps : event.getValue().getPatchSets()) {
+                  mine.add(ps.getRevision().get());
+                }
+
+                List<ChangeInfo> n = new ArrayList<ChangeInfo>();
+                for (int i = 0; i < info.changes().length(); i++) {
+                  ChangeAndCommit c = info.changes().get(i);
+                  if (c.has_change_number()
+                      && c.commit() != null
+                      && c.commit().parents() != null) {
+                    for (int j = 0; j < c.commit().parents().length(); j++) {
+                      CommitInfo p = c.commit().parents().get(j);
+                      if (mine.contains(p.commit())) {
+                        ChangeInfo u = new ChangeInfo();
+                        load(c, u);
+                        n.add(u);
+                        break;
+                      }
+                    }
+                  }
+                }
+                event.getValue().setNeededBy(n);
+              }
+
+              private void load(final ChangeAndCommit pc, final ChangeInfo i) {
+                RestApi call = ChangeApi.change(pc.legacy_id().get());
+                ChangeList.addOptions(call, EnumSet.of(
+                  ListChangesOption.DETAILED_ACCOUNTS,
+                  ListChangesOption.CURRENT_REVISION));
+                call.get(cbs2.add(new AsyncCallback<
+                    com.google.gerrit.client.changes.ChangeInfo>() {
+                  public void onFailure(Throwable caught) {}
+                  public void onSuccess(
+                      com.google.gerrit.client.changes.ChangeInfo result) {
+                    i.set(ChangeDetailCache.toChange(result),
+                        pc.patch_set_id());
+                    i.setStarred(result.starred());
+                    event.getValue().getAccounts()
+                        .merge(ChangeDetailCache.users(result));
+                  }}));
+              }
+              public void onFailure(Throwable caught) {}
+            }));
+        ChangeApi.revision(changeId.get(), revId)
+          .view("files")
+          .addParameterTrue("reviewed")
+          .get(cbs1.add(new AsyncCallback<JsArrayString>() {
+              @Override
+              public void onSuccess(JsArrayString result) {
+                for(int i = 0; i < result.length(); i++) {
+                  String path = result.get(i);
+                  Patch p = patches.get(path);
+                  if (p == null) {
+                    p = new Patch(new Patch.Key(psId, path));
+                    patches.put(path, p);
+                  }
+                  p.setReviewedByCurrentUser(true);
+                }
+              }
+              public void onFailure(Throwable caught) {}
+            }));
+        final Set<PatchSet.Id> withDrafts = new HashSet<PatchSet.Id>();
+        event.getValue().setPatchSetsWithDraftComments(withDrafts);
+        for (PatchSet ps : event.getValue().getPatchSets()) {
+          if (!ps.getId().equals(psId)) {
+            final PatchSet.Id id = ps.getId();
+            ChangeApi.revision(changeId.get(), "" + id.get())
+              .view("drafts")
+              .get(cbs1.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
+                @Override
+                public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+                  if (!result.isEmpty()) {
+                    withDrafts.add(id);
+                  }
+                }
+                public void onFailure(Throwable caught) {}
+              }));
+          }
+        }
+        ChangeApi.revision(changeId.get(), "" + psId.get())
+          .view("drafts")
+          .get(cbs1.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
+            @Override
+            public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+              for (String path : result.keySet()) {
+                Patch p = patches.get(path);
+                if (p == null) {
+                  p = new Patch(new Patch.Key(psId, path));
+                  patches.put(path, p);
+                }
+                p.setDraftCount(result.get(path).length());
+              }
+              if (!result.isEmpty()) {
+                withDrafts.add(psId);
+              }
+            }
+            public void onFailure(Throwable caught) {}
+          }));
+      }
+      ChangeApi.revision(changeId.get(), revId)
+        .view("comments")
+        .get(cbs1.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
+          @Override
+          public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+            for (String path : result.keySet()) {
+              Patch p = patches.get(path);
+              if (p == null) {
+                p = new Patch(new Patch.Key(psId, path));
+                patches.put(path, p);
+              }
+              p.setCommentCount(result.get(path).length());
+            }
+          }
+          public void onFailure(Throwable caught) {}
+        }));
+      DiffApi.list(changeId.get(), null, revId,
+          new AsyncCallback<NativeMap<FileInfo>>() {
+            @Override
+            public void onSuccess(NativeMap<FileInfo> result) {
+              JsArray<FileInfo> fileInfos = result.values();
+              FileInfo.sortFileInfoByPath(fileInfos);
+              List<Patch> list = new ArrayList<Patch>(fileInfos.length());
+              for (FileInfo f : Natives.asList(fileInfos)) {
+                Patch p = patches.get(f.path());
+                if (p == null) {
+                  p = new Patch(new Patch.Key(psId, f.path()));
+                  patches.put(f.path(), p);
+                }
+                p.setInsertions(f.lines_inserted());
+                p.setDeletions(f.lines_deleted());
+                p.setPatchType(f.binary() ? PatchType.BINARY : PatchType.UNIFIED);
+                if (f.status() == null) {
+                  p.setChangeType(ChangeType.MODIFIED);
+                } else {
+                  p.setChangeType(ChangeType.forCode(f.status().charAt(0)));
+                }
+                list.add(p);
+              }
+              event.getValue().getCurrentPatchSetDetail().setPatches(list);
+            }
+            public void onFailure(Throwable caught) {}
+      });
       ConfigInfoCache.get(
           event.getValue().getChange().getProject(),
-          cbs.add(new GerritCallback<ConfigInfoCache.Entry>() {
+          cbs1.add(new GerritCallback<ConfigInfoCache.Entry>() {
             @Override
             public void onSuccess(ConfigInfoCache.Entry result) {
               commentLinkProcessor = result.getCommentLinkProcessor();
@@ -288,13 +513,19 @@
               // Handled by last callback's onFailure.
             }
           }));
-      ChangeApi.detail(event.getValue().getChange().getId().get(), cbs.addFinal(
+      ChangeApi.detail(changeId.get(), cbs1.addFinal(
           new GerritCallback<com.google.gerrit.client.changes.ChangeInfo>() {
             @Override
             public void onSuccess(
                 com.google.gerrit.client.changes.ChangeInfo result) {
               changeInfo = result;
-              display(event.getValue());
+              cbs2.addFinal(new AsyncCallback<Void>() {
+                @Override
+                public void onSuccess(Void result) {
+                  display(event.getValue());
+                }
+                public void onFailure(Throwable caught) {}
+              }).onSuccess(null);
             }
           }));
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
index cedbd4b..0762628 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
@@ -38,6 +38,7 @@
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.SimplePanel;
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwt.user.client.ui.Widget;
 
@@ -53,22 +54,24 @@
   private static final int C_PROJECT = 5;
   private static final int C_BRANCH = 6;
   private static final int C_LAST_UPDATE = 7;
-  private static final int BASE_COLUMNS = 8;
+  private static final int C_SIZE = 8;
+  private static final int BASE_COLUMNS = 9;
 
+  private final boolean useNewFeatures = Gerrit.getConfig().getNewFeatures();
   private final List<Section> sections;
   private int columns;
   private List<String> labelNames;
 
   public ChangeTable2() {
     super(Util.C.changeItemHelp());
-    columns = BASE_COLUMNS;
+    columns = useNewFeatures ? BASE_COLUMNS : BASE_COLUMNS - 1;
     labelNames = Collections.emptyList();
 
     if (Gerrit.isSignedIn()) {
       keysAction.add(new StarKeyCommand(0, 's', Util.C.changeTableStar()));
     }
 
-    sections = new ArrayList<Section>();
+    sections = new ArrayList<>();
     table.setText(0, C_STAR, "");
     table.setText(0, C_SUBJECT, Util.C.changeTableColumnSubject());
     table.setText(0, C_STATUS, Util.C.changeTableColumnStatus());
@@ -76,6 +79,9 @@
     table.setText(0, C_PROJECT, Util.C.changeTableColumnProject());
     table.setText(0, C_BRANCH, Util.C.changeTableColumnBranch());
     table.setText(0, C_LAST_UPDATE, Util.C.changeTableColumnLastUpdate());
+    if (useNewFeatures) {
+      table.setText(0, C_SIZE, Util.C.changeTableColumnSize());
+    }
 
     final FlexCellFormatter fmt = table.getFlexCellFormatter();
     fmt.addStyleName(0, C_STAR, Gerrit.RESOURCES.css().iconHeader());
@@ -137,15 +143,21 @@
       fmt.addStyleName(row, i, Gerrit.RESOURCES.css().dataCell());
     }
     fmt.addStyleName(row, C_SUBJECT, Gerrit.RESOURCES.css().cSUBJECT());
+    fmt.addStyleName(row, C_STATUS, Gerrit.RESOURCES.css().cSTATUS());
     fmt.addStyleName(row, C_OWNER, Gerrit.RESOURCES.css().cOWNER());
     fmt.addStyleName(row, C_LAST_UPDATE, Gerrit.RESOURCES.css().cLastUpdate());
-    for (int i = BASE_COLUMNS; i < columns; i++) {
+
+    int i = C_SIZE;
+    if (useNewFeatures) {
+      fmt.addStyleName(row, i++, Gerrit.RESOURCES.css().cSIZE());
+    }
+    for (; i < columns; i++) {
       fmt.addStyleName(row, i, Gerrit.RESOURCES.css().cAPPROVAL());
     }
   }
 
   public void updateColumnsForLabels(ChangeList... lists) {
-    labelNames = new ArrayList<String>();
+    labelNames = new ArrayList<>();
     for (ChangeList list : lists) {
       for (int i = 0; i < list.length(); i++) {
         for (String name : list.get(i).labels()) {
@@ -157,18 +169,19 @@
     }
     Collections.sort(labelNames);
 
-    if (BASE_COLUMNS + labelNames.size() < columns) {
-      int n = columns - (BASE_COLUMNS + labelNames.size());
+    int baseColumns = useNewFeatures ? BASE_COLUMNS : BASE_COLUMNS - 1;
+    if (baseColumns + labelNames.size() < columns) {
+      int n = columns - (baseColumns + labelNames.size());
       for (int row = 0; row < table.getRowCount(); row++) {
         table.removeCells(row, columns, n);
       }
     }
-    columns = BASE_COLUMNS + labelNames.size();
+    columns = baseColumns + labelNames.size();
 
     FlexCellFormatter fmt = table.getFlexCellFormatter();
     for (int i = 0; i < labelNames.size(); i++) {
       String name = labelNames.get(i);
-      int col = BASE_COLUMNS + i;
+      int col = baseColumns + i;
 
       StringBuilder abbrev = new StringBuilder();
       for (String t : name.split("-")) {
@@ -188,6 +201,7 @@
 
   private void populateChangeRow(final int row, final ChangeInfo c,
       boolean highlightUnreviewed) {
+    CellFormatter fmt = table.getCellFormatter();
     if (Gerrit.isSignedIn()) {
       table.setWidget(row, C_STAR, StarredChanges.createIcon(
           c.legacy_id(),
@@ -200,6 +214,8 @@
     Change.Status status = c.status();
     if (status != Change.Status.NEW) {
       table.setText(row, C_STATUS, Util.toLongString(status));
+    } else if (!c.mergeable() && useNewFeatures) {
+      table.setText(row, C_STATUS, Util.C.changeTableNotMergeable());
     }
 
     if (c.owner() != null) {
@@ -219,14 +235,26 @@
     } else {
       table.setText(row, C_LAST_UPDATE, shortFormat(c.updated()));
     }
+    int col = C_SIZE;
+    if (useNewFeatures) {
+      if (Gerrit.isSignedIn()
+          && !Gerrit.getUserAccount().getGeneralPreferences()
+              .isSizeBarInChangeTable()) {
+        table.setText(row, col,
+            Util.M.insertionsAndDeletions(c.insertions(), c.deletions()));
+      } else {
+        table.setWidget(row, col, getSizeWidget(c));
+        fmt.getElement(row, col).setTitle(
+            Util.M.insertionsAndDeletions(c.insertions(), c.deletions()));
+      }
+      col++;
+    }
 
     boolean displayName = Gerrit.isSignedIn() && Gerrit.getUserAccount()
         .getGeneralPreferences().isShowUsernameInReviewCategory();
 
-    CellFormatter fmt = table.getCellFormatter();
-    for (int idx = 0; idx < labelNames.size(); idx++) {
+    for (int idx = 0; idx < labelNames.size(); idx++, col++) {
       String name = labelNames.get(idx);
-      int col = BASE_COLUMNS + idx;
 
       LabelInfo label = c.label(name);
       if (label == null) {
@@ -296,6 +324,31 @@
     setRowItem(row, c);
   }
 
+  private static Widget getSizeWidget(ChangeInfo c) {
+    int largeChangeSize = Gerrit.getConfig().getLargeChangeSize();
+    int changedLines = c.insertions() + c.deletions();
+    int p = 100;
+    if (changedLines < largeChangeSize) {
+      p = changedLines * 100 / largeChangeSize;
+    }
+
+    int width = Math.max(2, 70 * p / 100);
+    int red = p > 50 ? 255 : (int) Math.round((p) * 5.12);
+    int green = p < 50 ? 255 : (int) Math.round(256 - (p - 50) * 5.12);
+    String bg = "#" + toHex(red) + toHex(green) + "00";
+
+    SimplePanel panel = new SimplePanel();
+    panel.setStyleName(Gerrit.RESOURCES.css().changeSize());
+    panel.setWidth(width + "px");
+    panel.getElement().getStyle().setBackgroundColor(bg);
+    return panel;
+  }
+
+  private static String toHex(int i) {
+    String hex = Integer.toHexString(i);
+    return hex.length() == 1 ? "0" + hex : hex;
+  }
+
   public void addSection(final Section s) {
     assert s.parent == null;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentApi.java
index d87cb5e..66cd485 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentApi.java
@@ -43,13 +43,13 @@
     revision(id, "drafts").id(draftId).get(cb);
   }
 
-  public static void createDraft(PatchSet.Id id, CommentInput content,
+  public static void createDraft(PatchSet.Id id, CommentInfo content,
       AsyncCallback<CommentInfo> cb) {
     revision(id, "drafts").put(content, cb);
   }
 
   public static void updateDraft(PatchSet.Id id, String draftId,
-      CommentInput content, AsyncCallback<CommentInfo> cb) {
+      CommentInfo content, AsyncCallback<CommentInfo> cb) {
     revision(id, "drafts").id(draftId).put(content, cb);
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
index bb0c2ef..e1ec47a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInfo.java
@@ -23,42 +23,65 @@
 import java.sql.Timestamp;
 
 public class CommentInfo extends JavaScriptObject {
-  public static CommentInfo createRange(String path, Side side, int line,
-      String in_reply_to, String message, CommentRange range) {
-    CommentInfo info = createFile(path, side, in_reply_to, message);
-    info.setRange(range);
-    info.setLine(range == null ? line : range.end_line());
-    return info;
+  public static CommentInfo create(String path, Side side,
+      int line, CommentRange range) {
+    CommentInfo n = createObject().cast();
+    n.path(path);
+    n.side(side);
+    if (range != null) {
+      n.line(range.end_line());
+      n.range(range);
+    } else if (line > 0) {
+      n.line(line);
+    }
+    return n;
   }
 
-  public static CommentInfo createFile(String path, Side side,
-      String in_reply_to, String message) {
-    CommentInfo info = createObject().cast();
-    info.setPath(path);
-    info.setSide(side);
-    info.setInReplyTo(in_reply_to);
-    info.setMessage(message);
-    return info;
+  public static CommentInfo createReply(CommentInfo r) {
+    CommentInfo n = createObject().cast();
+    n.path(r.path());
+    n.side(r.side());
+    n.in_reply_to(r.id());
+    if (r.has_range()) {
+      n.line(r.range().end_line());
+      n.range(r.range());
+    } else if (r.has_line()) {
+      n.line(r.line());
+    }
+    return n;
   }
 
-  private final native void setId(String id) /*-{ this.id = id; }-*/;
-  public final native void setPath(String path) /*-{ this.path = path; }-*/;
-
-  private final void setSide(Side side) {
-    setSideRaw(side.toString());
+  public static CommentInfo copy(CommentInfo s) {
+    CommentInfo n = createObject().cast();
+    n.path(s.path());
+    n.side(s.side());
+    n.id(s.id());
+    n.in_reply_to(s.in_reply_to());
+    n.message(s.message());
+    if (s.has_range()) {
+      n.line(s.range().end_line());
+      n.range(s.range());
+    } else if (s.has_line()) {
+      n.line(s.line());
+    }
+    return n;
   }
-  private final native void setSideRaw(String side) /*-{ this.side = side; }-*/;
 
-  private final native void setLine(int line) /*-{ this.line = line; }-*/;
+  public final native void path(String p) /*-{ this.path = p }-*/;
+  public final native void id(String i) /*-{ this.id = i }-*/;
+  public final native void line(int n) /*-{ this.line = n }-*/;
+  public final native void range(CommentRange r) /*-{ this.range = r }-*/;
+  public final native void in_reply_to(String i) /*-{ this.in_reply_to = i }-*/;
+  public final native void message(String m) /*-{ this.message = m }-*/;
 
-  private final native void setInReplyTo(String in_reply_to) /*-{
-    this.in_reply_to = in_reply_to;
-  }-*/;
+  public final void side(Side side) {
+    sideRaw(side.toString());
+  }
+  private final native void sideRaw(String s) /*-{ this.side = s }-*/;
 
-  private final native void setMessage(String message) /*-{ this.message = message; }-*/;
-
-  public final native String id() /*-{ return this.id; }-*/;
-  public final native String path() /*-{ return this.path; }-*/;
+  public final native String path() /*-{ return this.path }-*/;
+  public final native String id() /*-{ return this.id }-*/;
+  public final native String in_reply_to() /*-{ return this.in_reply_to }-*/;
 
   public final Side side() {
     String s = sideRaw();
@@ -68,25 +91,27 @@
   }
   private final native String sideRaw() /*-{ return this.side }-*/;
 
-  public final native int line() /*-{ return this.line || 0; }-*/;
-  public final native String in_reply_to() /*-{ return this.in_reply_to; }-*/;
-  public final native String message() /*-{ return this.message; }-*/;
-
   public final Timestamp updated() {
-    String updatedRaw = updatedRaw();
-    return updatedRaw == null
-        ? null
-        : JavaSqlTimestamp_JsonSerializer.parseTimestamp(updatedRaw());
+    Timestamp r = updatedTimestamp();
+    if (r == null) {
+      String s = updatedRaw();
+      if (s != null) {
+        r = JavaSqlTimestamp_JsonSerializer.parseTimestamp(s);
+        updatedTimestamp(r);
+      }
+    }
+    return r;
   }
-  private final native String updatedRaw() /*-{ return this.updated; }-*/;
+  private final native String updatedRaw() /*-{ return this.updated }-*/;
+  private final native Timestamp updatedTimestamp() /*-{ return this._ts }-*/;
+  private final native void updatedTimestamp(Timestamp t) /*-{ this._ts = t }-*/;
 
-  public final native AccountInfo author() /*-{ return this.author; }-*/;
-
-  public final native boolean has_line() /*-{ return this.hasOwnProperty('line'); }-*/;
-
-  public final native CommentRange range() /*-{ return this.range; }-*/;
-
-  public final native void setRange(CommentRange range) /*-{ this.range = range; }-*/;
+  public final native AccountInfo author() /*-{ return this.author }-*/;
+  public final native int line() /*-{ return this.line || 0 }-*/;
+  public final native boolean has_line() /*-{ return this.hasOwnProperty('line') }-*/;
+  public final native boolean has_range() /*-{ return this.hasOwnProperty('range') }-*/;
+  public final native CommentRange range() /*-{ return this.range }-*/;
+  public final native String message() /*-{ return this.message }-*/;
 
   protected CommentInfo() {
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java
deleted file mode 100644
index c96a67f..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommentInput.java
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.client.changes;
-
-import com.google.gerrit.client.diff.CommentRange;
-import com.google.gerrit.common.changes.Side;
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
-
-import java.sql.Timestamp;
-
-public class CommentInput extends JavaScriptObject {
-  public static CommentInput create(CommentInfo original) {
-    CommentInput input = createObject().cast();
-    input.setId(original.id());
-    input.setPath(original.path());
-    input.setSide(original.side());
-    if (original.has_line()) {
-      input.setLine(original.line());
-    }
-    input.setRange(original.range());
-    input.setInReplyTo(original.in_reply_to());
-    input.setMessage(original.message());
-    return input;
-  }
-
-  public final native void setId(String id) /*-{ this.id = id; }-*/;
-  public final native void setPath(String path) /*-{ this.path = path; }-*/;
-
-  public final void setSide(Side side) {
-    setSideRaw(side.toString());
-  }
-  private final native void setSideRaw(String side) /*-{ this.side = side; }-*/;
-
-  public final native void setLine(int line) /*-{ this.line = line; }-*/;
-
-  public final native void setInReplyTo(String in_reply_to) /*-{
-    this.in_reply_to = in_reply_to;
-  }-*/;
-
-  public final native void setMessage(String message) /*-{ this.message = message; }-*/;
-  public final native String id() /*-{ return this.id; }-*/;
-  public final native String path() /*-{ return this.path; }-*/;
-
-  public final Side side() {
-    String s = sideRaw();
-    return s != null
-        ? Side.valueOf(s)
-        : Side.REVISION;
-  }
-  private final native String sideRaw() /*-{ return this.side }-*/;
-
-  public final native int line() /*-{ return this.line; }-*/;
-  public final native String in_reply_to() /*-{ return this.in_reply_to; }-*/;
-  public final native String message() /*-{ return this.message; }-*/;
-
-  public final Timestamp updated() {
-    return JavaSqlTimestamp_JsonSerializer.parseTimestamp(updatedRaw());
-  }
-  private final native String updatedRaw() /*-{ return this.updated; }-*/;
-
-  public final native boolean has_line() /*-{ return this.hasOwnProperty('line'); }-*/;
-
-  public final native CommentRange range() /*-{ return this.range; }-*/;
-
-  public final native void setRange(CommentRange range) /*-{ this.range = range; }-*/;
-
-  protected CommentInput() {
-  }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
index f612dcd..1f66b72 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
@@ -14,17 +14,17 @@
 
 package com.google.gerrit.client.changes;
 
-import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.ui.ChangeLink;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.CommentedActionDialog;
 import com.google.gerrit.client.ui.TextBoxChangeListener;
 import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.dom.client.PreElement;
 import com.google.gwt.dom.client.Style.Display;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -70,13 +70,14 @@
 
   public void display(String commitMessage,
       CommentLinkProcessor commentLinkProcessor) {
-    display(null, null, false, commitMessage, commentLinkProcessor);
+    display(null, null, null, false, commitMessage, commentLinkProcessor);
   }
 
-  private abstract class CommitMessageEditDialog extends CommentedActionDialog<ChangeDetail> {
+  private abstract class CommitMessageEditDialog
+      extends CommentedActionDialog<JavaScriptObject> {
     private final String originalMessage;
     public CommitMessageEditDialog(final String title, final String heading,
-        final String commitMessage, AsyncCallback<ChangeDetail> callback) {
+        final String commitMessage, AsyncCallback<JavaScriptObject> callback) {
       super(title, heading, callback);
       originalMessage = commitMessage.trim();
       message.setCharacterWidth(72);
@@ -103,7 +104,7 @@
     }
   }
 
-  public void display(final PatchSet.Id patchSetId,
+  public void display(final PatchSet.Id patchSetId, final String revision,
       Boolean starred, Boolean canEditCommitMessage, final String commitMessage,
       CommentLinkProcessor commentLinkProcessor) {
     starPanel.clear();
@@ -119,7 +120,7 @@
     }
 
     permalinkPanel.clear();
-    if (patchSetId != null) {
+    if (patchSetId != null && revision != null) {
       final Change.Id changeId = patchSetId.getParentKey();
       permalinkPanel.add(new ChangeLink(Util.C.changePermalink(), changeId));
       permalinkPanel.add(new CopyableLabel(ChangeLink.permalink(changeId),
@@ -134,24 +135,20 @@
             new CommitMessageEditDialog(Util.C.titleEditCommitMessage(),
                 Util.C.headingEditCommitMessage(),
                 commitMessage,
-                new ChangeDetailCache.IgnoreErrorCallback() {}) {
-
+                new GerritCallback<JavaScriptObject>() {
+                  @Override
+                  public void onSuccess(JavaScriptObject result) {}
+                }) {
               @Override
               public void onSend() {
-                Util.MANAGE_SVC.createNewPatchSet(patchSetId, getMessageText(),
-                    new AsyncCallback<ChangeDetail>() {
-                    @Override
-                    public void onSuccess(ChangeDetail result) {
-                      Gerrit.display(PageLinks.toChange(changeId));
-                      hide();
-                    }
-
-                    @Override
-                    public void onFailure(Throwable caught) {
-                      enableButtons(true);
-                      new ErrorDialog(caught.getMessage()).center();
-                    }
-                });
+                ChangeApi.message(changeId.get(), revision, getMessageText(),
+                    new GerritCallback<JavaScriptObject>() {
+                      @Override
+                      public void onSuccess(JavaScriptObject msg) {
+                        Gerrit.display(PageLinks.toChange(changeId));
+                        hide();
+                      }
+                    });
               }
             }.center();
           }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java
index 3002e48..ac68722 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/DashboardTable.java
@@ -19,7 +19,7 @@
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.http.client.URL;
 
@@ -35,8 +35,8 @@
   private List<String> queries;
 
   public DashboardTable(String params) {
-    titles = new ArrayList<String>();
-    queries = new ArrayList<String>();
+    titles = new ArrayList<>();
+    queries = new ArrayList<>();
     String foreach = null;
     for (String kvPair : params.split("[,;&]")) {
       String[] kv = kvPair.split("=", 2);
@@ -63,16 +63,28 @@
 
     addStyleName(Gerrit.RESOURCES.css().accountDashboard());
 
-    sections = new ArrayList<ChangeTable2.Section>();
+    sections = new ArrayList<>();
     int i = 0;
     for (String title : titles) {
       Section s = new Section();
-      s.setTitleWidget(new InlineHyperlink(title, PageLinks.toChangeQuery(queries.get(i++))));
+      String query = removeLimit(queries.get(i++));
+      s.setTitleWidget(new InlineHyperlink(title, PageLinks.toChangeQuery(query)));
       addSection(s);
       sections.add(s);
     }
   }
 
+  private String removeLimit(String query) {
+    StringBuilder unlimitedQuery = new StringBuilder();
+    String[] operators = query.split(" ");
+    for (String o : operators) {
+      if (!o.startsWith("limit:")) {
+        unlimitedQuery.append(o).append(" ");
+      }
+    }
+    return unlimitedQuery.toString().trim();
+  }
+
   public String getTitle() {
     return title;
   }
@@ -83,7 +95,7 @@
 
     if (queries.size() == 1) {
       ChangeList.next(queries.get(0),
-          0, PagedSingleListScreen.MAX_SORTKEY,
+          0, 0,
           new GerritCallback<ChangeList>() {
             @Override
             public void onSuccess(ChangeList result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/IncludedInTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/IncludedInTable.java
index 3a1ccde..62fbfb4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/IncludedInTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/IncludedInTable.java
@@ -15,9 +15,11 @@
 package com.google.gerrit.client.changes;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeInfo.IncludedInInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.common.data.IncludedInDetail;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.event.logical.shared.OpenEvent;
 import com.google.gwt.event.logical.shared.OpenHandler;
 import com.google.gwt.user.client.ui.Composite;
@@ -25,6 +27,9 @@
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 
+import java.util.ArrayList;
+import java.util.List;
+
 
 /** Displays a table of Branches and Tags containing the change record. */
 public class IncludedInTable extends Composite implements
@@ -73,13 +78,26 @@
   @Override
   public void onOpen(OpenEvent<DisclosurePanel> event) {
     if (!loaded) {
-      Util.DETAIL_SVC.includedInDetail(changeId,
-          new GerritCallback<IncludedInDetail>() {
-            @Override
-            public void onSuccess(final IncludedInDetail result) {
-              loadTable(result);
+      ChangeApi.includedIn(changeId.get(),
+          new GerritCallback<IncludedInInfo>() {
+        @Override
+        public void onSuccess(IncludedInInfo r) {
+          IncludedInDetail result = new IncludedInDetail();
+          result.setBranches(toList(r.branches()));
+          result.setTags(toList(r.tags()));
+          loadTable(result);
+        }
+
+        private List<String> toList(JsArrayString in) {
+          List<String> r = new ArrayList<>();
+          if (in != null) {
+            for (int i = 0; i < in.length(); i++) {
+              r.add(in.get(i));
             }
-          });
+          }
+          return r;
+        }
+      });
     }
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
index b2ccbcb..f98ac78 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PagedSingleListScreen.java
@@ -26,25 +26,19 @@
 import com.google.gwtexpui.globalkey.client.KeyCommand;
 
 public abstract class PagedSingleListScreen extends Screen {
-  protected static final String MIN_SORTKEY = "";
-  protected static final String MAX_SORTKEY = "z";
-
   protected final int pageSize;
+  protected final int start;
+  private final String anchorPrefix;
+
+  protected ChangeList changes;
   private ChangeTable2 table;
   private ChangeTable2.Section section;
-  protected Hyperlink prev;
-  protected Hyperlink next;
-  protected ChangeList changes;
+  private Hyperlink prev;
+  private Hyperlink next;
 
-  protected final String anchorPrefix;
-  protected boolean useLoadPrev;
-  protected String pos;
-
-  protected PagedSingleListScreen(final String anchorToken,
-      final String positionToken) {
+  protected PagedSingleListScreen(String anchorToken, int start) {
     anchorPrefix = anchorToken;
-    useLoadPrev = positionToken.startsWith("p,");
-    pos = positionToken.substring(2);
+    this.start = start;
 
     if (Gerrit.isSignedIn()) {
       final AccountGeneralPreferences p =
@@ -67,15 +61,13 @@
 
     table = new ChangeTable2() {
       {
-        keysNavigation.add(new DoLinkCommand(0, 'p', Util.C
-            .changeTablePagePrev(), prev));
-        keysNavigation.add(new DoLinkCommand(0, 'n', Util.C
-            .changeTablePageNext(), next));
+        keysNavigation.add(
+            new DoLinkCommand(0, 'p', Util.C.changeTablePagePrev(), prev),
+            new DoLinkCommand(0, 'n', Util.C.changeTablePageNext(), next));
 
-        keysNavigation.add(new DoLinkCommand(0, '[', Util.C
-            .changeTablePagePrev(), prev));
-        keysNavigation.add(new DoLinkCommand(0, ']', Util.C
-            .changeTablePageNext(), next));
+        keysNavigation.add(
+            new DoLinkCommand(0, '[', Util.C.changeTablePagePrev(), prev),
+            new DoLinkCommand(0, ']', Util.C.changeTablePageNext(), next));
 
         keysNavigation.add(new KeyCommand(0, 'R', Util.C.keyReloadSearch()) {
           @Override
@@ -98,25 +90,11 @@
   }
 
   @Override
-  protected void onLoad() {
-    super.onLoad();
-    if (useLoadPrev) {
-      loadPrev();
-    } else {
-      loadNext();
-    }
-  }
-
-  @Override
   public void registerKeys() {
     super.registerKeys();
     table.setRegisterKeys(true);
   }
 
-  protected abstract void loadPrev();
-
-  protected abstract void loadNext();
-
   protected AsyncCallback<ChangeList> loadCallback() {
     return new ScreenLoadCallback<ChangeList>(this) {
       @Override
@@ -126,22 +104,20 @@
     };
   }
 
-  protected void display(final ChangeList result) {
+  protected void display(ChangeList result) {
     changes = result;
     if (changes.length() != 0) {
-      final ChangeInfo f = changes.get(0);
-      final ChangeInfo l = changes.get(changes.length() - 1);
-
-      prev.setTargetHistoryToken(anchorPrefix + ",p," + f._sortkey());
-      next.setTargetHistoryToken(anchorPrefix + ",n," + l._sortkey());
-
-      if (useLoadPrev) {
-        prev.setVisible(f._more_changes());
-        next.setVisible(!MIN_SORTKEY.equals(pos));
+      if (start > 0) {
+        int p = start - pageSize;
+        prev.setTargetHistoryToken(anchorPrefix + (p > 0 ? "," + p : ""));
+        prev.setVisible(true);
       } else {
-        prev.setVisible(!MAX_SORTKEY.equals(pos));
-        next.setVisible(l._more_changes());
+        prev.setVisible(false);
       }
+
+      int n = start + changes.length();
+      next.setTargetHistoryToken(anchorPrefix + "," + n);
+      next.setVisible(changes.get(changes.length() - 1)._more_changes());
     }
     table.updateColumnsForLabels(result);
     section.display(result);
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 f1bb5b2..31685e4 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
@@ -18,8 +18,8 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GitwebLink;
+import com.google.gerrit.client.change.DraftActions;
 import com.google.gerrit.client.download.DownloadPanel;
-import com.google.gerrit.client.patches.PatchUtil;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
@@ -55,7 +55,6 @@
 import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.InlineLabel;
 import com.google.gwt.user.client.ui.Panel;
-import com.google.gwtjsonrpc.common.VoidResult;
 
 import java.util.HashSet;
 import java.util.List;
@@ -312,10 +311,6 @@
       final Button b =
           new Button(Util.M
               .submitPatchSet(detail.getPatchSet().getPatchSetId()));
-      if (Gerrit.getConfig().testChangeMerge()) {
-        b.setEnabled(changeDetail.getChange().isMergeable());
-      }
-
       b.addClickHandler(new ClickHandler() {
         @Override
         public void onClick(final ClickEvent event) {
@@ -483,18 +478,22 @@
         @Override
         public void onClick(final ClickEvent event) {
           b.setEnabled(false);
-          Util.MANAGE_SVC.deleteDraftChange(patchSet.getId(),
-              new GerritCallback<VoidResult>() {
-                public void onSuccess(VoidResult result) {
-                  Gerrit.display(PageLinks.MINE);
-                }
+          ChangeApi.deleteChange(patchSet.getId().getParentKey().get(),
+              new GerritCallback<JavaScriptObject>() {
+            public void onSuccess(JavaScriptObject result) {
+              Gerrit.display(PageLinks.MINE);
+            }
 
-                @Override
-                public void onFailure(Throwable caught) {
-                  b.setEnabled(true);
-                  super.onFailure(caught);
-                }
-              });
+            public void onFailure(Throwable err) {
+              if (SubmitFailureDialog.isConflict(err)) {
+                new SubmitFailureDialog(err.getMessage()).center();
+                Gerrit.display(PageLinks.MINE);
+              } else {
+                b.setEnabled(true);
+                super.onFailure(err);
+              }
+            }
+          });
         }
       });
       actionsPanel.add(b);
@@ -543,8 +542,13 @@
         @Override
         public void onClick(final ClickEvent event) {
           b.setEnabled(false);
-          Util.MANAGE_SVC.rebaseChange(patchSet.getId(),
-              new ChangeDetailCache.GerritWidgetCallback(b));
+          final Change.Id id = patchSet.getId().getParentKey();
+          ChangeApi.rebase(id.get(), patchSet.getRevision().get(),
+              new GerritCallback<ChangeInfo>() {
+                public void onSuccess(ChangeInfo result) {
+                  Gerrit.display(PageLinks.toChange(id));
+                }
+              });
         }
       });
       actionsPanel.add(b);
@@ -609,8 +613,10 @@
       @Override
       public void onClick(final ClickEvent event) {
         b.setEnabled(false);
-        Util.MANAGE_SVC.publish(patchSet.getId(),
-            new ChangeDetailCache.GerritWidgetCallback(b));
+        final Change.Id id = patchSet.getId().getParentKey();
+        ChangeApi.publish(id.get(),
+            patchSet.getRevision().get(),
+            DraftActions.cs(id));
       }
     });
     actionsPanel.add(b);
@@ -622,16 +628,10 @@
       @Override
       public void onClick(final ClickEvent event) {
         b.setEnabled(false);
-        PatchUtil.DETAIL_SVC.deleteDraftPatchSet(patchSet.getId(),
-            new ChangeDetailCache.GerritWidgetCallback(b) {
-              public void onSuccess(final ChangeDetail result) {
-                if (result != null) {
-                  detailCache.set(result);
-                } else {
-                  Gerrit.display(PageLinks.MINE);
-                }
-              }
-            });
+        final Change.Id id = patchSet.getId().getParentKey();
+        ChangeApi.deleteRevision(id.get(),
+            patchSet.getRevision().get(),
+            DraftActions.cs(id));
       }
     });
     actionsPanel.add(b);
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 aa31245..b89d1cc 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
@@ -220,11 +220,8 @@
     if (previousPatchIndex < 0) {
       return null;
     }
-    InlineHyperlink link =
-        createLink(previousPatchIndex, patchType,
-            SafeHtml.asis(Util.C.prevPatchLinkIcon()), null);
-
-    return link;
+    return createLink(previousPatchIndex, patchType,
+        SafeHtml.asis(Util.C.prevPatchLinkIcon()), null);
   }
 
   /**
@@ -235,11 +232,8 @@
     if (nextPatchIndex < 0) {
       return null;
     }
-    InlineHyperlink link =
-        createLink(nextPatchIndex, patchType, null,
-            SafeHtml.asis(Util.C.nextPatchLinkIcon()));
-
-    return link;
+    return createLink(nextPatchIndex, patchType, null,
+        SafeHtml.asis(Util.C.nextPatchLinkIcon()));
   }
 
   /**
@@ -466,7 +460,7 @@
           for (Patch p : detail.getPatches()) {
             openWindow(Dispatcher.toPatchUnified(base, p.getKey()));
           }
-        };
+        }
       });
       table.setWidget(row, C_UNIFIED, unified);
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
index 6e9b6d6..38a8cf9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
@@ -23,6 +23,8 @@
 import com.google.gerrit.client.projects.ConfigInfoCache;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
@@ -32,11 +34,14 @@
 import com.google.gerrit.client.ui.SmallHeading;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.PatchSetPublishDetail;
+import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
@@ -57,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -66,7 +72,6 @@
   private static SavedState lastState;
 
   private final PatchSet.Id patchSetId;
-  private String revision;
   private Collection<ValueRadioButton> approvalButtons;
   private ChangeDescriptionBlock descBlock;
   private ApprovalTable approvals;
@@ -74,11 +79,13 @@
   private NpTextArea message;
   private FlowPanel draftsPanel;
   private Button send;
-  private Button submit;
   private Button cancel;
   private boolean saveStateOnUnload = true;
   private List<CommentEditorPanel> commentEditors;
   private ChangeInfo change;
+  private ChangeInfo detail;
+  private NativeMap<JsArray<CommentInfo>> drafts;
+  private SubmitTypeRecord submitTypeRecord;
   private CommentLinkProcessor commentLinkProcessor;
 
   public PublishCommentScreen(final PatchSet.Id psi) {
@@ -123,10 +130,6 @@
     send.addClickHandler(this);
     buttonRow.add(send);
 
-    submit = new Button(Util.C.buttonPublishSubmitSend());
-    submit.addClickHandler(this);
-    buttonRow.add(submit);
-
     cancel = new Button(Util.C.buttonPublishCommentsCancel());
     cancel.addClickHandler(this);
     buttonRow.add(cancel);
@@ -141,7 +144,6 @@
       commentEditor.enableButtons(enabled);
     }
     send.setEnabled(enabled);
-    submit.setEnabled(enabled);
     cancel.setEnabled(enabled);
   }
 
@@ -149,50 +151,63 @@
   protected void onLoad() {
     super.onLoad();
 
-    CallbackGroup cbs = new CallbackGroup();
+    CallbackGroup group = new CallbackGroup();
+    RestApi call = ChangeApi.detail(patchSetId.getParentKey().get());
+    ChangeList.addOptions(call, EnumSet.of(
+      ListChangesOption.CURRENT_ACTIONS,
+      ListChangesOption.ALL_REVISIONS,
+      ListChangesOption.ALL_COMMITS));
+    call.get(group.add(new GerritCallback<ChangeInfo>() {
+        @Override
+        public void onSuccess(ChangeInfo result) {
+          detail = result;
+        }
+      }));
+    ChangeApi.revision(patchSetId)
+      .view("submit_type")
+      .get(group.add(new GerritCallback<NativeString>() {
+        @Override
+        public void onSuccess(NativeString result) {
+          submitTypeRecord = SubmitTypeRecord.OK(
+              Project.SubmitType.valueOf(result.asString()));
+        }
+        public void onFailure(Throwable caught) {}
+      }));
+    ChangeApi.revision(patchSetId.getParentKey().get(), "" + patchSetId.get())
+      .view("drafts")
+      .get(group.add(new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
+        @Override
+        public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+          drafts = result;
+        }
+        public void onFailure(Throwable caught) {}
+      }));
     ChangeApi.revision(patchSetId).view("review")
-        .get(cbs.add(new AsyncCallback<ChangeInfo>() {
-          @Override
-          public void onSuccess(ChangeInfo result) {
-            result.init();
-            change = result;
-          }
+      .get(group.addFinal(new GerritCallback<ChangeInfo>() {
+        @Override
+        public void onSuccess(ChangeInfo result) {
+          result.init();
+          change = result;
+          preDisplay(result);
+        }
+      }));
+  }
 
+  private void preDisplay(final ChangeInfo info) {
+    ConfigInfoCache.get(info.project_name_key(),
+        new ScreenLoadCallback<ConfigInfoCache.Entry>(this) {
           @Override
-          public void onFailure(Throwable caught) {
-            // Handled by ScreenLoadCallback.onFailure().
-          }
-        }));
-    Util.DETAIL_SVC.patchSetPublishDetail(patchSetId, cbs.addFinal(
-        new ScreenLoadCallback<PatchSetPublishDetail>(this) {
-          @Override
-          protected void preDisplay(final PatchSetPublishDetail result) {
+          protected void preDisplay(ConfigInfoCache.Entry result) {
             send.setEnabled(true);
-            PublishCommentScreen.this.preDisplay(result, this);
+            commentLinkProcessor = result.getCommentLinkProcessor();
+            setTheme(result.getTheme());
+            displayScreen();
           }
 
           @Override
           protected void postDisplay() {
             message.setFocus(true);
           }
-        }));
-  }
-
-  private void preDisplay(final PatchSetPublishDetail pubDetail,
-      final ScreenLoadCallback<PatchSetPublishDetail> origCb) {
-    ConfigInfoCache.get(pubDetail.getChange().getProject(),
-        new AsyncCallback<ConfigInfoCache.Entry>() {
-          @Override
-          public void onSuccess(ConfigInfoCache.Entry result) {
-            commentLinkProcessor = result.getCommentLinkProcessor();
-            setTheme(result.getTheme());
-            display(pubDetail);
-          }
-
-          @Override
-          public void onFailure(Throwable caught) {
-            origCb.onFailure(caught);
-          }
         });
   }
 
@@ -207,8 +222,6 @@
     final Widget sender = (Widget) event.getSource();
     if (send == sender) {
       onSend(false);
-    } else if (submit == sender) {
-      onSend(true);
     } else if (cancel == sender) {
       saveStateOnUnload = false;
       goChange();
@@ -317,14 +330,13 @@
     body.add(vp);
   }
 
-  private void display(final PatchSetPublishDetail r) {
-    ChangeDetail changeDetail = new ChangeDetail();
-    changeDetail.setChange(r.getChange());
+  private void displayScreen() {
+    ChangeDetail r = ChangeDetailCache.reverse(detail);
 
     setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
         patchSetId.get()));
-    descBlock.display(changeDetail, null, false, r.getPatchSetInfo(), r.getAccounts(),
-       r.getSubmitTypeRecord(), commentLinkProcessor);
+    descBlock.display(r, null, false, r.getCurrentPatchSetDetail().getInfo(),
+        r.getAccounts(), submitTypeRecord, commentLinkProcessor);
 
     if (r.getChange().getStatus().isOpen()) {
       initApprovals(approvalPanel);
@@ -339,14 +351,13 @@
 
     draftsPanel.clear();
     commentEditors = new ArrayList<CommentEditorPanel>();
-    revision = r.getPatchSetInfo().getRevId();
 
-    if (!r.getDrafts().isEmpty()) {
+    if (!drafts.isEmpty()) {
       draftsPanel.add(new SmallHeading(Util.C.headingPatchComments()));
 
       Panel panel = null;
       String priorFile = "";
-      for (final PatchLineComment c : r.getDrafts()) {
+      for (final PatchLineComment c : draftList()) {
         final Patch.Key patchKey = c.getKey().getParentKey();
         final String fn = patchKey.get();
         if (!fn.equals(priorFile)) {
@@ -374,11 +385,6 @@
         panel.add(editor);
       }
     }
-
-    submit.setVisible(r.canSubmit());
-    if (Gerrit.getConfig().testChangeMerge()) {
-      submit.setEnabled(r.getChange().isMergeable());
-    }
   }
 
   private void onSend(final boolean submit) {
@@ -414,7 +420,7 @@
     enableForm(false);
     new RestApi("/changes/")
       .id(String.valueOf(patchSetId.getParentKey().get()))
-      .view("revisions").id(revision).view("review")
+      .view("revisions").id(patchSetId.get()).view("review")
       .post(data, new GerritCallback<ReviewInput>() {
           @Override
           public void onSuccess(ReviewInput result) {
@@ -435,7 +441,9 @@
   }
 
   private void submit() {
-    ChangeApi.submit(patchSetId.getParentKey().get(), revision,
+    ChangeApi.submit(
+      patchSetId.getParentKey().get(),
+      "" + patchSetId.get(),
       new GerritCallback<SubmitInfo>() {
           public void onSuccess(SubmitInfo result) {
             saveStateOnUnload = false;
@@ -459,6 +467,19 @@
     Gerrit.display(PageLinks.toChange(ck), new ChangeScreen(ck));
   }
 
+  private List<PatchLineComment> draftList() {
+    List<PatchLineComment> d = new ArrayList<PatchLineComment>();
+    List<String> paths = new ArrayList<String>(drafts.keySet());
+    Collections.sort(paths);
+    for (String path : paths) {
+      JsArray<CommentInfo> comments = drafts.get(path);
+      for (int i = 0; i < comments.length(); i++) {
+        d.add(CommentEditorPanel.toComment(patchSetId, path, comments.get(i)));
+      }
+    }
+    return d;
+  }
+
   private static class ValueRadioButton extends RadioButton {
     final LabelInfo label;
     final String value;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
index 12bcf63..488b34b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/QueryScreen.java
@@ -25,17 +25,17 @@
 public class QueryScreen extends PagedSingleListScreen implements
     ChangeListScreen {
   public static QueryScreen forQuery(String query) {
-    return forQuery(query, PageLinks.TOP);
+    return forQuery(query, 0);
   }
 
-  public static QueryScreen forQuery(String query, String position) {
-    return new QueryScreen(KeyUtil.encode(query), position);
+  public static QueryScreen forQuery(String query, int start) {
+    return new QueryScreen(KeyUtil.encode(query), start);
   }
 
   private final String query;
 
-  public QueryScreen(final String encQuery, final String positionToken) {
-    super("/q/" + encQuery, positionToken);
+  public QueryScreen(String encQuery, int start) {
+    super(PageLinks.QUERY + encQuery, start);
     query = KeyUtil.decode(encQuery);
   }
 
@@ -72,13 +72,9 @@
   }
 
   @Override
-  protected void loadPrev() {
-    ChangeList.prev(query, pageSize, pos, loadCallback());
-  }
-
-  @Override
-  protected void loadNext() {
-    ChangeList.next(query, pageSize, pos, loadCallback());
+  protected void onLoad() {
+    super.onLoad();
+    ChangeList.next(query, start, pageSize, loadCallback());
   }
 
   private static boolean isSingleQuery(String query) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java
index fa3d784..0599a8b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java
@@ -14,31 +14,59 @@
 
 package com.google.gerrit.client.changes;
 
+import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
 
 public class ReviewInput extends JavaScriptObject {
   public static enum NotifyHandling {
-    NONE, OWNER, OWNER_REVIEWERS, ALL;
+    NONE, OWNER, OWNER_REVIEWERS, ALL
+  }
+
+  public static enum DraftHandling {
+    DELETE, PUBLISH, KEEP
   }
 
   public static ReviewInput create() {
     ReviewInput r = createObject().cast();
     r.init();
+    r.drafts(DraftHandling.PUBLISH);
     return r;
   }
 
   public final native void message(String m) /*-{ if(m)this.message=m; }-*/;
   public final native void label(String n, short v) /*-{ this.labels[n]=v; }-*/;
+  public final native void comments(NativeMap<JsArray<CommentInfo>> m)
+  /*-{ this.comments=m }-*/;
 
   public final void notify(NotifyHandling e) {
     _notify(e.name());
   }
   private final native void _notify(String n) /*-{ this.notify=n; }-*/;
 
+  public final void drafts(DraftHandling e) {
+    _drafts(e.name());
+  }
+  private final native void _drafts(String n) /*-{ this.drafts=n; }-*/;
+
   private final native void init() /*-{
     this.labels = {};
     this.strict_labels = true;
-    this.drafts = 'PUBLISH';
+  }-*/;
+
+  public final native void prePost() /*-{
+    var m=this.comments;
+    if (m) {
+      for (var p in m) {
+        var l=m[p];
+        for (var i=0;i<l.length;i++) {
+          var c=l[i];
+          delete c['kind'];
+          delete c['path'];
+          delete c['updated'];
+        }
+      }
+    }
   }-*/;
 
   protected ReviewInput() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
index b097bd8..be64a3d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java
@@ -34,8 +34,7 @@
 
 /** Supports the star icon displayed on changes and tracking the status. */
 public class StarredChanges {
-  private static final Event.Type<ChangeStarHandler> TYPE =
-      new Event.Type<ChangeStarHandler>();
+  private static final Event.Type<ChangeStarHandler> TYPE = new Event.Type<>();
 
   /** Handler that can receive notifications of a change's starred status. */
   public static interface ChangeStarHandler {
@@ -118,7 +117,7 @@
 
   private static boolean busy;
   private static final Map<Change.Id, Boolean> pending =
-      new LinkedHashMap<Change.Id, Boolean>(4);
+      new LinkedHashMap<>(4);
 
   private static void startRequest() {
     busy = true;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
index 76dfd58..9c16330 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/Util.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.client.changes;
 
 import com.google.gerrit.common.data.ChangeDetailService;
-import com.google.gerrit.common.data.ChangeManageService;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwt.core.client.GWT;
 import com.google.gwtjsonrpc.client.JsonUtil;
@@ -26,7 +25,6 @@
   public static final ChangeResources R = GWT.create(ChangeResources.class);
 
   public static final ChangeDetailService DETAIL_SVC;
-  public static final ChangeManageService MANAGE_SVC;
 
   private static final int SUBJECT_MAX_LENGTH = 80;
   private static final String SUBJECT_CROP_APPENDIX = "...";
@@ -35,9 +33,6 @@
   static {
     DETAIL_SVC = GWT.create(ChangeDetailService.class);
     JsonUtil.bind(DETAIL_SVC, "rpc/ChangeDetailService");
-
-    MANAGE_SVC = GWT.create(ChangeManageService.class);
-    JsonUtil.bind(MANAGE_SVC, "rpc/ChangeManageService");
   }
 
   public static String toLongString(final Change.Status status) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerNormal.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerNormal.png
index 839e8ef..9fde3fa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerNormal.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerNormal.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerPressed.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerPressed.png
index 2e509ec..e46f0aa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerPressed.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/removeReviewerPressed.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardsTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardsTable.java
index 00721b7..3b168e1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardsTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardsTable.java
@@ -60,7 +60,7 @@
   }
 
   public void display(JsArray<DashboardList> in) {
-    Map<String, DashboardInfo> map = new HashMap<String, DashboardInfo>();
+    Map<String, DashboardInfo> map = new HashMap<>();
     for (DashboardList list : Natives.asList(in)) {
       for (DashboardInfo d : Natives.asList(list)) {
         if (!map.containsKey(d.id())) {
@@ -68,7 +68,7 @@
         }
       }
     }
-    display(new ArrayList<DashboardInfo>(map.values()));
+    display(new ArrayList<>(map.values()));
   }
 
   public void display(List<DashboardInfo> list) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java
new file mode 100644
index 0000000..36bd64f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java
@@ -0,0 +1,326 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.diff;
+
+import static com.google.gerrit.client.diff.OverviewBar.MarkType.DELETE;
+import static com.google.gerrit.client.diff.OverviewBar.MarkType.EDIT;
+import static com.google.gerrit.client.diff.OverviewBar.MarkType.INSERT;
+
+import com.google.gerrit.client.diff.DiffInfo.Region;
+import com.google.gerrit.client.diff.DiffInfo.Span;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.LineClassWhere;
+import net.codemirror.lib.Configuration;
+import net.codemirror.lib.LineCharacter;
+import net.codemirror.lib.LineWidget;
+import net.codemirror.lib.TextMarker;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/** Colors modified regions for {@link SideBySide2}. */
+class ChunkManager {
+  private final SideBySide2 host;
+  private final CodeMirror cmA;
+  private final CodeMirror cmB;
+  private final OverviewBar sidePanel;
+  private final LineMapper mapper;
+
+  private List<DiffChunkInfo> chunks;
+  private List<TextMarker> markers;
+  private List<Runnable> undo;
+  private List<LineWidget> padding;
+
+  ChunkManager(SideBySide2 host,
+      CodeMirror cmA,
+      CodeMirror cmB,
+      OverviewBar sidePanel) {
+    this.host = host;
+    this.cmA = cmA;
+    this.cmB = cmB;
+    this.sidePanel = sidePanel;
+    this.mapper = new LineMapper();
+  }
+
+  LineMapper getLineMapper() {
+    return mapper;
+  }
+
+  DiffChunkInfo getFirst() {
+    if (!chunks.isEmpty()) {
+      for (DiffChunkInfo d : chunks) {
+        if (d.getSide() == DisplaySide.B) {
+          return d;
+        }
+      }
+      return chunks.get(0);
+    }
+    return null;
+  }
+
+  void reset() {
+    mapper.reset();
+    for (TextMarker m : markers) {
+      m.clear();
+    }
+    for (Runnable r : undo) {
+      r.run();
+    }
+    for (LineWidget w : padding) {
+      w.clear();
+    }
+  }
+
+  void render(DiffInfo diff) {
+    chunks = new ArrayList<>();
+    markers = new ArrayList<>();
+    undo = new ArrayList<>();
+    padding = new ArrayList<>();
+
+    String diffColor = diff.meta_a() == null || diff.meta_b() == null
+        ? DiffTable.style.intralineBg()
+        : DiffTable.style.diff();
+
+    for (Region current : Natives.asList(diff.content())) {
+      if (current.ab() != null) {
+        mapper.appendCommon(current.ab().length());
+      } else if (current.skip() > 0) {
+        mapper.appendCommon(current.skip());
+      } else if (current.common()) {
+        mapper.appendCommon(current.b().length());
+      } else {
+        render(current, diffColor);
+      }
+    }
+  }
+
+  private void render(Region region, String diffColor) {
+    int startA = mapper.getLineA();
+    int startB = mapper.getLineB();
+
+    JsArrayString a = region.a();
+    JsArrayString b = region.b();
+    int aLen = a != null ? a.length() : 0;
+    int bLen = b != null ? b.length() : 0;
+
+    String color = a == null || b == null
+        ? diffColor
+        : DiffTable.style.intralineBg();
+
+    colorLines(cmA, color, startA, aLen);
+    colorLines(cmB, color, startB, bLen);
+    markEdit(cmA, startA, a, region.edit_a());
+    markEdit(cmB, startB, b, region.edit_b());
+    addPadding(cmA, startA + aLen - 1, bLen - aLen);
+    addPadding(cmB, startB + bLen - 1, aLen - bLen);
+    addGutterTag(region, startA, startB);
+    mapper.appendReplace(aLen, bLen);
+
+    int endA = mapper.getLineA() - 1;
+    int endB = mapper.getLineB() - 1;
+    if (aLen > 0) {
+      addDiffChunk(cmB, endB, endA, aLen, bLen > 0);
+    }
+    if (bLen > 0) {
+      addDiffChunk(cmA, endA, endB, bLen, aLen > 0);
+    }
+  }
+
+  private void addGutterTag(Region region, int startA, int startB) {
+    if (region.a() == null) {
+      sidePanel.add(cmB, startB, region.b().length(), INSERT);
+    } else if (region.b() == null) {
+      sidePanel.add(cmA, startA, region.a().length(), DELETE);
+    } else {
+      sidePanel.add(cmB, startB, region.b().length(), EDIT);
+    }
+  }
+
+  private void markEdit(CodeMirror cm, int startLine,
+      JsArrayString lines, JsArray<Span> edits) {
+    if (lines == null || edits == null) {
+      return;
+    }
+
+    EditIterator iter = new EditIterator(lines, startLine);
+    Configuration bg = Configuration.create()
+        .set("className", DiffTable.style.intralineBg())
+        .set("readOnly", true);
+
+    Configuration diff = Configuration.create()
+        .set("className", DiffTable.style.diff())
+        .set("readOnly", true);
+
+    LineCharacter last = CodeMirror.pos(0, 0);
+    for (Span span : Natives.asList(edits)) {
+      LineCharacter from = iter.advance(span.skip());
+      LineCharacter to = iter.advance(span.mark());
+      if (from.getLine() == last.getLine()) {
+        markers.add(cm.markText(last, from, bg));
+      } else {
+        markers.add(cm.markText(CodeMirror.pos(from.getLine(), 0), from, bg));
+      }
+      markers.add(cm.markText(from, to, diff));
+      last = to;
+      colorLines(cm, LineClassWhere.BACKGROUND,
+          DiffTable.style.diff(),
+          from.getLine(), to.getLine());
+    }
+  }
+
+  private void colorLines(CodeMirror cm, String color, int line, int cnt) {
+    colorLines(cm, LineClassWhere.WRAP, color, line, line + cnt);
+  }
+
+  private void colorLines(final CodeMirror cm, final LineClassWhere where,
+      final String className, final int start, final int end) {
+    if (start < end) {
+      for (int line = start; line < end; line++) {
+        cm.addLineClass(line, where, className);
+      }
+      undo.add(new Runnable() {
+        @Override
+        public void run() {
+          for (int line = start; line < end; line++) {
+            cm.removeLineClass(line, where, className);
+          }
+        }
+      });
+    }
+  }
+
+  /**
+   * Insert a new padding div below the given line.
+   *
+   * @param cm parent CodeMirror to add extra space into.
+   * @param line line to put the padding below.
+   * @param len number of lines to pad. Padding is inserted only if
+   *        {@code len >= 1}.
+   */
+  private void addPadding(CodeMirror cm, int line, int len) {
+    if (0 < len) {
+      // DiffTable adds 1px bottom padding to each line to preserve
+      // sufficient space for underscores commonly appearing in code.
+      // Padding should be 1em + 1px high for each line. Add within
+      // the browser using height + padding-bottom.
+      Element pad = DOM.createDiv();
+      pad.setClassName(DiffTable.style.padding());
+      pad.getStyle().setHeight(len, Unit.EM);
+      pad.getStyle().setPaddingBottom(len, Unit.PX);
+      padding.add(cm.addLineWidget(
+        line == -1 ? 0 : line,
+        pad,
+        Configuration.create()
+          .set("coverGutter", true)
+          .set("noHScroll", true)
+          .set("above", line == -1)));
+    }
+  }
+
+  private void addDiffChunk(CodeMirror cmToPad, int lineToPad,
+      int lineOnOther, int chunkSize, boolean edit) {
+    chunks.add(new DiffChunkInfo(host.otherCm(cmToPad).side(),
+        lineOnOther - chunkSize + 1, lineOnOther, edit));
+  }
+
+  Runnable diffChunkNav(final CodeMirror cm, final Direction dir) {
+    return new Runnable() {
+      @Override
+      public void run() {
+        int line = cm.hasActiveLine() ? cm.getLineNumber(cm.getActiveLine()) : 0;
+        int res = Collections.binarySearch(
+                chunks,
+                new DiffChunkInfo(cm.side(), line, 0, false),
+                getDiffChunkComparator());
+        if (res < 0) {
+          res = -res - (dir == Direction.PREV ? 1 : 2);
+        }
+        res = res + (dir == Direction.PREV ? -1 : 1);
+        if (res < 0 || chunks.size() <= res) {
+          return;
+        }
+
+        DiffChunkInfo lookUp = chunks.get(res);
+        // If edit, skip the deletion chunk and set focus on the insertion one.
+        if (lookUp.isEdit() && lookUp.getSide() == DisplaySide.A) {
+          res = res + (dir == Direction.PREV ? -1 : 1);
+          if (res < 0 || chunks.size() <= res) {
+            return;
+          }
+        }
+
+        DiffChunkInfo target = chunks.get(res);
+        CodeMirror targetCm = host.getCmFromSide(target.getSide());
+        targetCm.setCursor(LineCharacter.create(target.getStart()));
+        targetCm.focus();
+        targetCm.scrollToY(
+            targetCm.heightAtLine(target.getStart(), "local") -
+            0.5 * cmB.getScrollbarV().getClientHeight());
+      }
+    };
+  }
+
+  private Comparator<DiffChunkInfo> getDiffChunkComparator() {
+    // Chunks are ordered by their starting line. If it's a deletion,
+    // use its corresponding line on the revision side for comparison.
+    // In the edit case, put the deletion chunk right before the
+    // insertion chunk. This placement guarantees well-ordering.
+    return new Comparator<DiffChunkInfo>() {
+      @Override
+      public int compare(DiffChunkInfo a, DiffChunkInfo b) {
+        if (a.getSide() == b.getSide()) {
+          return a.getStart() - b.getStart();
+        } else if (a.getSide() == DisplaySide.A) {
+          int comp = mapper.lineOnOther(a.getSide(), a.getStart())
+              .getLine() - b.getStart();
+          return comp == 0 ? -1 : comp;
+        } else {
+          int comp = a.getStart() -
+              mapper.lineOnOther(b.getSide(), b.getStart()).getLine();
+          return comp == 0 ? 1 : comp;
+        }
+      }
+    };
+  }
+
+  DiffChunkInfo getDiffChunk(DisplaySide side, int line) {
+    int res = Collections.binarySearch(
+        chunks,
+        new DiffChunkInfo(side, line, 0, false), // Dummy DiffChunkInfo
+        getDiffChunkComparator());
+    if (res >= 0) {
+      return chunks.get(res);
+    } else { // The line might be within a DiffChunk
+      res = -res - 1;
+      if (res > 0) {
+        DiffChunkInfo info = chunks.get(res - 1);
+        if (info.getSide() == side && info.getStart() <= line &&
+            line <= info.getEnd()) {
+          return info;
+        }
+      }
+    }
+    return null;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java
index 63b8cca..1d74520 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java
@@ -15,8 +15,6 @@
 package com.google.gerrit.client.diff;
 
 import com.google.gerrit.client.changes.CommentInfo;
-import com.google.gerrit.client.diff.PaddingManager.PaddingWidgetWrapper;
-import com.google.gerrit.client.diff.SidePanel.GutterWrapper;
 import com.google.gwt.event.dom.client.MouseOutEvent;
 import com.google.gwt.event.dom.client.MouseOutHandler;
 import com.google.gwt.event.dom.client.MouseOverEvent;
@@ -34,24 +32,17 @@
     Resources.I.style().ensureInjected();
   }
 
-  private PaddingManager widgetManager;
-  private PaddingWidgetWrapper selfWidgetWrapper;
-  private SideBySide2 parent;
-  private CodeMirror cm;
-  private DisplaySide side;
-  private DiffChunkInfo diffChunkInfo;
-  private GutterWrapper gutterWrapper;
+  private final CommentGroup group;
+  private OverviewBar.MarkHandle mark;
   private FromTo fromTo;
   private TextMarker rangeMarker;
   private TextMarker rangeHighlightMarker;
 
-  CommentBox(CodeMirror cm, CommentInfo info, DisplaySide side) {
-    this.cm = cm;
-    this.side = side;
-    CommentRange range = info.range();
+  CommentBox(CommentGroup group, CommentRange range) {
+    this.group = group;
     if (range != null) {
       fromTo = FromTo.create(range);
-      rangeMarker = cm.markText(
+      rangeMarker = group.getCm().markText(
           fromTo.getFrom(),
           fromTo.getTo(),
           Configuration.create()
@@ -71,71 +62,35 @@
     }, MouseOutEvent.getType());
   }
 
-  @Override
-  protected void onLoad() {
-    resizePaddingWidget();
-  }
-
-  void resizePaddingWidget() {
-    if (!getCommentInfo().has_line()) {
-      return;
-    }
-    parent.defer(new Runnable() {
-      @Override
-      public void run() {
-        assert selfWidgetWrapper != null;
-        selfWidgetWrapper.getWidget().changed();
-        if (diffChunkInfo != null) {
-          parent.resizePaddingOnOtherSide(side, diffChunkInfo.getEnd());
-        } else {
-          assert widgetManager != null;
-          widgetManager.resizePaddingWidget();
-        }
-      }
-    });
-  }
-
   abstract CommentInfo getCommentInfo();
   abstract boolean isOpen();
 
   void setOpen(boolean open) {
-    resizePaddingWidget();
+    group.resize();
     setRangeHighlight(open);
     getCm().focus();
   }
 
-  PaddingManager getPaddingManager() {
-    return widgetManager;
+  CommentGroup getCommentGroup() {
+    return group;
   }
 
-  void setPaddingManager(PaddingManager manager) {
-    widgetManager = manager;
+  CommentManager getCommentManager() {
+    return group.getCommentManager();
   }
 
-  void setSelfWidgetWrapper(PaddingWidgetWrapper wrapper) {
-    selfWidgetWrapper = wrapper;
+  OverviewBar.MarkHandle getMark() {
+    return mark;
   }
 
-  PaddingWidgetWrapper getSelfWidgetWrapper() {
-    return selfWidgetWrapper;
-  }
-
-  void setDiffChunkInfo(DiffChunkInfo info) {
-    this.diffChunkInfo = info;
-  }
-
-  void setParent(SideBySide2 parent) {
-    this.parent = parent;
-  }
-
-  void setGutterWrapper(GutterWrapper wrapper) {
-    gutterWrapper = wrapper;
+  void setMark(OverviewBar.MarkHandle mh) {
+    mark = mh;
   }
 
   void setRangeHighlight(boolean highlight) {
     if (fromTo != null) {
       if (highlight && rangeHighlightMarker == null) {
-        rangeHighlightMarker = cm.markText(
+        rangeHighlightMarker = group.getCm().markText(
             fromTo.getFrom(),
             fromTo.getTo(),
             Configuration.create()
@@ -150,18 +105,15 @@
   void clearRange() {
     if (rangeMarker != null) {
       rangeMarker.clear();
+      rangeMarker = null;
     }
   }
 
-  GutterWrapper getGutterWrapper() {
-    return gutterWrapper;
-  }
-
-  DisplaySide getSide() {
-    return side;
-  }
-
   CodeMirror getCm() {
-    return cm;
+    return group.getCm();
+  }
+
+  FromTo getFromTo() {
+    return fromTo;
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
index 36f57b9..b95a86e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css
@@ -13,21 +13,35 @@
  * limitations under the License.
  */
 
-.commentBox {
-  position: relative;
-  width: 679px;
-  min-height: 16px;
+.commentWidgets {
+  max-width: 650px;
+
   font-family: sans-serif;
   background-color: #fcfa96;
   border: 1px solid black;
   -webkit-box-shadow: 3px 3px 3px #888888;
   -moz-box-shadow: 3px 3px 3px #888888;
   box-shadow: 3px 3px 3px #888888;
+
+  /* margin-bottom is fixed in CommentGroup.computeHeight() */
   margin-bottom: 5px;
   margin-right: 5px;
+
+  -webkit-touch-callout: initial;
+  -webkit-user-select: initial;
+  -khtml-user-select: initial;
+  -moz-user-select: text;
+  -ms-user-select: initial;
 }
 
-.header { cursor: pointer; }
+.commentBox {
+  position: relative;
+  min-height: 16px;
+}
+
+.header {
+  cursor: pointer;
+}
 
 .summary {
   color: #777;
@@ -53,12 +67,15 @@
   padding-top: 2px;
   position: relative;
 }
-.contents p,
-.contents ul {
+.message {
+  overflow-x: auto;
+}
+.message p,
+.message ul,
+.message blockquote {
   -webkit-margin-before: 0;
   -webkit-margin-after: 0.3em;
 }
-
 .commentBox button {
   margin-right: 3px;
   margin-bottom: 1px;
@@ -79,3 +96,16 @@
   white-space: nowrap;
   color: #fff;
 }
+
+@sprite .go_prev {
+  gwt-image: "go_prev";
+  display: inline-block;
+}
+@sprite .go_next {
+  gwt-image: "go_next";
+  display: inline-block;
+}
+@sprite .go_up {
+  gwt-image: "go_up";
+  display: inline-block;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
new file mode 100644
index 0000000..9fc36b0
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
@@ -0,0 +1,249 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.diff;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.Configuration;
+import net.codemirror.lib.LineWidget;
+import net.codemirror.lib.TextMarker.FromTo;
+
+/**
+ * LineWidget attached to a CodeMirror container.
+ *
+ * When a comment is placed on a line a CommentWidget is created on both sides.
+ * The group tracks all comment boxes on that same line, and also includes an
+ * empty padding element to keep subsequent lines vertically aligned.
+ */
+class CommentGroup extends Composite {
+  static void pair(CommentGroup a, CommentGroup b) {
+    a.peer = b;
+    b.peer = a;
+  }
+
+  private final CommentManager manager;
+  private final CodeMirror cm;
+  private final int line;
+  private final FlowPanel comments;
+  private final Element padding;
+  private LineWidget lineWidget;
+  private Timer resizeTimer;
+  private CommentGroup peer;
+
+  CommentGroup(CommentManager manager, CodeMirror cm, int line) {
+    this.manager = manager;
+    this.cm = cm;
+    this.line = line;
+
+    comments = new FlowPanel();
+    comments.setStyleName(Resources.I.style().commentWidgets());
+    comments.setVisible(false);
+    initWidget(new SimplePanel(comments));
+
+    padding = DOM.createDiv();
+    padding.setClassName(DiffTable.style.padding());
+    getElement().appendChild(padding);
+  }
+
+  CommentManager getCommentManager() {
+    return manager;
+  }
+
+  CodeMirror getCm() {
+    return cm;
+  }
+
+  CommentGroup getPeer() {
+    return peer;
+  }
+
+  int getLine() {
+    return line;
+  }
+
+  void add(PublishedBox box) {
+    comments.add(box);
+    comments.setVisible(true);
+  }
+
+  void add(DraftBox box) {
+    PublishedBox p = box.getReplyToBox();
+    if (p != null) {
+      for (int i = 0; i < getBoxCount(); i++) {
+        if (p == getCommentBox(i)) {
+          comments.insert(box, i + 1);
+          comments.setVisible(true);
+          resize();
+          return;
+        }
+      }
+    }
+    comments.add(box);
+    comments.setVisible(true);
+    resize();
+  }
+
+  CommentBox getCommentBox(int i) {
+    return (CommentBox) comments.getWidget(i);
+  }
+
+  int getBoxCount() {
+    return comments.getWidgetCount();
+  }
+
+  void openCloseLast() {
+    if (0 < getBoxCount()) {
+      CommentBox box = getCommentBox(getBoxCount() - 1);
+      box.setOpen(!box.isOpen());
+    }
+  }
+
+  void openCloseAll() {
+    boolean open = false;
+    for (int i = 0; i < getBoxCount(); i++) {
+      if (!getCommentBox(i).isOpen()) {
+        open = true;
+        break;
+      }
+    }
+    setOpenAll(open);
+  }
+
+  void setOpenAll(boolean open) {
+    for (int i = 0; i < getBoxCount(); i++) {
+      getCommentBox(i).setOpen(open);
+    }
+  }
+
+  void remove(DraftBox box) {
+    comments.remove(box);
+    comments.setVisible(0 < getBoxCount());
+
+    if (0 < getBoxCount() || 0 < peer.getBoxCount()) {
+      resize();
+    } else {
+      detach();
+      peer.detach();
+    }
+  }
+
+  private void detach() {
+    if (lineWidget != null) {
+      lineWidget.clear();
+      lineWidget = null;
+      updateSelection();
+    }
+    manager.clearLine(cm.side(), line, this);
+    removeFromParent();
+  }
+
+  void attachPair(DiffTable parent) {
+    if (lineWidget == null && peer.lineWidget == null) {
+      this.attach(parent);
+      peer.attach(parent);
+    }
+  }
+
+  private void attach(DiffTable parent) {
+    parent.add(this);
+    lineWidget = cm.addLineWidget(Math.max(0, line - 1), getElement(),
+        Configuration.create()
+          .set("coverGutter", true)
+          .set("noHScroll", true)
+          .set("above", line <= 0)
+          .set("insertAt", 0));
+  }
+
+  void handleRedraw() {
+    lineWidget.onRedraw(new Runnable() {
+      @Override
+      public void run() {
+        if (canComputeHeight() && peer.canComputeHeight()) {
+          if (resizeTimer != null) {
+            resizeTimer.cancel();
+            resizeTimer = null;
+          }
+          adjustPadding(CommentGroup.this, peer);
+        } else if (resizeTimer == null) {
+          resizeTimer = new Timer() {
+            @Override
+            public void run() {
+              if (canComputeHeight() && peer.canComputeHeight()) {
+                cancel();
+                resizeTimer = null;
+                adjustPadding(CommentGroup.this, peer);
+              }
+            }
+          };
+          resizeTimer.scheduleRepeating(5);
+        }
+      }
+    });
+  }
+
+  @Override
+  protected void onUnload() {
+    super.onUnload();
+    if (resizeTimer != null) {
+      resizeTimer.cancel();
+    }
+  }
+
+  void resize() {
+    if (lineWidget != null) {
+      adjustPadding(this, peer);
+    }
+  }
+
+  private void updateSelection() {
+    if (cm.somethingSelected()) {
+      FromTo r = cm.getSelectedRange();
+      if (r.getTo().getLine() >= line) {
+        cm.setSelection(r.getFrom(), r.getTo());
+      }
+    }
+  }
+
+  private boolean canComputeHeight() {
+    return !comments.isVisible() || comments.getOffsetHeight() > 0;
+  }
+
+  private int computeHeight() {
+    if (comments.isVisible()) {
+      // Include margin-bottom: 5px from CSS class.
+      return comments.getOffsetHeight() + 5;
+    }
+    return 0;
+  }
+
+  private static void adjustPadding(CommentGroup a, CommentGroup b) {
+    int apx = a.computeHeight();
+    int bpx = b.computeHeight();
+    int h = Math.max(apx, bpx);
+    a.padding.getStyle().setHeight(Math.max(0, h - apx), Unit.PX);
+    b.padding.getStyle().setHeight(Math.max(0, h - bpx), Unit.PX);
+    a.lineWidget.changed();
+    b.lineWidget.changed();
+    a.updateSelection();
+    b.updateSelection();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java
new file mode 100644
index 0000000..c082b8c
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java
@@ -0,0 +1,429 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.diff;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.patches.SkippedLine;
+import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.ui.CommentLinkProcessor;
+import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.JsArray;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.LineHandle;
+import net.codemirror.lib.LineCharacter;
+import net.codemirror.lib.TextMarker.FromTo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/** Tracks comment widgets for {@link SideBySide2}. */
+class CommentManager {
+  private final SideBySide2 host;
+  private final PatchSet.Id base;
+  private final PatchSet.Id revision;
+  private final String path;
+  private final CommentLinkProcessor commentLinkProcessor;
+
+  private final Map<String, PublishedBox> published;
+  private final SortedMap<Integer, CommentGroup> sideA;
+  private final SortedMap<Integer, CommentGroup> sideB;
+  private final Set<DraftBox> unsavedDrafts;
+  private boolean attached;
+
+  CommentManager(SideBySide2 host,
+      PatchSet.Id base, PatchSet.Id revision,
+      String path,
+      CommentLinkProcessor clp) {
+    this.host = host;
+    this.base = base;
+    this.revision = revision;
+    this.path = path;
+    this.commentLinkProcessor = clp;
+
+    published = new HashMap<>();
+    sideA = new TreeMap<>();
+    sideB = new TreeMap<>();
+    unsavedDrafts = new HashSet<>();
+  }
+
+  SideBySide2 getSideBySide2() {
+    return host;
+  }
+
+  void setExpandAllComments(boolean b) {
+    for (CommentGroup g : sideA.values()) {
+      g.setOpenAll(b);
+    }
+    for (CommentGroup g : sideB.values()) {
+      g.setOpenAll(b);
+    }
+  }
+
+  Runnable commentNav(final CodeMirror src, final Direction dir) {
+    return new Runnable() {
+      @Override
+      public void run() {
+        // Every comment appears in both side maps as a linked pair.
+        // It is only necessary to search one side to find a comment
+        // on either side of the editor pair.
+        SortedMap<Integer, CommentGroup> map = map(src.side());
+        int line = src.hasActiveLine()
+            ? src.getLineNumber(src.getActiveLine()) + 1
+            : 0;
+        if (dir == Direction.NEXT) {
+          map = map.tailMap(line + 1);
+          if (map.isEmpty()) {
+            return;
+          }
+          line = map.firstKey();
+        } else {
+          map = map.headMap(line);
+          if (map.isEmpty()) {
+            return;
+          }
+          line = map.lastKey();
+        }
+
+        CommentGroup g = map.get(line);
+        if (g.getBoxCount() == 0) {
+          g = g.getPeer();
+        }
+
+        CodeMirror cm = g.getCm();
+        double y = cm.heightAtLine(g.getLine() - 1, "local");
+        cm.setCursor(LineCharacter.create(g.getLine() - 1));
+        cm.scrollToY(y - 0.5 * cm.getScrollbarV().getClientHeight());
+        cm.focus();
+      }
+    };
+  }
+
+  void render(CommentsCollections in, boolean expandAll) {
+    if (in.publishedBase != null) {
+      renderPublished(DisplaySide.A, in.publishedBase);
+    }
+    if (in.publishedRevision != null) {
+      renderPublished(DisplaySide.B, in.publishedRevision);
+    }
+    if (in.draftsBase != null) {
+      renderDrafts(DisplaySide.A, in.draftsBase);
+    }
+    if (in.draftsRevision != null) {
+      renderDrafts(DisplaySide.B, in.draftsRevision);
+    }
+    if (expandAll) {
+      setExpandAllComments(true);
+    }
+    for (CommentGroup g : sideA.values()) {
+      g.attachPair(host.diffTable);
+    }
+    for (CommentGroup g : sideB.values()) {
+      g.attachPair(host.diffTable);
+      g.handleRedraw();
+    }
+    attached = true;
+  }
+
+  private void renderPublished(DisplaySide forSide, JsArray<CommentInfo> in) {
+    for (CommentInfo info : Natives.asList(in)) {
+      DisplaySide side = displaySide(info, forSide);
+      if (side != null) {
+        CommentGroup group = group(side, info.line());
+        PublishedBox box = new PublishedBox(
+            group,
+            commentLinkProcessor,
+            getPatchSetIdFromSide(side),
+            info);
+        group.add(box);
+        box.setMark(host.diffTable.overview.add(
+            host.getCmFromSide(side),
+            Math.max(0, info.line() - 1), 1,
+            OverviewBar.MarkType.COMMENT));
+        published.put(info.id(), box);
+      }
+    }
+  }
+
+  private void renderDrafts(DisplaySide forSide, JsArray<CommentInfo> in) {
+    for (CommentInfo info : Natives.asList(in)) {
+      DisplaySide side = displaySide(info, forSide);
+      if (side != null) {
+        addDraftBox(side, info);
+      }
+    }
+  }
+
+  /**
+   * Create a new {@link DraftBox} at the specified line and focus it.
+   *
+   * @param side which side the draft will appear on.
+   * @param line the line the draft will be at. Lines are 1-based. Line 0 is a
+   *        special case creating a file level comment.
+   */
+  void insertNewDraft(DisplaySide side, int line) {
+    if (line == 0) {
+      host.getSkipManager().ensureFirstLineIsVisible();
+    }
+
+    CommentGroup group = group(side, line);
+    if (0 < group.getBoxCount()) {
+      CommentBox last = group.getCommentBox(group.getBoxCount() - 1);
+      if (last instanceof DraftBox) {
+        ((DraftBox)last).setEdit(true);
+      } else {
+        ((PublishedBox)last).doReply();
+      }
+    } else {
+      addDraftBox(side, CommentInfo.create(
+          path,
+          getStoredSideFromDisplaySide(side),
+          line,
+          null)).setEdit(true);
+    }
+  }
+
+  DraftBox addDraftBox(DisplaySide side, CommentInfo info) {
+    CommentGroup group = group(side, info.line());
+    DraftBox box = new DraftBox(
+        group,
+        commentLinkProcessor,
+        getPatchSetIdFromSide(side),
+        info);
+
+    if (info.in_reply_to() != null) {
+      PublishedBox r = published.get(info.in_reply_to());
+      if (r != null) {
+        r.setReplyBox(box);
+      }
+    }
+
+    group.add(box);
+    box.setMark(host.diffTable.overview.add(
+        host.getCmFromSide(side),
+        Math.max(0, info.line() - 1), 1,
+        OverviewBar.MarkType.DRAFT));
+    return box;
+  }
+
+  private DisplaySide displaySide(CommentInfo info, DisplaySide forSide) {
+    if (info.side() == Side.PARENT) {
+      return base == null ? DisplaySide.A : null;
+    }
+    return forSide;
+  }
+
+  List<SkippedLine> splitSkips(int context, List<SkippedLine> skips) {
+    if (sideB.containsKey(0)) {
+      // Special case of file comment; cannot skip first line.
+      for (SkippedLine skip : skips) {
+        if (skip.getStartB() == 0) {
+          skip.incrementStart(1);
+        }
+      }
+    }
+
+    // TODO: This is not optimal, but shouldn't be too costly in most cases.
+    // Maybe rewrite after done keeping track of diff chunk positions.
+    for (int boxLine : sideB.tailMap(1).keySet()) {
+      List<SkippedLine> temp = new ArrayList<>(skips.size() + 2);
+      for (SkippedLine skip : skips) {
+        int startLine = skip.getStartB();
+        int deltaBefore = boxLine - startLine;
+        int deltaAfter = startLine + skip.getSize() - boxLine;
+        if (deltaBefore < -context || deltaAfter < -context) {
+          temp.add(skip); // Size guaranteed to be greater than 1
+        } else if (deltaBefore > context && deltaAfter > context) {
+          SkippedLine before = new SkippedLine(
+              skip.getStartA(), skip.getStartB(),
+              skip.getSize() - deltaAfter - context);
+          skip.incrementStart(deltaBefore + context);
+          checkAndAddSkip(temp, before);
+          checkAndAddSkip(temp, skip);
+        } else if (deltaAfter > context) {
+          skip.incrementStart(deltaBefore + context);
+          checkAndAddSkip(temp, skip);
+        } else if (deltaBefore > context) {
+          skip.reduceSize(deltaAfter + context);
+          checkAndAddSkip(temp, skip);
+        }
+      }
+      if (temp.isEmpty()) {
+        return temp;
+      }
+      skips = temp;
+    }
+    return skips;
+  }
+
+  private static void checkAndAddSkip(List<SkippedLine> out, SkippedLine s) {
+    if (s.getSize() > 1) {
+      out.add(s);
+    }
+  }
+
+  void clearLine(DisplaySide side, int line, CommentGroup group) {
+    SortedMap<Integer, CommentGroup> map = map(side);
+    if (map.get(line) == group) {
+      map.remove(line);
+    }
+  }
+
+  Runnable toggleOpenBox(final CodeMirror cm) {
+    return new Runnable() {
+      public void run() {
+        if (cm.hasActiveLine()) {
+          CommentGroup w = map(cm.side()).get(
+              cm.getLineNumber(cm.getActiveLine()) + 1);
+          if (w != null) {
+            w.openCloseLast();
+          }
+        }
+      }
+    };
+  }
+
+  Runnable openCloseAll(final CodeMirror cm) {
+    return new Runnable() {
+      @Override
+      public void run() {
+        if (cm.hasActiveLine()) {
+          CommentGroup w = map(cm.side()).get(
+              cm.getLineNumber(cm.getActiveLine()) + 1);
+          if (w != null) {
+            w.openCloseAll();
+          }
+        }
+      }
+    };
+  }
+
+  Runnable insertNewDraft(final CodeMirror cm) {
+    if (!Gerrit.isSignedIn()) {
+      return new Runnable() {
+        @Override
+        public void run() {
+          String token = host.getToken();
+          if (cm.hasActiveLine()) {
+            LineHandle handle = cm.getActiveLine();
+            int line = cm.getLineNumber(handle) + 1;
+            token += "@" + (cm.side() == DisplaySide.A ? "a" : "") + line;
+          }
+          Gerrit.doSignIn(token);
+        }
+     };
+    }
+
+    return new Runnable() {
+      public void run() {
+        if (cm.hasActiveLine()) {
+          newDraft(cm);
+        }
+      }
+    };
+  }
+
+  private void newDraft(CodeMirror cm) {
+    int line = cm.getLineNumber(cm.getActiveLine()) + 1;
+    if (cm.somethingSelected()) {
+      FromTo fromTo = cm.getSelectedRange();
+      LineCharacter end = fromTo.getTo();
+      if (end.getCh() == 0) {
+        end.setLine(end.getLine() - 1);
+        end.setCh(cm.getLine(end.getLine()).length());
+      }
+
+      addDraftBox(cm.side(), CommentInfo.create(
+              path,
+              getStoredSideFromDisplaySide(cm.side()),
+              line,
+              CommentRange.create(fromTo))).setEdit(true);
+      cm.setSelection(cm.getCursor());
+    } else {
+      insertNewDraft(cm.side(), line);
+    }
+  }
+
+  void setUnsaved(DraftBox box, boolean isUnsaved) {
+    if (isUnsaved) {
+      unsavedDrafts.add(box);
+    } else {
+      unsavedDrafts.remove(box);
+    }
+  }
+
+  void saveAllDrafts(CallbackGroup cb) {
+    for (DraftBox box : unsavedDrafts) {
+      box.save(cb);
+    }
+  }
+
+  private CommentGroup group(DisplaySide side, int line) {
+    CommentGroup w = map(side).get(line);
+    if (w != null) {
+      return w;
+    }
+
+    int lineA, lineB;
+    if (line == 0) {
+      lineA = lineB = 0;
+    } else if (side == DisplaySide.A) {
+      lineA = line;
+      lineB = host.lineOnOther(side, line - 1).getLine() + 1;
+    } else {
+      lineA = host.lineOnOther(side, line - 1).getLine() + 1;
+      lineB = line;
+    }
+
+    CommentGroup a = newGroup(DisplaySide.A, lineA);
+    CommentGroup b = newGroup(DisplaySide.B, lineB);
+    CommentGroup.pair(a, b);
+
+    sideA.put(lineA, a);
+    sideB.put(lineB, b);
+
+    if (attached) {
+      a.attachPair(host.diffTable);
+      b.handleRedraw();
+    }
+
+    return side == DisplaySide.A ? a : b;
+  }
+
+  private CommentGroup newGroup(DisplaySide side, int line) {
+    return new CommentGroup(this, host.getCmFromSide(side), line);
+  }
+
+  private SortedMap<Integer, CommentGroup> map(DisplaySide side) {
+    return side == DisplaySide.A ? sideA : sideB;
+  }
+
+  private Side getStoredSideFromDisplaySide(DisplaySide side) {
+    return side == DisplaySide.A && base == null ? Side.PARENT : Side.REVISION;
+  }
+
+  private PatchSet.Id getPatchSetIdFromSide(DisplaySide side) {
+    return side == DisplaySide.A && base != null ? base : revision;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
new file mode 100644
index 0000000..1fe85b3
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentsCollections.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.diff;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.CommentApi;
+import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.JsArray;
+
+import java.util.Collections;
+import java.util.Comparator;
+
+/** Collection of published and draft comments loaded from the server. */
+class CommentsCollections {
+  private String path;
+
+  JsArray<CommentInfo> publishedBase;
+  JsArray<CommentInfo> publishedRevision;
+  JsArray<CommentInfo> draftsBase;
+  JsArray<CommentInfo> draftsRevision;
+
+  void load(PatchSet.Id base, PatchSet.Id revision, String path,
+      CallbackGroup group) {
+    this.path = path;
+
+    if (base != null) {
+      CommentApi.comments(base, group.add(publishedBase()));
+    }
+    CommentApi.comments(revision, group.add(publishedRevision()));
+
+    if (Gerrit.isSignedIn()) {
+      if (base != null) {
+        CommentApi.drafts(base, group.add(draftsBase()));
+      }
+      CommentApi.drafts(revision, group.add(draftsRevision()));
+    }
+  }
+
+  private GerritCallback<NativeMap<JsArray<CommentInfo>>> publishedBase() {
+    return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+      @Override
+      public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+        publishedBase = sort(result.get(path));
+      }
+    };
+  }
+
+  private GerritCallback<NativeMap<JsArray<CommentInfo>>> publishedRevision() {
+    return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+      @Override
+      public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+        publishedRevision = sort(result.get(path));
+      }
+    };
+  }
+
+  private GerritCallback<NativeMap<JsArray<CommentInfo>>> draftsBase() {
+    return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+      @Override
+      public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+        draftsBase = sort(result.get(path));
+      }
+    };
+  }
+
+  private GerritCallback<NativeMap<JsArray<CommentInfo>>> draftsRevision() {
+    return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+      @Override
+      public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+        draftsRevision = sort(result.get(path));
+      }
+    };
+  }
+
+  private JsArray<CommentInfo> sort(JsArray<CommentInfo> in) {
+    if (in != null) {
+      for (CommentInfo c : Natives.asList(in)) {
+        c.path(path);
+      }
+      Collections.sort(Natives.asList(in), new Comparator<CommentInfo>() {
+        @Override
+        public int compare(CommentInfo a, CommentInfo b) {
+          return a.updated().compareTo(b.updated());
+        }
+      });
+    }
+    return in;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java
index 89f1de1..5e88ac9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffApi.java
@@ -23,8 +23,8 @@
 
 public class DiffApi {
   public enum IgnoreWhitespace {
-    NONE, TRAILING, CHANGED, ALL;
-  };
+    NONE, TRAILING, CHANGED, ALL
+  }
 
   public static void list(int id, String base, String revision,
       AsyncCallback<NativeMap<FileInfo>> cb) {
@@ -35,6 +35,15 @@
     api.get(NativeMap.copyKeysIntoChildren("path", cb));
   }
 
+  public static void list(PatchSet.Id id, PatchSet.Id base,
+      AsyncCallback<NativeMap<FileInfo>> cb) {
+    RestApi api = ChangeApi.revision(id).view("files");
+    if (base != null) {
+      api.addParameter("base", base.get());
+    }
+    api.get(NativeMap.copyKeysIntoChildren("path", cb));
+  }
+
   public static DiffApi diff(PatchSet.Id id, String path) {
     return new DiffApi(ChangeApi.revision(id)
         .view("files").id(path)
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
index f833509..c0d0532 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffInfo.java
@@ -93,12 +93,13 @@
   }
 
   public enum IntraLineStatus {
-    OFF, OK, TIMEOUT, FAILURE;
+    OFF, OK, TIMEOUT, FAILURE
   }
 
   public static class FileMeta extends JavaScriptObject {
     public final native String name() /*-{ return this.name; }-*/;
     public final native String content_type() /*-{ return this.content_type; }-*/;
+    public final native int lines() /*-{ return this.lines || 0 }-*/;
 
     protected FileMeta() {
     }
@@ -109,6 +110,7 @@
     public final native JsArrayString a() /*-{ return this.a; }-*/;
     public final native JsArrayString b() /*-{ return this.b; }-*/;
     public final native int skip() /*-{ return this.skip || 0; }-*/;
+    public final native boolean common() /*-{ return this.common || false; }-*/;
 
     public final native JsArray<Span> edit_a() /*-{ return this.edit_a }-*/;
     public final native JsArray<Span> edit_b() /*-{ return this.edit_b }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
index 1e33321..8a64e76 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.client.diff;
 
-import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
@@ -24,6 +23,7 @@
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwt.user.client.ui.Widget;
@@ -39,30 +39,26 @@
   interface DiffTableStyle extends CssResource {
     String fullscreen();
     String intralineBg();
+    String dark();
     String diff();
+    String noIntraline();
     String activeLine();
     String range();
     String rangeHighlight();
-    String showtabs();
+    String showTabs();
+    String showLineNumbers();
+    String columnMargin();
+    String padding();
   }
 
-  @UiField
-  Element cmA;
-
-  @UiField
-  Element cmB;
-
-  @UiField
-  SidePanel sidePanel;
-
-  @UiField
-  Element patchSetNavRow;
-
-  @UiField
-  Element patchSetNavCellA;
-
-  @UiField
-  Element patchSetNavCellB;
+  @UiField Element cmA;
+  @UiField Element cmB;
+  @UiField OverviewBar overview;
+  @UiField Element patchSetNavRow;
+  @UiField Element patchSetNavCellA;
+  @UiField Element patchSetNavCellB;
+  @UiField FlowPanel widgets;
+  @UiField static DiffTableStyle style;
 
   @UiField(provided = true)
   PatchSetSelectBox2 patchSetSelectBoxA;
@@ -70,71 +66,39 @@
   @UiField(provided = true)
   PatchSetSelectBox2 patchSetSelectBoxB;
 
-  @UiField
-  Element fileCommentRow;
+  private SideBySide2 parent;
+  private boolean headerVisible;
 
-  @UiField
-  Element fileCommentCellA;
-
-  @UiField
-  Element fileCommentCellB;
-
-  @UiField(provided = true)
-  FileCommentPanel fileCommentPanelA;
-
-  @UiField(provided = true)
-  FileCommentPanel fileCommentPanelB;
-
-  @UiField
-  static DiffTableStyle style;
-
-  private SideBySide2 host;
-
-  DiffTable(SideBySide2 host, PatchSet.Id base, PatchSet.Id revision, String path) {
+  DiffTable(SideBySide2 parent, PatchSet.Id base, PatchSet.Id revision,
+      String path) {
     patchSetSelectBoxA = new PatchSetSelectBox2(
-        this, DisplaySide.A, revision.getParentKey(), base, path);
+        parent, DisplaySide.A, revision.getParentKey(), base, path);
     patchSetSelectBoxB = new PatchSetSelectBox2(
-        this, DisplaySide.B, revision.getParentKey(), revision, path);
+        parent, DisplaySide.B, revision.getParentKey(), revision, path);
     PatchSetSelectBox2.link(patchSetSelectBoxA, patchSetSelectBoxB);
-    fileCommentPanelA = new FileCommentPanel(host, this, path, DisplaySide.A);
-    fileCommentPanelB = new FileCommentPanel(host, this, path, DisplaySide.B);
+
     initWidget(uiBinder.createAndBindUi(this));
-    this.host = host;
+    this.parent = parent;
+    this.headerVisible = true;
+  }
+
+  boolean isHeaderVisible() {
+    return headerVisible;
   }
 
   void setHeaderVisible(boolean show) {
-    Gerrit.setHeaderVisible(show);
+    headerVisible = show;
     UIObject.setVisible(patchSetNavRow, show);
-    UIObject.setVisible(fileCommentRow, show
-        && (fileCommentPanelA.getBoxCount() > 0
-            || fileCommentPanelB.getBoxCount() > 0));
     if (show) {
-      host.header.removeStyleName(style.fullscreen());
+      parent.header.removeStyleName(style.fullscreen());
     } else {
-      host.header.addStyleName(style.fullscreen());
+      parent.header.addStyleName(style.fullscreen());
     }
-    host.resizeCodeMirror();
-  }
-
-  private FileCommentPanel getPanelFromSide(DisplaySide side) {
-    return side == DisplaySide.A ? fileCommentPanelA : fileCommentPanelB;
-  }
-
-  void createOrEditFileComment(DisplaySide side) {
-    getPanelFromSide(side).createOrEditFileComment();
-    setHeaderVisible(true);
-  }
-
-  void addFileCommentBox(CommentBox box) {
-    getPanelFromSide(box.getSide()).addFileComment(box);
-  }
-
-  void onRemoveDraftBox(DraftBox box) {
-    getPanelFromSide(box.getSide()).onRemoveDraftBox(box);
+    parent.resizeCodeMirror();
   }
 
   int getHeaderHeight() {
-    return fileCommentRow.getOffsetHeight() + patchSetSelectBoxA.getOffsetHeight();
+    return patchSetSelectBoxA.getOffsetHeight();
   }
 
   void setUpPatchSetNav(JsArray<RevisionInfo> list, DiffInfo info) {
@@ -143,6 +107,6 @@
   }
 
   void add(Widget widget) {
-    ((HTMLPanel) getWidget()).add(widget);
+    widgets.add(widget);
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
index 5e280a9..09eacaa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
@@ -20,52 +20,74 @@
   <ui:style type='com.google.gerrit.client.diff.DiffTable.DiffTableStyle'>
     @external .CodeMirror, .CodeMirror-lines, .CodeMirror-selectedtext;
     @external .CodeMirror-linenumber, .CodeMirror-vscrollbar .CodeMirror-scroll;
+    @external .CodeMirror-dialog-bottom;
     @external .cm-keymap-fat-cursor, CodeMirror-cursor;
     @external .cm-searching, .cm-trailingspace, .cm-tab;
 
     .fullscreen {
+      background-color: #f7f7f7;
       border-bottom: 1px solid #ddd;
     }
 
+    .difftable {
+      -webkit-touch-callout: none;
+      -webkit-user-select: none;
+      -khtml-user-select: none;
+      -moz-user-select: none;
+      -ms-user-select: none;
+    }
     .difftable .CodeMirror-lines { padding: 0; }
     .difftable .CodeMirror pre {
       padding: 0;
-      padding-bottom: 0.11em;
       overflow: hidden;
       border-right: 0;
       width: auto;
     }
+
+    /* Preserve space for underscores. If this changes
+     * see ChunkManager.addPadding() and adjust there.
+     */
+    .difftable .CodeMirror pre,
     .difftable .CodeMirror pre span {
-      padding-bottom: 0.11em;
+      padding-bottom: 1px;
     }
-    .contentCell {
-      padding: 0;
-    }
-    .sidePanelCell {
-      padding: 0;
-      width: 10px;
-    }
+
     .table {
       width: 100%;
       table-layout: fixed;
       border-spacing: 0;
     }
+    .table td { padding: 0 }
+    .a, .b { width: 50% }
 
-    .a, .b {
-      padding: 0;
-      width: 50%;
+    .overview {
+      width: 10px;
+      vertical-align: top;
     }
 
-    /* Hide left side scrollbar, right side controls both views. */
-    .a .CodeMirror-scroll { padding-right: 0; }
-    .a .CodeMirror-vscrollbar { display: none !important; }
+    /* Hide scrollbars, OverviewBar controls both views. */
+    .difftable .CodeMirror-scroll { padding-right: 0; }
+    .difftable .CodeMirror-vscrollbar { display: none !important; }
+
+    .showLineNumbers .b { border-left: none; }
+    .b { border-left: 1px solid #ddd; }
 
     .a .diff { background-color: #faa; }
     .b .diff { background-color: #9f9; }
     .a .intralineBg { background-color: #fee; }
     .b .intralineBg { background-color: #dfd; }
+    .noIntraline .a .intralineBg { background-color: #faa; }
+    .noIntraline .b .intralineBg { background-color: #9f9; }
 
-    .fileCommentRow {
+    .dark .a .diff { background-color: #400; }
+    .dark .b .diff { background-color: #444; }
+
+    .dark .a .intralineBg { background-color: #888; }
+    .dark .b .intralineBg { background-color: #bbb; }
+    .dark .noIntraline .a .intralineBg { background-color: #400; }
+    .dark .noIntraline .b .intralineBg { background-color: #444; }
+
+    .patchSetNav {
       background-color: #f7f7f7;
       line-height: 1;
     }
@@ -102,41 +124,52 @@
       text-decoration: underline;
       z-index: 2;
     }
-    .showtabs .cm-tab:before {
+    .difftable .CodeMirror-dialog-bottom {
+      border-top: 0;
+      border-left: 1px solid #000;
+      border-bottom: 1px solid #000;
+      background-color: #f7f7f7;
+      top: 0;
+      right: 0;
+      bottom: auto;
+      left: auto;
+    }
+    .showTabs .cm-tab:before {
       position: absolute;
       content: "\00bb";
       color: #f00;
     }
+    .showLineNumbers .padding {
+      margin-left: 21px;
+      border-left: 2px solid #d64040;
+    }
+    .columnMargin {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      width: 0;
+      border-right: 1px dashed #ffa500;
+      z-index: 2;
+      cursor: text;
+    }
   </ui:style>
   <g:HTMLPanel styleName='{style.difftable}'>
     <table class='{style.table}'>
-      <tr>
-        <td class='{style.contentCell}'>
-          <table class='{style.table}'>
-            <tr ui:field='patchSetNavRow' class='{style.fileCommentRow}'>
-              <td ui:field='patchSetNavCellA'>
-                <d:PatchSetSelectBox2 ui:field='patchSetSelectBoxA' />
-              </td>
-              <td ui:field='patchSetNavCellB'>
-                <d:PatchSetSelectBox2 ui:field='patchSetSelectBoxB' />
-              </td>
-            </tr>
-            <tr ui:field='fileCommentRow' class='{style.fileCommentRow}'>
-              <td ui:field='fileCommentCellA' class='{style.fileCommentCell}'>
-                <d:FileCommentPanel ui:field='fileCommentPanelA' />
-              </td>
-              <td ui:field='fileCommentCellB' class='{style.fileCommentCell}'>
-                <d:FileCommentPanel ui:field='fileCommentPanelB' />
-              </td>
-            </tr>
-            <tr>
-              <td ui:field='cmA' class='{style.a}'></td>
-              <td ui:field='cmB' class='{style.b}'></td>
-            </tr>
-          </table>
+      <tr ui:field='patchSetNavRow' class='{style.patchSetNav}'>
+        <td ui:field='patchSetNavCellA'>
+          <d:PatchSetSelectBox2 ui:field='patchSetSelectBoxA' />
         </td>
-        <td class='{style.sidePanelCell}'><d:SidePanel ui:field='sidePanel'/></td>
+        <td ui:field='patchSetNavCellB'>
+          <d:PatchSetSelectBox2 ui:field='patchSetSelectBoxB' />
+        </td>
+        <td class='{style.overview}' />
+      </tr>
+      <tr>
+        <td ui:field='cmA' class='{style.a}' />
+        <td ui:field='cmB' class='{style.b}' />
+        <td class='{style.overview}'><d:OverviewBar ui:field='overview'/></td>
       </tr>
     </table>
+    <g:FlowPanel ui:field='widgets' visible='false'/>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Direction.java
similarity index 82%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Direction.java
index 4457fb6..9b0b1c4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Direction.java
@@ -12,8 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.client.diff;
 
-public class GroupOptionsInfo {
-  public Boolean visible_to_all;
+/** Direction of traversal in an ordered list. */
+public enum Direction {
+  PREV, NEXT
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DisplaySide.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DisplaySide.java
index c51a673..4dfcd8c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DisplaySide.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DisplaySide.java
@@ -15,6 +15,6 @@
 package com.google.gerrit.client.diff;
 
 /** Enum representing the side on a side-by-side view */
-enum DisplaySide {
-  A, B;
+public enum DisplaySide {
+  A, B
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
index 1daee49..edc79a5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
@@ -17,7 +17,6 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.changes.CommentApi;
 import com.google.gerrit.client.changes.CommentInfo;
-import com.google.gerrit.client.changes.CommentInput;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
@@ -25,8 +24,9 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.BlurEvent;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.DoubleClickEvent;
@@ -35,6 +35,8 @@
 import com.google.gwt.event.dom.client.KeyDownEvent;
 import com.google.gwt.event.dom.client.MouseMoveEvent;
 import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
@@ -47,8 +49,6 @@
 import com.google.gwtexpui.globalkey.client.NpTextArea;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
-import net.codemirror.lib.CodeMirror;
-
 /** An HtmlPanel for displaying and editing a draft */
 class DraftBox extends CommentBox {
   interface Binder extends UiBinder<HTMLPanel, DraftBox> {}
@@ -57,13 +57,15 @@
   private static final int INITIAL_LINES = 5;
   private static final int MAX_LINES = 30;
 
-  private final SideBySide2 parent;
   private final CommentLinkProcessor linkProcessor;
   private final PatchSet.Id psId;
   private CommentInfo comment;
   private PublishedBox replyToBox;
   private Timer expandTimer;
+  private Timer resizeTimer;
+  private int editAreaHeight;
   private boolean autoClosed;
+  private CallbackGroup pendingGroup;
 
   @UiField Widget header;
   @UiField Element summary;
@@ -81,15 +83,12 @@
   @UiField Button discard2;
 
   DraftBox(
-      SideBySide2 sideBySide,
-      CodeMirror cm,
-      DisplaySide side,
+      CommentGroup group,
       CommentLinkProcessor clp,
       PatchSet.Id id,
       CommentInfo info) {
-    super(cm, info, side);
+    super(group, info.range());
 
-    parent = sideBySide;
     linkProcessor = clp;
     psId = id;
     initWidget(uiBinder.createAndBindUi(this));
@@ -115,6 +114,7 @@
         }
       }
     }, ClickEvent.getType());
+
     addDomHandler(new DoubleClickHandler() {
       @Override
       public void onDoubleClick(DoubleClickEvent event) {
@@ -126,12 +126,8 @@
         }
       }
     }, DoubleClickEvent.getType());
-    addDomHandler(new MouseMoveHandler() {
-      @Override
-      public void onMouseMove(MouseMoveEvent event) {
-        resizePaddingWidget();
-      }
-    }, MouseMoveEvent.getType());
+
+    initResizeHandler();
   }
 
   private void set(CommentInfo info) {
@@ -173,7 +169,8 @@
     if (editArea.getVisibleLines() != rows) {
       editArea.setVisibleLines(rows);
     }
-    resizePaddingWidget();
+    editAreaHeight = editArea.getOffsetHeight();
+    getCommentGroup().resize();
   }
 
   boolean isEdit() {
@@ -187,36 +184,44 @@
 
     setRangeHighlight(edit);
     if (edit) {
-      final String msg = comment.message() != null
+      String msg = comment.message() != null
           ? comment.message().trim()
           : "";
       editArea.setValue(msg);
-      editArea.setFocus(true);
       cancel.setVisible(!isNew());
       expandText();
-      if (msg.length() > 0) {
-        Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
-          @Override
-          public boolean execute() {
-            editArea.setCursorPos(msg.length());
-            return false;
+      editAreaHeight = editArea.getOffsetHeight();
+
+      final int len = msg.length();
+      Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+        @Override
+        public void execute() {
+          editArea.setFocus(true);
+          if (len > 0) {
+            editArea.setCursorPos(len);
           }
-        }, 0);
-      }
+        }
+      });
     } else {
       expandTimer.cancel();
+      resizeTimer.cancel();
     }
-    parent.updateUnsaved(this, edit);
-    resizePaddingWidget();
+    getCommentManager().setUnsaved(this, edit);
+    getCommentGroup().resize();
   }
 
-  void registerReplyToBox(PublishedBox box) {
+  PublishedBox getReplyToBox() {
+    return replyToBox;
+  }
+
+  void setReplyToBox(PublishedBox box) {
     replyToBox = box;
   }
 
   @Override
   protected void onUnload() {
     expandTimer.cancel();
+    resizeTimer.cancel();
     super.onUnload();
   }
 
@@ -224,20 +229,19 @@
     if (replyToBox != null) {
       replyToBox.unregisterReplyBox();
     }
-    clearRange();
+
+    getCommentManager().setUnsaved(this, false);
     setRangeHighlight(false);
-    removeFromParent();
-    if (!getCommentInfo().has_line()) {
-      parent.removeFileCommentBox(this);
-      return;
-    }
-    PaddingManager manager = getPaddingManager();
-    manager.remove(this);
-    parent.removeDraft(this, comment.line() - 1);
+    clearRange();
+    getMark().remove();
+    getCommentGroup().remove(this);
     getCm().focus();
-    getSelfWidgetWrapper().getWidget().clear();
-    getGutterWrapper().remove();
-    resizePaddingWidget();
+  }
+
+  private void restoreSelection() {
+    if (getFromTo() != null && comment.in_reply_to() == null) {
+      getCm().setSelection(getFromTo().getFrom(), getFromTo().getTo());
+    }
   }
 
   @UiHandler("message")
@@ -259,43 +263,51 @@
   @UiHandler("save")
   void onSave(ClickEvent e) {
     e.stopPropagation();
-    save(null);
+    CallbackGroup group = new CallbackGroup();
+    save(group);
+    group.done();
   }
 
   void save(CallbackGroup group) {
+    if (pendingGroup != null) {
+      pendingGroup.addListener(group);
+      return;
+    }
+
     String message = editArea.getValue().trim();
     if (message.length() == 0) {
       return;
     }
 
-    CommentInfo original = comment;
-    CommentInput input = CommentInput.create(original);
-    input.setMessage(message);
+    CommentInfo input = CommentInfo.copy(comment);
+    input.message(message);
     enableEdit(false);
 
+    pendingGroup = group;
     GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() {
       @Override
       public void onSuccess(CommentInfo result) {
         enableEdit(true);
+        pendingGroup = null;
         set(result);
         setEdit(false);
         if (autoClosed) {
           setOpen(false);
         }
-        parent.updateUnsaved(DraftBox.this, false);
+        getCommentManager().setUnsaved(DraftBox.this, false);
       }
 
       @Override
       public void onFailure(Throwable e) {
         enableEdit(true);
+        pendingGroup = null;
         super.onFailure(e);
       }
     };
-    if (original.id() == null) {
-      CommentApi.createDraft(psId, input, group == null ? cb : group.add(cb));
+    if (input.id() == null) {
+      CommentApi.createDraft(psId, input, group.add(cb));
     } else {
-      CommentApi.updateDraft(
-          psId, original.id(), input, group == null ? cb : group.add(cb));
+      CommentApi.updateDraft(psId, input.id(), input, group.add(cb));
     }
     getCm().focus();
   }
@@ -312,6 +324,7 @@
     e.stopPropagation();
     if (isNew() && !isDirty()) {
       removeUI();
+      restoreSelection();
     } else {
       setEdit(false);
       if (autoClosed) {
@@ -326,32 +339,39 @@
     e.stopPropagation();
     if (isNew()) {
       removeUI();
+      restoreSelection();
     } else {
       setEdit(false);
+      pendingGroup = new CallbackGroup();
       CommentApi.deleteDraft(psId, comment.id(),
-          new GerritCallback<JavaScriptObject>() {
+          pendingGroup.addFinal(new GerritCallback<JavaScriptObject>() {
         @Override
         public void onSuccess(JavaScriptObject result) {
+          pendingGroup = null;
           removeUI();
         }
-      });
+      }));
     }
   }
 
   @UiHandler("editArea")
   void onKeyDown(KeyDownEvent e) {
+    resizeTimer.cancel();
     if ((e.isControlKeyDown() || e.isMetaKeyDown())
         && !e.isAltKeyDown() && !e.isShiftKeyDown()) {
       switch (e.getNativeKeyCode()) {
         case 's':
         case 'S':
           e.preventDefault();
-          save(null);
+          CallbackGroup group = new CallbackGroup();
+          save(group);
+          group.done();
           return;
       }
     } else if (e.getNativeKeyCode() == KeyCodes.KEY_ESCAPE && !isDirty()) {
       if (isNew()) {
         removeUI();
+        restoreSelection();
         return;
       } else {
         setEdit(false);
@@ -365,6 +385,40 @@
     expandTimer.schedule(250);
   }
 
+  @UiHandler("editArea")
+  void onBlur(BlurEvent e) {
+    resizeTimer.cancel();
+  }
+
+  private void initResizeHandler() {
+    resizeTimer = new Timer() {
+      @Override
+      public void run() {
+        getCommentGroup().resize();
+      }
+    };
+
+    addDomHandler(new MouseMoveHandler() {
+      @Override
+      public void onMouseMove(MouseMoveEvent event) {
+        int h = editArea.getOffsetHeight();
+        if (isEdit() && h != editAreaHeight) {
+          getCommentGroup().resize();
+          resizeTimer.scheduleRepeating(50);
+          editAreaHeight = h;
+        }
+      }
+    }, MouseMoveEvent.getType());
+
+    addDomHandler(new MouseUpHandler() {
+      @Override
+      public void onMouseUp(MouseUpEvent event) {
+        resizeTimer.cancel();
+        getCommentGroup().resize();
+      }
+    }, MouseUpEvent.getType());
+  }
+
   private boolean isNew() {
     return comment.id() == null;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml
index 52ef0ff..0c97e8b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.ui.xml
@@ -49,7 +49,7 @@
         <div ui:field='date' class='{res.style.date}'/>
       </g:HTMLPanel>
       <div ui:field='p_view' aria-hidden='true' style='display: NONE'>
-        <g:HTML ui:field='message' styleName=''/>
+        <g:HTML ui:field='message' styleName='{res.style.message}'/>
         <div style='position: relative'>
           <g:Button ui:field='edit'
               title='Edit this draft comment'
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileCommentPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileCommentPanel.java
deleted file mode 100644
index 37e0178..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileCommentPanel.java
+++ /dev/null
@@ -1,84 +0,0 @@
-//Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.client.diff;
-
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.CommentInfo;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * HTMLPanel to hold file comments.
- * TODO: Need to resize CodeMirror if this is resized since we don't have the
- * system scrollbar.
- */
-class FileCommentPanel extends Composite {
-
-  private SideBySide2 parent;
-  private DiffTable table;
-  private String path;
-  private DisplaySide side;
-  private List<CommentBox> boxes;
-  private FlowPanel body;
-
-  FileCommentPanel(SideBySide2 host, DiffTable table, String path, DisplaySide side) {
-    this.parent = host;
-    this.table = table;
-    this.path = path;
-    this.side = side;
-    boxes = new ArrayList<CommentBox>();
-    initWidget(body = new FlowPanel());
-  }
-
-  void createOrEditFileComment() {
-    if (!Gerrit.isSignedIn()) {
-      Gerrit.doSignIn(parent.getToken());
-      return;
-    }
-    if (boxes.isEmpty()) {
-      CommentInfo info = CommentInfo.createFile(
-          path,
-          parent.getStoredSideFromDisplaySide(side),
-          null,
-          null);
-      addFileComment(parent.addDraftBox(info, side));
-    } else {
-      CommentBox box = boxes.get(boxes.size() - 1);
-      if (box instanceof DraftBox) {
-        ((DraftBox) box).setEdit(true);
-      } else {
-        addFileComment(((PublishedBox) box).addReplyBox());
-      }
-    }
-  }
-
-  int getBoxCount() {
-    return boxes.size();
-  }
-
-  void addFileComment(CommentBox box) {
-    boxes.add(box);
-    body.add(box);
-    table.setHeaderVisible(true);
-  }
-
-  void onRemoveDraftBox(DraftBox box) {
-    boxes.remove(box);
-    table.setHeaderVisible(true);
-  }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
index 477f018..424f3fa 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
@@ -14,18 +14,23 @@
 
 package com.google.gerrit.client.diff;
 
+import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.GitwebLink;
 import com.google.gerrit.client.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
 import com.google.gerrit.client.changes.ReviewInfo;
 import com.google.gerrit.client.changes.Util;
+import com.google.gerrit.client.diff.DiffInfo.Region;
 import com.google.gerrit.client.patches.PatchUtil;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
@@ -33,6 +38,7 @@
 import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -42,17 +48,26 @@
 import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
 import com.google.gwtexpui.globalkey.client.KeyCommandSet;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import com.google.gwtorm.client.KeyUtil;
 
 class Header extends Composite {
   interface Binder extends UiBinder<HTMLPanel, Header> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
+  static {
+    Resources.I.style().ensureInjected();
+  }
+
+  private static enum ReviewedState {
+    AUTO_REVIEW, LOADED;
+  }
 
   @UiField CheckBox reviewed;
+  @UiField Element project;
   @UiField Element filePath;
 
   @UiField Element noDiff;
@@ -60,6 +75,7 @@
   @UiField InlineHyperlink prev;
   @UiField InlineHyperlink up;
   @UiField InlineHyperlink next;
+  @UiField Image preferences;
 
   private final KeyCommandSet keys;
   private final PatchSet.Id base;
@@ -68,6 +84,8 @@
   private boolean hasPrev;
   private boolean hasNext;
   private String nextPath;
+  private PreferencesAction prefsAction;
+  private ReviewedState reviewedState;
 
   Header(KeyCommandSet keys, PatchSet.Id base, PatchSet.Id patchSetId,
       String path) {
@@ -77,21 +95,32 @@
     this.patchSetId = patchSetId;
     this.path = path;
 
-    SafeHtml.setInnerHTML(filePath, formatPath(path));
+    SafeHtml.setInnerHTML(filePath, formatPath(path, null, null));
     up.setTargetHistoryToken(PageLinks.toChange(
         patchSetId.getParentKey(),
         base != null ? String.valueOf(base.get()) : null,
         String.valueOf(patchSetId.get())));
   }
 
-  private static SafeHtml formatPath(String path) {
+  private static SafeHtml formatPath(String path, String project, String commit) {
     SafeHtmlBuilder b = new SafeHtmlBuilder();
     if (Patch.COMMIT_MSG.equals(path)) {
       return b.append(Util.C.commitMessage());
     }
 
+    GitwebLink gw = (project != null && commit != null) ? Gerrit.getGitwebLink() : null;
     int s = path.lastIndexOf('/') + 1;
-    b.append(path.substring(0, s));
+    if (gw != null && s > 0) {
+      String base = path.substring(0, s - 1);
+      b.openAnchor()
+          .setAttribute("href", gw.toFile(project, commit, base))
+          .setAttribute("title", gw.getLinkName())
+          .append(base)
+          .closeAnchor()
+          .append('/');
+    } else {
+      b.append(path.substring(0, s));
+    }
     b.openElement("b");
     b.append(path.substring(s));
     b.closeElement("b");
@@ -100,11 +129,9 @@
 
   @Override
   protected void onLoad() {
-    ChangeApi.revision(patchSetId).view("files").get(
-        new GerritCallback<NativeMap<FileInfo>>() {
+    DiffApi.list(patchSetId, base, new GerritCallback<NativeMap<FileInfo>>() {
       @Override
       public void onSuccess(NativeMap<FileInfo> result) {
-        result.copyKeysIntoChildren("path");
         JsArray<FileInfo> files = result.values();
         FileInfo.sortFileInfoByPath(files);
         int index = 0; // TODO: Maybe use patchIndex.
@@ -117,9 +144,13 @@
         FileInfo nextInfo = index == files.length() - 1
             ? null
             : files.get(index + 1);
-        setupNav(prev, '[', PatchUtil.C.previousFileHelp(),
+        KeyCommand p = setupNav(prev, '[', PatchUtil.C.previousFileHelp(),
             index == 0 ? null : files.get(index - 1));
-        setupNav(next, ']', PatchUtil.C.nextFileHelp(), nextInfo);
+        KeyCommand n = setupNav(next, ']', PatchUtil.C.nextFileHelp(),
+            nextInfo);
+        if (p != null && n != null) {
+          keys.pair(p, n);
+        }
         nextPath = nextInfo != null ? nextInfo.path() : null;
       }
     });
@@ -130,12 +161,12 @@
         .get(new AsyncCallback<JsArrayString>() {
             @Override
             public void onSuccess(JsArrayString result) {
-              for (int i = 0; i < result.length(); i++) {
-                if (path.equals(result.get(i))) {
-                  reviewed.setValue(true, false);
-                  break;
-                }
+              boolean b = Natives.asList(result).contains(path);
+              reviewed.setValue(b, false);
+              if (!b && reviewedState == ReviewedState.AUTO_REVIEW) {
+                postAutoReviewed();
               }
+              reviewedState = ReviewedState.LOADED;
             }
 
             @Override
@@ -145,76 +176,160 @@
     }
   }
 
-  void setReviewed(boolean r) {
-    reviewed.setValue(r, true);
+  void autoReview() {
+    if (reviewedState == ReviewedState.LOADED && !reviewed.getValue()) {
+      postAutoReviewed();
+    } else {
+      reviewedState = ReviewedState.AUTO_REVIEW;
+    }
   }
 
-  boolean isReviewed() {
-    return reviewed.getValue();
+  void setChangeInfo(ChangeInfo info) {
+    GitwebLink gw = Gerrit.getGitwebLink();
+    if (gw != null) {
+      for (RevisionInfo rev : Natives.asList(info.revisions().values())) {
+        if (rev._number() == patchSetId.get()) {
+          String c = rev.name();
+          SafeHtml.setInnerHTML(filePath, formatPath(path, info.project(), c));
+          SafeHtml.setInnerHTML(project, new SafeHtmlBuilder()
+              .openAnchor()
+              .setAttribute("href", gw.toFile(info.project(), c, ""))
+              .setAttribute("title", gw.getLinkName())
+              .append(info.project())
+              .closeAnchor());
+          return;
+        }
+      }
+    }
+
+    project.setInnerText(info.project());
+  }
+
+  void init(PreferencesAction pa) {
+    prefsAction = pa;
+    prefsAction.setPartner(preferences);
   }
 
   @UiHandler("reviewed")
   void onValueChange(ValueChangeEvent<Boolean> event) {
-    RestApi api = ChangeApi.revision(patchSetId)
+    if (event.getValue()) {
+      reviewed().put(CallbackGroup.<ReviewInfo> emptyCallback());
+    } else {
+      reviewed().delete(CallbackGroup.<ReviewInfo> emptyCallback());
+    }
+  }
+
+  private void postAutoReviewed() {
+    reviewed().background().put(new AsyncCallback<ReviewInfo>() {
+        @Override
+        public void onSuccess(ReviewInfo result) {
+          reviewed.setValue(true, false);
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+        }
+      });
+  }
+
+  private RestApi reviewed() {
+    return ChangeApi.revision(patchSetId)
         .view("files")
         .id(path)
         .view("reviewed");
-    if (event.getValue()) {
-      api.put(CallbackGroup.<ReviewInfo>emptyCallback());
-    } else {
-      api.delete(CallbackGroup.<ReviewInfo>emptyCallback());
-    }
+  }
+
+  @UiHandler("preferences")
+  void onPreferences(ClickEvent e) {
+    prefsAction.show();
   }
 
   private String url(FileInfo info) {
-    Change.Id c = patchSetId.getParentKey();
-    StringBuilder p = new StringBuilder();
-    p.append("/c/").append(c).append('/');
-    if (base != null) {
-      p.append(base.get()).append("..");
-    }
-    p.append(patchSetId.get()).append('/').append(KeyUtil.encode(info.path()));
-    p.append(info.binary() ? ",unified" : ",cm");
-    return p.toString();
+    return info.binary()
+      ? Dispatcher.toUnified(base, patchSetId, info.path())
+      : Dispatcher.toSideBySide(base, patchSetId, info.path());
   }
 
-  private void setupNav(InlineHyperlink link, char key, String help, FileInfo info) {
+  private KeyCommand setupNav(InlineHyperlink link, char key, String help, FileInfo info) {
     if (info != null) {
       final String url = url(info);
       link.setTargetHistoryToken(url);
       link.setTitle(PatchUtil.M.fileNameWithShortcutKey(
           FileInfo.getFileName(info.path()),
           Character.toString(key)));
-      keys.add(new KeyCommand(0, key, help) {
+      KeyCommand k = new KeyCommand(0, key, help) {
         @Override
         public void onKeyPress(KeyPressEvent event) {
           Gerrit.display(url);
         }
-      });
+      };
+      keys.add(k);
       if (link == prev) {
         hasPrev = true;
       } else {
         hasNext = true;
       }
+      return k;
     } else {
       link.getElement().getStyle().setVisibility(Visibility.HIDDEN);
       keys.add(new UpToChangeCommand2(patchSetId, 0, key));
+      return null;
     }
   }
 
-  boolean hasPrev() {
-    return hasPrev;
+  Runnable toggleReviewed() {
+    return new Runnable() {
+      public void run() {
+        reviewed.setValue(!reviewed.getValue(), true);
+      }
+    };
   }
 
-  boolean hasNext() {
-    return hasNext;
+  Runnable navigate(Direction dir) {
+    switch (dir) {
+      case PREV:
+        return new Runnable() {
+          @Override
+          public void run() {
+            (hasPrev ? prev : up).go();
+          }
+        };
+      case NEXT:
+        return new Runnable() {
+          @Override
+          public void run() {
+            (hasNext ? next : up).go();
+          }
+        };
+      default:
+        return new Runnable() {
+          @Override
+          public void run() {
+          }
+        };
+    }
+  }
+
+  Runnable reviewedAndNext() {
+    return new Runnable() {
+      @Override
+      public void run() {
+        if (Gerrit.isSignedIn()) {
+          reviewed.setValue(true, true);
+        }
+        navigate(Direction.NEXT).run();
+      }
+    };
   }
 
   String getNextPath() {
     return nextPath;
   }
 
-  void removeNoDiff() {
-    noDiff.removeFromParent();
+  void setNoDiff(DiffInfo diff) {
+    JsArray<Region> regions = diff.content();
+    boolean b = regions.length() == 0
+        || (regions.length() == 1 && regions.get(0).ab() != null);
+    UIObject.setVisible(noDiff, b);
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
index 18e7d97..7d1f1e5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.ui.xml
@@ -18,28 +18,37 @@
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'
     xmlns:x='urn:import:com.google.gerrit.client.ui'>
+  <ui:with field='res' type='com.google.gerrit.client.diff.Resources'/>
   <ui:style>
   .header {
     position: relative;
-    max-width: 1484px;
+    height: 16px;
+    line-height: 16px;
   }
   .reviewed input {
     margin: 0;
     padding: 0;
-    vertical-align: bottom;
+    vertical-align: middle;
   }
   .path {
+    white-space: nowrap;
   }
   .navigation {
     position: absolute;
     top: 0;
-    right: 15px;
-    font-family: Arial Unicode MS, sans-serif;
+    right: 10px;
+    height: 16px;
+    line-height: 16px;
   }
   .nodiff {
-    position: absolute;
-    left: literal("calc(50% - 30px)");
+    white-space: nowrap;
     color: #B00000;
+    vertical-align: top;
+    font-weight: bold;
+    margin-right: 1em;
+  }
+  .preferences {
+    cursor: pointer;
   }
   </ui:style>
   <g:HTMLPanel styleName='{style.header}'>
@@ -48,19 +57,22 @@
         title='Mark file as reviewed (Shortcut: r)'>
       <ui:attribute name='title'/>
     </g:CheckBox>
-    <span ui:field='filePath' class='{style.path}'/>
-
-    <span ui:field='noDiff' class='{style.nodiff}'>
-      <ui:msg>No Differences</ui:msg>
-    </span>
-
+    <span class='{style.path}'><span ui:field='project'/> / <span ui:field='filePath'/></span>
     <div class='{style.navigation}'>
-      <x:InlineHyperlink ui:field='prev'>&#x21e6;</x:InlineHyperlink>
-      <x:InlineHyperlink ui:field='up' title='Up to change (Shortcut: u)'>
+      <span ui:field='noDiff' class='{style.nodiff}'><ui:msg>No Differences</ui:msg></span>
+      <x:InlineHyperlink ui:field='prev' styleName='{res.style.go_prev}'/>
+      <x:InlineHyperlink ui:field='up'
+          styleName='{res.style.go_up}'
+          title='Up to change (Shortcut: u)'>
         <ui:attribute name='title'/>
-        &#x21e7;
       </x:InlineHyperlink>
-      <x:InlineHyperlink ui:field='next'>&#x21e8;</x:InlineHyperlink>
+      <x:InlineHyperlink ui:field='next' styleName='{res.style.go_next}'/>
+      <g:Image ui:field='preferences'
+           styleName='{style.preferences}'
+           resource='{res.gear}'
+           title='Diff preferences (Shortcut: ,)'>
+         <ui:attribute name='title'/>
+      </g:Image>
     </div>
   </g:HTMLPanel>
-</ui:UiBinder>
\ No newline at end of file
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/InsertCommentBubble.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/InsertCommentBubble.java
new file mode 100644
index 0000000..7287a65
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/InsertCommentBubble.java
@@ -0,0 +1,62 @@
+//Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.diff;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Image;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.Rect;
+
+/** Bubble displayed near a selected region to create a comment. */
+class InsertCommentBubble extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, InsertCommentBubble> {}
+  private static final Binder uiBinder = GWT.create(Binder.class);
+
+  @UiField Image icon;
+
+  InsertCommentBubble(
+      final CommentManager commentManager,
+      final CodeMirror cm) {
+    initWidget(uiBinder.createAndBindUi(this));
+    addDomHandler(new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        setVisible(false);
+        commentManager.insertNewDraft(cm).run();
+      }
+    }, ClickEvent.getType());
+  }
+
+  void position(Rect r) {
+    Style s = getElement().getStyle();
+    int top = (int) (r.top() - (getOffsetHeight() - 8));
+    if (top < 0) {
+      s.setTop(-3, Unit.PX);
+      s.setLeft(r.right() + 2, Unit.PX);
+    } else {
+      s.setTop(top, Unit.PX);
+      s.setLeft((int) (r.right() - 14), Unit.PX);
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/InsertCommentBubble.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/InsertCommentBubble.ui.xml
new file mode 100644
index 0000000..d7c8fc9
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/InsertCommentBubble.ui.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<ui:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:with field='res' type='com.google.gerrit.client.GerritResources'/>
+  <ui:style>
+    .bubble {
+      z-index: 150;
+      white-space: nowrap;
+      line-height: 16px;
+      cursor: pointer;
+    }
+    .message {
+      background: #fff1a8;
+      padding-left: 5px;
+      padding-right: 5px;
+      border-radius: 5px;
+      border: 1px solid #aaa;
+      font-family: sans-serif;
+      font-size: smaller;
+      font-style: italic;
+      vertical-align: top;
+    }
+    .message b {
+      vertical-align: top;
+    }
+  </ui:style>
+  <g:HTMLPanel styleName='{style.bubble}'>
+    <g:Image ui:field='icon'
+        styleName=''
+        resource='{res.draftComments}'
+        title='Create a new inline comment'>
+      <ui:attribute name='title'/>
+    </g:Image><span class='{style.message}'><ui:msg>press <b>c</b> to comment</ui:msg></span>
+  </g:HTMLPanel>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java
index 7a5ce5d..6c7423c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/LineMapper.java
@@ -26,8 +26,14 @@
   private List<LineGap> lineMapBtoA;
 
   LineMapper() {
-    lineMapAtoB = new ArrayList<LineGap>();
-    lineMapBtoA = new ArrayList<LineGap>();
+    reset();
+  }
+
+  void reset() {
+    lineA = 0;
+    lineB = 0;
+    lineMapAtoB = new ArrayList<>();
+    lineMapBtoA = new ArrayList<>();
   }
 
   int getLineA() {
@@ -43,6 +49,15 @@
     lineB += numLines;
   }
 
+  void appendReplace(int aLen, int bLen) {
+    appendCommon(Math.min(aLen, bLen));
+    if (aLen < bLen) { // Edit with insertion
+      appendInsert(bLen - aLen);
+    } else if (aLen > bLen) { // Edit with deletion
+      appendDelete(aLen - bLen);
+    }
+  }
+
   void appendInsert(int numLines) {
     int origLineB = lineB;
     lineB += numLines;
@@ -97,7 +112,7 @@
    *      ...
    */
   LineOnOtherInfo lineOnOther(DisplaySide mySide, int line) {
-    List<LineGap> lineGaps = mySide == DisplaySide.A ? lineMapAtoB : lineMapBtoA;
+    List<LineGap> lineGaps = gapList(mySide);
     // Create a dummy LineGap for the search.
     int ret = Collections.binarySearch(lineGaps, new LineGap(line));
     if (ret == -1) {
@@ -115,6 +130,38 @@
     }
   }
 
+  AlignedPair align(DisplaySide mySide, int line) {
+    List<LineGap> gaps = gapList(mySide);
+    int idx = Collections.binarySearch(gaps, new LineGap(line));
+    if (idx == -1) {
+      return new AlignedPair(line, line);
+    }
+
+    LineGap g = gaps.get(0 <= idx ? idx : -idx - 2);
+    if (g.start <= line && line <= g.end && g.end != -1) {
+      if (0 < g.start) {
+        // Line falls within this gap, use alignment before.
+        return new AlignedPair(g.start - 1, g.end + g.delta);
+      }
+      return new AlignedPair(g.end, g.end + g.delta + 1);
+    }
+    return new AlignedPair(line, line + g.delta);
+  }
+
+  private List<LineGap> gapList(DisplaySide mySide) {
+    return mySide == DisplaySide.A ? lineMapAtoB : lineMapBtoA;
+  }
+
+  static class AlignedPair {
+    final int src;
+    final int dst;
+
+    AlignedPair(int s, int d) {
+      src = s;
+      dst = d;
+    }
+  }
+
   /**
    * @field line The line number on the other side.
    * @field aligned Whether the two lines are at the same height when displayed.
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java
new file mode 100644
index 0000000..b9e6375
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java
@@ -0,0 +1,266 @@
+//Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.diff;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+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.uibinder.client.UiHandler;
+import com.google.gwt.user.client.DOM;
+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.Widget;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.LineCharacter;
+import net.codemirror.lib.ScrollInfo;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Displays overview of all edits and comments in this file. */
+class OverviewBar extends Composite implements ClickHandler {
+  interface Binder extends UiBinder<HTMLPanel, OverviewBar> {}
+  private static final Binder uiBinder = GWT.create(Binder.class);
+
+  interface Style extends CssResource {
+    String gutter();
+    String halfGutter();
+    String comment();
+    String draft();
+    String insert();
+    String delete();
+    String viewportDrag();
+  }
+
+  enum MarkType {
+    COMMENT, DRAFT, INSERT, DELETE, EDIT
+  }
+
+  @UiField Style style;
+  @UiField Label viewport;
+
+  private final List<MarkHandle> diff;
+  private final Set<MarkHandle> comments;
+  private CodeMirror cmB;
+
+  private boolean dragging;
+  private int startY;
+  private double ratio;
+
+  OverviewBar() {
+    initWidget(uiBinder.createAndBindUi(this));
+    diff = new ArrayList<>();
+    comments = new HashSet<>();
+    addDomHandler(this, ClickEvent.getType());
+  }
+
+  void init(CodeMirror cmB) {
+    this.cmB = cmB;
+  }
+
+  void refresh() {
+    update(cmB.getScrollInfo());
+  }
+
+  void update(ScrollInfo si) {
+    double viewHeight = si.getClientHeight();
+    double r = ratio(si);
+
+    com.google.gwt.dom.client.Style style = viewport.getElement().getStyle();
+    style.setTop(si.getTop() * r, Unit.PX);
+    style.setHeight(Math.max(10, viewHeight * r), Unit.PX);
+    getElement().getStyle().setHeight(viewHeight, Unit.PX);
+    for (MarkHandle info : diff) {
+      info.position(r);
+    }
+    for (MarkHandle info : comments) {
+      info.position(r);
+    }
+  }
+
+  @Override
+  protected void onUnload() {
+    super.onUnload();
+    if (dragging) {
+      DOM.releaseCapture(viewport.getElement());
+    }
+  }
+
+  @Override
+  public void onClick(ClickEvent e) {
+    if (e.getY() < viewport.getElement().getOffsetTop()) {
+      CodeMirror.handleVimKey(cmB, "<PageUp>");
+    } else {
+      CodeMirror.handleVimKey(cmB, "<PageDown>");
+    }
+    cmB.focus();
+  }
+
+  @UiHandler("viewport")
+  void onMouseDown(MouseDownEvent e) {
+    if (cmB != null) {
+      dragging = true;
+      ratio = ratio(cmB.getScrollInfo());
+      startY = e.getY();
+      viewport.addStyleName(style.viewportDrag());
+      DOM.setCapture(viewport.getElement());
+      e.preventDefault();
+      e.stopPropagation();
+    }
+  }
+
+  @UiHandler("viewport")
+  void onMouseMove(MouseMoveEvent e) {
+    if (dragging) {
+      int y = e.getRelativeY(getElement()) - startY;
+      cmB.scrollToY(Math.max(0, y / ratio));
+      e.preventDefault();
+      e.stopPropagation();
+    }
+  }
+
+  @UiHandler("viewport")
+  void onMouseUp(MouseUpEvent e) {
+    if (dragging) {
+      dragging = false;
+      DOM.releaseCapture(viewport.getElement());
+      viewport.removeStyleName(style.viewportDrag());
+      e.preventDefault();
+      e.stopPropagation();
+    }
+  }
+
+  private double ratio(ScrollInfo si) {
+    double barHeight = si.getClientHeight();
+    double contentHeight = si.getHeight();
+    return barHeight / contentHeight;
+  }
+
+  MarkHandle add(CodeMirror cm, int line, int height, MarkType type) {
+    MarkHandle mark = new MarkHandle(cm, line, height);
+    switch (type) {
+      case COMMENT:
+        mark.addStyleName(style.comment());
+        comments.add(mark);
+        break;
+      case DRAFT:
+        mark.addStyleName(style.draft());
+        mark.getElement().setInnerText("*");
+        comments.add(mark);
+        break;
+      case INSERT:
+        mark.addStyleName(style.insert());
+        diff.add(mark);
+        break;
+      case DELETE:
+        mark.addStyleName(style.delete());
+        diff.add(mark);
+        break;
+      case EDIT:
+        mark.edit = DOM.createDiv();
+        mark.edit.setClassName(style.halfGutter());
+        mark.getElement().appendChild(mark.edit);
+        mark.addStyleName(style.insert());
+        diff.add(mark);
+        break;
+    }
+    if (cmB != null) {
+      mark.position(ratio(cmB.getScrollInfo()));
+    }
+    ((HTMLPanel) getWidget()).add(mark);
+    return mark;
+  }
+
+  void clearDiffMarkers() {
+    for (MarkHandle mark : diff) {
+      mark.removeFromParent();
+    }
+    diff.clear();
+  }
+
+  class MarkHandle extends Widget implements ClickHandler {
+    private static final int MIN_HEIGHT = 3;
+
+    private final CodeMirror cm;
+    private final int line;
+    private final int height;
+    private Element edit;
+
+    MarkHandle(CodeMirror cm, int line, int height) {
+      this.cm = cm;
+      this.line = line;
+      this.height = height;
+
+      setElement((Element)(DOM.createDiv()));
+      setStyleName(style.gutter());
+      addDomHandler(this, ClickEvent.getType());
+    }
+
+    void position(double ratio) {
+      double y = cm.heightAtLine(line, "local");
+      getElement().getStyle().setTop(y * ratio, Unit.PX);
+      if (height > 1) {
+        double e = cm.heightAtLine(line + height, "local");
+        double h = Math.max(MIN_HEIGHT, (e - y) * ratio);
+        getElement().getStyle().setHeight(h, Unit.PX);
+        if (edit != null) {
+          edit.getStyle().setHeight(h, Unit.PX);
+        }
+      }
+    }
+
+    @Override
+    public void onClick(ClickEvent e) {
+      if (height == 1 || !visible()) {
+        e.stopPropagation();
+
+        double y = cm.heightAtLine(line, "local");
+        double viewport = cm.getScrollInfo().getClientHeight();
+        cm.setCursor(LineCharacter.create(line));
+        cm.scrollToY(y - 0.5 * viewport);
+        cm.focus();
+      }
+    }
+
+    private boolean visible() {
+      int markT = getElement().getOffsetTop();
+      int markE = markT + getElement().getOffsetHeight();
+
+      int viewT = viewport.getElement().getOffsetTop();
+      int viewE = viewT + viewport.getElement().getOffsetHeight();
+
+      return (viewT <= markT && markT < viewE) // mark top within viewport
+          || (viewT <= markE && markE < viewE) // mark end within viewport
+          || (markT <= viewT && viewE <= markE); // mark contains viewport
+    }
+
+    void remove() {
+      removeFromParent();
+      comments.remove(this);
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml
similarity index 63%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.ui.xml
rename to gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml
index e2b56f3f..a56c3da 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml
@@ -16,23 +16,28 @@
 -->
 <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'>
-  <ui:style type='com.google.gerrit.client.diff.SidePanel.SidePanelStyle'>
+  <ui:style type='com.google.gerrit.client.diff.OverviewBar.Style'>
+    .overview {
+      position: relative;
+    }
     .gutter {
       cursor: pointer;
-      position: fixed;
+      position: absolute;
       height: 3px;
-      width: 10px;
-      border: 1px solid black;
+      width: 6px;
+      border: 1px solid grey;
+      margin-left: 1px;
     }
     .halfGutter {
       cursor: pointer;
-      position: fixed;
+      position: absolute;
       height: 3px;
-      width: 5px;
+      width: 3px;
       background-color: #faa;
     }
     .comment, .draft {
-      background-color: #e5ecf9;
+      background-color: #fcfa96;
+      z-index: 2;
     }
     .draft {
       text-align: center;
@@ -47,6 +52,20 @@
     .insert {
       background-color: #9f9;
     }
+    .viewport {
+      position: absolute;
+      background-color: rgba(128, 128, 128, 0.50);
+      border: 1px solid #444;
+      width: 8px;
+      cursor: default;
+      z-index: 3;
+      border-radius: 4px;
+    }
+    .viewportDrag {
+      cursor: move;
+    }
   </ui:style>
-  <g:HTMLPanel/>
-</ui:UiBinder>
\ No newline at end of file
+  <g:HTMLPanel styleName='{style.overview}'>
+    <g:Label ui:field='viewport' styleName='{style.viewport}'/>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PaddingManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PaddingManager.java
deleted file mode 100644
index dc212e5..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PaddingManager.java
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.client.diff;
-
-import com.google.gwt.dom.client.Element;
-
-import net.codemirror.lib.LineWidget;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Manages paddings for CommentBoxes. Each line that may need to be padded owns
- * a PaddingManager instance, which maintains a padding widget whose height
- * changes as necessary. PaddingManager calculates padding by taking the
- * difference of the sum of CommentBox heights on the two sides.
- *
- * Note that in the case of an insertion or deletion gap, A PaddingManager
- * can map to a list of managers on the other side. The padding needed is then
- * calculated from the sum of all their heights.
- *
- * TODO: Let PaddingManager also take care of the paddings introduced by
- * insertions and deletions.
- */
-class PaddingManager {
-  private List<CommentBox> comments;
-  private PaddingWidgetWrapper wrapper;
-  private List<PaddingManager> others;
-
-  PaddingManager(PaddingWidgetWrapper padding) {
-    comments = new ArrayList<CommentBox>();
-    others = new ArrayList<PaddingManager>();
-    this.wrapper = padding;
-  }
-
-  static void link(PaddingManager a, PaddingManager b) {
-    if (!a.others.contains(b)) {
-      a.others.add(b);
-    }
-    if (!b.others.contains(a)) {
-      b.others.add(a);
-    }
-  }
-
-  private int getMyTotalHeight() {
-    int total = 0;
-    for (CommentBox box : comments) {
-      /**
-       * This gets the height of CM's line widget div, taking the margin and
-       * the horizontal scrollbar into account.
-       */
-      total += box.getSelfWidgetWrapper().getElement().getParentElement().getOffsetHeight();
-    }
-    return total;
-  }
-
-  /**
-   * If this instance is on the insertion side, its counterpart on the other
-   * side will map to a group of PaddingManagers on this side, so we calculate
-   * the group's total height instead of an individual one's.
-   */
-  private int getGroupTotalHeight() {
-    if (others.size() > 1) {
-      return getMyTotalHeight();
-    } else {
-      return others.get(0).getOthersTotalHeight();
-    }
-  }
-
-  private int getOthersTotalHeight() {
-    int total = 0;
-    for (PaddingManager manager : others) {
-      total += manager.getMyTotalHeight();
-    }
-    return total;
-  }
-
-  private void setPaddingHeight(int height) {
-    SideBySide2.setHeightInPx(wrapper.element, height);
-    wrapper.widget.changed();
-  }
-
-  void resizePaddingWidget() {
-    if (others.isEmpty()) {
-      return;
-    }
-    int myHeight = getGroupTotalHeight();
-    int othersHeight = getOthersTotalHeight();
-    int paddingNeeded = othersHeight - myHeight;
-    if (paddingNeeded < 0) {
-      for (PaddingManager manager : others.get(0).others) {
-        manager.setPaddingHeight(0);
-      }
-      others.get(others.size() - 1).setPaddingHeight(-paddingNeeded);
-    } else {
-      setPaddingHeight(paddingNeeded);
-      for (PaddingManager other : others) {
-        other.setPaddingHeight(0);
-      }
-    }
-  }
-
-  /** This is unused now because threading info is ignored. */
-  int getReplyIndex(CommentBox box) {
-    return comments.indexOf(box) + 1;
-  }
-
-  int getCurrentCount() {
-    return comments.size();
-  }
-
-  void insert(CommentBox box, int index) {
-    comments.add(index, box);
-  }
-
-  void remove(CommentBox box) {
-    comments.remove(box);
-  }
-
-  static class PaddingWidgetWrapper {
-    private LineWidget widget;
-    private Element element;
-
-    PaddingWidgetWrapper(LineWidget w, Element e) {
-      widget = w;
-      element = e;
-    }
-
-    LineWidget getWidget() {
-      return widget;
-    }
-
-    Element getElement() {
-      return element;
-    }
-  }
-
-  static class LinePaddingWidgetWrapper extends PaddingWidgetWrapper {
-    private int chunkLength;
-    private int otherLine;
-
-    LinePaddingWidgetWrapper(PaddingWidgetWrapper pair, int otherLine, int chunkLength) {
-      super(pair.widget, pair.element);
-
-      this.otherLine = otherLine;
-      this.chunkLength = chunkLength;
-    }
-
-    int getChunkLength() {
-      return chunkLength;
-    }
-
-    int getOtherLine() {
-      return otherLine;
-    }
-  }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java
index 4a09615..7154c9f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox2.java
@@ -49,7 +49,7 @@
   @UiField HTMLPanel linkPanel;
   @UiField BoxStyle style;
 
-  private DiffTable table;
+  private SideBySide2 parent;
   private DisplaySide side;
   private boolean sideA;
   private String path;
@@ -58,12 +58,16 @@
   private PatchSet.Id idActive;
   private PatchSetSelectBox2 other;
 
-  PatchSetSelectBox2(DiffTable table, final DisplaySide side,
-      final Change.Id changeId, final PatchSet.Id revision, String path) {
+  PatchSetSelectBox2(SideBySide2 parent,
+      DisplaySide side,
+      Change.Id changeId,
+      PatchSet.Id revision,
+      String path) {
     initWidget(uiBinder.createAndBindUi(this));
     icon.setTitle(PatchUtil.C.addFileCommentToolTip());
     icon.addStyleName(Gerrit.RESOURCES.css().link());
-    this.table = table;
+
+    this.parent = parent;
     this.side = side;
     this.sideA = side == DisplaySide.A;
     this.changeId = changeId;
@@ -108,7 +112,7 @@
     if (sideA) {
       assert other.idActive != null;
     }
-    return new InlineHyperlink(label, Dispatcher.toPatchSideBySide2(
+    return new InlineHyperlink(label, Dispatcher.toSideBySide(
         sideA ? id : other.idActive,
         sideA ? other.idActive : id,
         path));
@@ -127,6 +131,7 @@
 
   @UiHandler("icon")
   void onIconClick(ClickEvent e) {
-    table.createOrEditFileComment(side);
+    parent.getCmFromSide(side).scrollToY(0);
+    parent.getCommentManager().insertNewDraft(side, 0);
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java
new file mode 100644
index 0000000..4cbe1be
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesAction.java
@@ -0,0 +1,84 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.diff;
+
+import com.google.gerrit.client.account.DiffPreferences;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.gwt.user.client.ui.Widget;
+
+class PreferencesAction {
+  private final SideBySide2 view;
+  private final DiffPreferences prefs;
+  private PopupPanel popup;
+  private PreferencesBox current;
+  private Widget partner;
+
+  PreferencesAction(SideBySide2 view, DiffPreferences prefs) {
+    this.view = view;
+    this.prefs = prefs;
+  }
+
+  void update() {
+    if (current != null) {
+      current.set(prefs);
+    }
+  }
+
+  void show() {
+    if (popup != null) {
+      // Already open? Close the dialog.
+      hide();
+      return;
+    }
+
+    current = new PreferencesBox(view);
+    current.set(prefs);
+
+    popup = new PopupPanel(true, false);
+    popup.setStyleName(current.style.dialog());
+    popup.add(current);
+    popup.addAutoHidePartner(partner.getElement());
+    popup.addCloseHandler(new CloseHandler<PopupPanel>() {
+      @Override
+      public void onClose(CloseEvent<PopupPanel> event) {
+        view.getCmFromSide(DisplaySide.B).focus();
+        popup = null;
+        current = null;
+      }
+    });
+    popup.setPopupPositionAndShow(new PositionCallback() {
+      @Override
+      public void setPosition(int offsetWidth, int offsetHeight) {
+        popup.setPopupPosition(300, 120);
+      }
+    });
+    current.setFocus(true);
+  }
+
+  void hide() {
+    if (popup != null) {
+      popup.hide();
+      popup = null;
+      current = null;
+    }
+  }
+
+  void setPartner(Widget w) {
+    partner = w;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
new file mode 100644
index 0000000..ed7c3b6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
@@ -0,0 +1,411 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.diff;
+
+import static com.google.gerrit.reviewdb.client.AccountDiffPreference.DEFAULT_CONTEXT;
+import static com.google.gerrit.reviewdb.client.AccountDiffPreference.WHOLE_FILE_CONTEXT;
+import static com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace.IGNORE_ALL_SPACE;
+import static com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace.IGNORE_NONE;
+import static com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace.IGNORE_SPACE_AT_EOL;
+import static com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace.IGNORE_SPACE_CHANGE;
+import static com.google.gwt.event.dom.client.KeyCodes.KEY_ESCAPE;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.account.AccountApi;
+import com.google.gerrit.client.account.DiffPreferences;
+import com.google.gerrit.client.patches.PatchUtil;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.NpIntTextBox;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+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.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.ToggleButton;
+
+/** Displays current diff preferences. */
+class PreferencesBox extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, PreferencesBox> {}
+  private static final Binder uiBinder = GWT.create(Binder.class);
+
+  interface Style extends CssResource {
+    String dialog();
+  }
+
+  private final SideBySide2 view;
+  private DiffPreferences prefs;
+  private int contextLastValue;
+  private Timer updateContextTimer;
+
+  @UiField Style style;
+  @UiField Anchor close;
+  @UiField ListBox ignoreWhitespace;
+  @UiField NpIntTextBox tabWidth;
+  @UiField NpIntTextBox lineLength;
+  @UiField NpIntTextBox context;
+  @UiField CheckBox contextEntireFile;
+  @UiField ToggleButton intralineDifference;
+  @UiField ToggleButton syntaxHighlighting;
+  @UiField ToggleButton whitespaceErrors;
+  @UiField ToggleButton showTabs;
+  @UiField ToggleButton lineNumbers;
+  @UiField ToggleButton topMenu;
+  @UiField ToggleButton manualReview;
+  @UiField ToggleButton expandAllComments;
+  @UiField ToggleButton renderEntireFile;
+  @UiField ListBox theme;
+  @UiField Button apply;
+  @UiField Button save;
+
+  PreferencesBox(SideBySide2 view) {
+    this.view = view;
+
+    initWidget(uiBinder.createAndBindUi(this));
+    initIgnoreWhitespace();
+    initTheme();
+  }
+
+  @Override
+  public void onLoad() {
+    super.onLoad();
+
+    save.setVisible(Gerrit.isSignedIn());
+    addDomHandler(new KeyDownHandler() {
+      @Override
+      public void onKeyDown(KeyDownEvent event) {
+        if (event.getNativeKeyCode() == KEY_ESCAPE
+            || event.getNativeKeyCode() == ',') {
+          close();
+        }
+      }
+    }, KeyDownEvent.getType());
+    updateContextTimer = new Timer() {
+      @Override
+      public void run() {
+        if (prefs.context() == WHOLE_FILE_CONTEXT) {
+          contextEntireFile.setValue(true);
+        }
+        view.setContext(prefs.context());
+      }
+    };
+  }
+
+  void set(DiffPreferences prefs) {
+    this.prefs = prefs;
+
+    setIgnoreWhitespace(prefs.ignoreWhitespace());
+    tabWidth.setIntValue(prefs.tabSize());
+    lineLength.setIntValue(prefs.lineLength());
+    syntaxHighlighting.setValue(prefs.syntaxHighlighting());
+    whitespaceErrors.setValue(prefs.showWhitespaceErrors());
+    showTabs.setValue(prefs.showTabs());
+    lineNumbers.setValue(prefs.showLineNumbers());
+    topMenu.setValue(!prefs.hideTopMenu());
+    manualReview.setValue(prefs.manualReview());
+    expandAllComments.setValue(prefs.expandAllComments());
+    renderEntireFile.setValue(prefs.renderEntireFile());
+    setTheme(prefs.theme());
+
+    switch (view.getIntraLineStatus()) {
+      case OFF:
+      case OK:
+        intralineDifference.setValue(prefs.intralineDifference());
+        break;
+
+      case TIMEOUT:
+      case FAILURE:
+        intralineDifference.setValue(false);
+        intralineDifference.setEnabled(false);
+        break;
+    }
+
+    if (prefs.context() == WHOLE_FILE_CONTEXT) {
+      contextLastValue = DEFAULT_CONTEXT;
+      context.setText("");
+      contextEntireFile.setValue(true);
+    } else {
+      context.setIntValue(prefs.context());
+      contextEntireFile.setValue(false);
+    }
+  }
+
+  @UiHandler("ignoreWhitespace")
+  void onIgnoreWhitespace(ChangeEvent e) {
+    prefs.ignoreWhitespace(Whitespace.valueOf(
+        ignoreWhitespace.getValue(ignoreWhitespace.getSelectedIndex())));
+    view.reloadDiffInfo();
+  }
+
+  @UiHandler("intralineDifference")
+  void onIntralineDifference(ValueChangeEvent<Boolean> e) {
+    prefs.intralineDifference(e.getValue());
+    view.setShowIntraline(prefs.intralineDifference());
+  }
+
+  @UiHandler("context")
+  void onContextKey(KeyPressEvent e) {
+    if (contextEntireFile.getValue()) {
+      char c = e.getCharCode();
+      if ('0' <= c && c <= '9') {
+        contextEntireFile.setValue(false);
+      }
+    }
+  }
+
+  @UiHandler("context")
+  void onContext(ValueChangeEvent<String> e) {
+    String v = e.getValue();
+    int c;
+    if (v != null && v.length() > 0) {
+      c = Math.min(Math.max(0, Integer.parseInt(v)), 32767);
+      contextEntireFile.setValue(false);
+    } else if (v == null || v.isEmpty()) {
+      c = WHOLE_FILE_CONTEXT;
+    } else {
+      return;
+    }
+    prefs.context(c);
+    updateContextTimer.schedule(200);
+  }
+
+  @UiHandler("contextEntireFile")
+  void onContextEntireFile(ValueChangeEvent<Boolean> e) {
+    // If a click arrives too fast after onContext applied an update
+    // the user committed the context line update by clicking on the
+    // whole file checkmark. Drop this event, but transfer focus.
+    if (e.getValue()) {
+      contextLastValue = context.getIntValue();
+      context.setText("");
+      prefs.context(WHOLE_FILE_CONTEXT);
+    } else {
+      prefs.context(contextLastValue > 0 ? contextLastValue : DEFAULT_CONTEXT);
+      context.setIntValue(prefs.context());
+      context.setFocus(true);
+      context.setSelectionRange(0, context.getText().length());
+    }
+    updateContextTimer.schedule(200);
+  }
+
+  @UiHandler("tabWidth")
+  void onTabWidth(ValueChangeEvent<String> e) {
+    String v = e.getValue();
+    if (v != null && v.length() > 0) {
+      prefs.tabSize(Math.max(1, Integer.parseInt(v)));
+      view.operation(new Runnable() {
+        @Override
+        public void run() {
+          int v = prefs.tabSize();
+          view.getCmFromSide(DisplaySide.A).setOption("tabSize", v);
+          view.getCmFromSide(DisplaySide.B).setOption("tabSize", v);
+        }
+      });
+    }
+  }
+
+  @UiHandler("lineLength")
+  void onLineLength(ValueChangeEvent<String> e) {
+    String v = e.getValue();
+    if (v != null && v.length() > 0) {
+      prefs.lineLength(Math.max(1, Integer.parseInt(v)));
+      view.operation(new Runnable() {
+        @Override
+        public void run() {
+          view.setLineLength(prefs.lineLength());
+        }
+      });
+    }
+  }
+  @UiHandler("expandAllComments")
+  void onExpandAllComments(ValueChangeEvent<Boolean> e) {
+    prefs.expandAllComments(e.getValue());
+    view.getCommentManager().setExpandAllComments(prefs.expandAllComments());
+  }
+
+  @UiHandler("showTabs")
+  void onShowTabs(ValueChangeEvent<Boolean> e) {
+    prefs.showTabs(e.getValue());
+    view.setShowTabs(prefs.showTabs());
+  }
+
+  @UiHandler("lineNumbers")
+  void onLineNumbers(ValueChangeEvent<Boolean> e) {
+    prefs.showLineNumbers(e.getValue());
+    view.setShowLineNumbers(prefs.showLineNumbers());
+  }
+
+  @UiHandler("topMenu")
+  void onTopMenu(ValueChangeEvent<Boolean> e) {
+    prefs.hideTopMenu(!e.getValue());
+    Gerrit.setHeaderVisible(!prefs.hideTopMenu());
+    view.resizeCodeMirror();
+  }
+
+  @UiHandler("manualReview")
+  void onManualReview(ValueChangeEvent<Boolean> e) {
+    prefs.manualReview(e.getValue());
+  }
+
+  @UiHandler("syntaxHighlighting")
+  void onSyntaxHighlighting(ValueChangeEvent<Boolean> e) {
+    prefs.syntaxHighlighting(e.getValue());
+    view.setSyntaxHighlighting(prefs.syntaxHighlighting());
+  }
+
+  @UiHandler("whitespaceErrors")
+  void onWhitespaceErrors(ValueChangeEvent<Boolean> e) {
+    prefs.showWhitespaceErrors(e.getValue());
+    view.operation(new Runnable() {
+      @Override
+      public void run() {
+        boolean s = prefs.showWhitespaceErrors();
+        view.getCmFromSide(DisplaySide.A).setOption("showTrailingSpace", s);
+        view.getCmFromSide(DisplaySide.B).setOption("showTrailingSpace", s);
+      }
+    });
+  }
+
+  @UiHandler("renderEntireFile")
+  void onRenderEntireFile(ValueChangeEvent<Boolean> e) {
+    prefs.renderEntireFile(e.getValue());
+    view.updateRenderEntireFile();
+  }
+
+  @UiHandler("theme")
+  void onTheme(ChangeEvent e) {
+    prefs.theme(Theme.valueOf(theme.getValue(theme.getSelectedIndex())));
+    view.setThemeStyles(prefs.theme().isDark());
+    view.operation(new Runnable() {
+      @Override
+      public void run() {
+        String t = prefs.theme().name().toLowerCase();
+        view.getCmFromSide(DisplaySide.A).setOption("theme", t);
+        view.getCmFromSide(DisplaySide.B).setOption("theme", t);
+      }
+    });
+  }
+
+  @UiHandler("apply")
+  void onApply(ClickEvent e) {
+    close();
+  }
+
+  @UiHandler("save")
+  void onSave(ClickEvent e) {
+    AccountApi.putDiffPreferences(prefs, new GerritCallback<DiffPreferences>() {
+      @Override
+      public void onSuccess(DiffPreferences result) {
+        AccountDiffPreference p = Gerrit.getAccountDiffPreference();
+        if (p == null) {
+          p = AccountDiffPreference.createDefault(Gerrit.getUserAccount().getId());
+        }
+        result.copyTo(p);
+        Gerrit.setAccountDiffPreference(p);
+      }
+    });
+    close();
+  }
+
+  @UiHandler("close")
+  void onClose(ClickEvent e) {
+    e.preventDefault();
+    close();
+  }
+
+  void setFocus(boolean focus) {
+    ignoreWhitespace.setFocus(focus);
+  }
+
+  private void close() {
+    ((PopupPanel) getParent()).hide();
+  }
+
+  private void setIgnoreWhitespace(Whitespace v) {
+    String name = v != null ? v.name() : IGNORE_NONE.name();
+    for (int i = 0; i < ignoreWhitespace.getItemCount(); i++) {
+      if (ignoreWhitespace.getValue(i).equals(name)) {
+        ignoreWhitespace.setSelectedIndex(i);
+        return;
+      }
+    }
+    ignoreWhitespace.setSelectedIndex(0);
+  }
+
+  private void initIgnoreWhitespace() {
+    ignoreWhitespace.addItem(
+        PatchUtil.C.whitespaceIGNORE_NONE(),
+        IGNORE_NONE.name());
+    ignoreWhitespace.addItem(
+        PatchUtil.C.whitespaceIGNORE_SPACE_AT_EOL(),
+        IGNORE_SPACE_AT_EOL.name());
+    ignoreWhitespace.addItem(
+        PatchUtil.C.whitespaceIGNORE_SPACE_CHANGE(),
+        IGNORE_SPACE_CHANGE.name());
+    ignoreWhitespace.addItem(
+        PatchUtil.C.whitespaceIGNORE_ALL_SPACE(),
+        IGNORE_ALL_SPACE.name());
+  }
+
+  private void setTheme(Theme v) {
+    String name = v != null ? v.name() : Theme.DEFAULT.name();
+    for (int i = 0; i < theme.getItemCount(); i++) {
+      if (theme.getValue(i).equals(name)) {
+        theme.setSelectedIndex(i);
+        return;
+      }
+    }
+    theme.setSelectedIndex(0);
+  }
+
+  private void initTheme() {
+    theme.addItem(
+        Theme.DEFAULT.name().toLowerCase(),
+        Theme.DEFAULT.name());
+    theme.addItem(
+        Theme.ECLIPSE.name().toLowerCase(),
+        Theme.ECLIPSE.name());
+    theme.addItem(
+        Theme.ELEGANT.name().toLowerCase(),
+        Theme.ELEGANT.name());
+    theme.addItem(
+        Theme.NEAT.name().toLowerCase(),
+        Theme.NEAT.name());
+    theme.addItem(
+        Theme.MIDNIGHT.name().toLowerCase(),
+        Theme.MIDNIGHT.name());
+    theme.addItem(
+        Theme.NIGHT.name().toLowerCase(),
+        Theme.NIGHT.name());
+    theme.addItem(
+        Theme.TWILIGHT.name().toLowerCase(),
+        Theme.TWILIGHT.name());
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
new file mode 100644
index 0000000..d940590
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2013 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:g='urn:import:com.google.gwt.user.client.ui'
+    xmlns:x='urn:import:com.google.gerrit.client.ui'>
+  <ui:style type='com.google.gerrit.client.diff.PreferencesBox.Style'>
+    @external .gwt-TextBox;
+    @external .gwt-ToggleButton .html-face;
+    @external .gwt-ToggleButton-up;
+    @external .gwt-ToggleButton-up-hovering;
+    @external .gwt-ToggleButton-up-disabled;
+    @external .gwt-ToggleButton-down;
+    @external .gwt-ToggleButton-down-hovering;
+    @external .gwt-ToggleButton-down-disabled;
+
+    .dialog {
+      background: rgba(0, 0, 0, 0.85) none repeat scroll 0 50%;
+      color: #ffffff;
+      font-family: arial,sans-serif;
+      font-weight: bold;
+      overflow: hidden;
+      text-align: left;
+      text-shadow: 1px 1px 7px #000000;
+      min-width: 300px;
+      z-index: 200;
+      border-radius: 10px;
+    }
+
+    @if user.agent safari {
+      .dialog {
+        \-webkit-border-radius: 10px;
+      }
+    }
+
+    @if user.agent gecko1_8 {
+      .dialog {
+        \-moz-border-radius: 10px;
+      }
+    }
+
+    .box { margin: 10px; }
+    .box .gwt-TextBox { padding: 0; }
+    .context { vertical-align: bottom; }
+
+    .table tr { min-height: 23px; }
+    .table th,
+    .table td {
+      white-space: nowrap;
+      color: #ffffff;
+    }
+    .table th {
+      padding-right: 8px;
+      text-align: right;
+    }
+
+    .box a,
+    .box a:visited,
+    .box a:hover {
+      color: #dddd00;
+    }
+
+    .box .gwt-ToggleButton {
+      position: relative;
+      height: 19px;
+      width: 140px;
+      background: #fff;
+      color: #000;
+      text-shadow: none;
+    }
+    .box .gwt-ToggleButton .html-face {
+      position: absolute;
+      top: 0;
+      width: 68px;
+      height: 17px;
+      line-height: 17px;
+      text-align: center;
+      border-width: 1px;
+    }
+
+    .box .gwt-ToggleButton-up,
+    .box .gwt-ToggleButton-up-hovering,
+    .box .gwt-ToggleButton-up-disabled,
+    .box .gwt-ToggleButton-down,
+    .box .gwt-ToggleButton-down-hovering,
+    .box .gwt-ToggleButton-down-disabled {
+      padding: 0;
+      border: 0;
+    }
+    .box .gwt-ToggleButton-up .html-face,
+    .box .gwt-ToggleButton-up-hovering .html-face {
+      left: 0;
+      background: #cacaca;
+      border-style: outset;
+    }
+    .box .gwt-ToggleButton-down .html-face,
+    .box .gwt-ToggleButton-down-hovering .html-face {
+      right: 0;
+      background: #bcf;
+      border-style: inset;
+    }
+
+    .box button {
+      margin: 6px 3px 0 0;
+      border-color: rgba(0, 0, 0, 0.1);
+      text-align: center;
+      font-size: 8pt;
+      font-weight: bold;
+      border: 1px solid;
+      cursor: pointer;
+      color: #444;
+      background-color: #f5f5f5;
+      background-image: -webkit-linear-gradient(top, #f5f5f5, #f1f1f1);
+      -webkit-border-radius: 2px;
+      -webkit-box-sizing: content-box;
+    }
+    .box button div {
+      color: #444;
+      height: 10px;
+      min-width: 54px;
+      line-height: 10px;
+      white-space: nowrap;
+    }
+
+    button.apply {
+      background-color: #4d90fe;
+      background-image: -webkit-linear-gradient(top, #4d90fe, #4d90fe);
+    }
+    button.apply div { color: #fff; }
+
+    button.save {
+      margin-left: 10px;
+      color: #d14836;
+      background-color: #d14836;
+      background-image: -webkit-linear-gradient(top, #d14836, #d14836);
+    }
+    button.save div { color: #fff; }
+  </ui:style>
+
+  <g:HTMLPanel styleName='{style.box}'>
+    <table style='width: 100%'>
+      <tr>
+        <td><ui:msg>Diff Preferences</ui:msg></td>
+        <td style='text-align: right'>
+          <g:Anchor ui:field='close' href='javascript:;'><ui:msg>Close</ui:msg></g:Anchor>
+        </td>
+      </tr>
+    </table>
+    <hr/>
+    <table class='{style.table}'>
+      <tr>
+        <th><ui:msg>Theme</ui:msg></th>
+        <td><g:ListBox ui:field='theme'/></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Ignore Whitespace</ui:msg></th>
+        <td><g:ListBox ui:field='ignoreWhitespace'/></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Tab Width</ui:msg></th>
+        <td><x:NpIntTextBox ui:field='tabWidth'
+            visibleLength='4'
+            alignment='RIGHT'/></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Columns</ui:msg></th>
+        <td><x:NpIntTextBox ui:field='lineLength'
+            visibleLength='4'
+            alignment='RIGHT'/></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Lines of Context</ui:msg></th>
+        <td><ui:msg><x:NpIntTextBox ui:field='context'
+            addStyleNames='{style.context}'
+            visibleLength='4'
+            alignment='RIGHT'/>
+          or <g:CheckBox ui:field='contextEntireFile'>entire file</g:CheckBox></ui:msg></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Intraline Difference</ui:msg></th>
+        <td><g:ToggleButton ui:field='intralineDifference'>
+          <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Syntax Highlighting</ui:msg></th>
+        <td><g:ToggleButton ui:field='syntaxHighlighting'>
+          <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Whitespace Errors</ui:msg></th>
+        <td><g:ToggleButton ui:field='whitespaceErrors'>
+          <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Show Tabs</ui:msg></th>
+        <td><g:ToggleButton ui:field='showTabs'>
+          <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Line Numbers</ui:msg></th>
+        <td><g:ToggleButton ui:field='lineNumbers'>
+          <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Top Menu</ui:msg></th>
+        <td><g:ToggleButton ui:field='topMenu'>
+          <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Mark Reviewed</ui:msg></th>
+        <td><g:ToggleButton ui:field='manualReview'>
+          <g:upFace><ui:msg>Automatic</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Manual</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Expand All Comments</ui:msg></th>
+        <td><g:ToggleButton ui:field='expandAllComments'>
+          <g:upFace><ui:msg>Collapse</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Expand</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
+        <th><ui:msg>Render</ui:msg></th>
+        <td><g:ToggleButton ui:field='renderEntireFile'>
+          <g:upFace><ui:msg>Fast</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Slow</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
+        <td></td>
+        <td>
+          <g:Button ui:field='apply' styleName='{style.apply}'>
+            <div><ui:msg>Apply</ui:msg></div>
+          </g:Button>
+          <g:Button ui:field='save' styleName='{style.save}'>
+            <div><ui:msg>Save</ui:msg></div>
+          </g:Button>
+        </td>
+      </tr>
+    </table>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
index f1ea38b..f5f2891 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.changes.CommentApi;
 import com.google.gerrit.client.changes.CommentInfo;
-import com.google.gerrit.client.changes.CommentInput;
 import com.google.gerrit.client.changes.Util;
 import com.google.gerrit.client.patches.PatchUtil;
 import com.google.gerrit.client.rpc.GerritCallback;
@@ -39,8 +38,6 @@
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
-import net.codemirror.lib.CodeMirror;
-
 /** An HtmlPanel for displaying a published comment */
 class PublishedBox extends CommentBox {
   interface Binder extends UiBinder<HTMLPanel, PublishedBox> {}
@@ -50,7 +47,6 @@
     String closed();
   }
 
-  private final SideBySide2 parent;
   private final PatchSet.Id psId;
   private final CommentInfo comment;
   private DraftBox replyBox;
@@ -69,15 +65,12 @@
   AvatarImage avatar;
 
   PublishedBox(
-      SideBySide2 parent,
-      CodeMirror cm,
-      DisplaySide side,
+      CommentGroup group,
       CommentLinkProcessor clp,
       PatchSet.Id psId,
       CommentInfo info) {
-    super(cm, info, side);
+    super(group, info.range());
 
-    this.parent = parent;
     this.psId = psId;
     this.comment = info;
 
@@ -119,7 +112,7 @@
   void setOpen(boolean open) {
     UIObject.setVisible(summary, !open);
     UIObject.setVisible(message, open);
-    UIObject.setVisible(buttons, open);
+    UIObject.setVisible(buttons, open && replyBox == null);
     if (open) {
       removeStyleName(style.closed());
     } else {
@@ -128,13 +121,15 @@
     super.setOpen(open);
   }
 
-  void registerReplyBox(DraftBox box) {
+  void setReplyBox(DraftBox box) {
     replyBox = box;
-    box.registerReplyToBox(this);
+    UIObject.setVisible(buttons, false);
+    box.setReplyToBox(this);
   }
 
   void unregisterReplyBox() {
     replyBox = null;
+    UIObject.setVisible(buttons, isOpen());
   }
 
   private void openReplyBox() {
@@ -142,20 +137,17 @@
     replyBox.setEdit(true);
   }
 
-  DraftBox addReplyBox() {
-    DraftBox box = parent.addDraftBox(parent.createReply(comment), getSide());
-    registerReplyBox(box);
-    return box;
+  void addReplyBox() {
+    getCommentManager().addDraftBox(
+      getCm().side(),
+      CommentInfo.createReply(comment)).setEdit(true);
   }
 
   void doReply() {
     if (!Gerrit.isSignedIn()) {
-      Gerrit.doSignIn(parent.getToken());
+      Gerrit.doSignIn(getCommentManager().getSideBySide2().getToken());
     } else if (replyBox == null) {
-      DraftBox box = addReplyBox();
-      if (!getCommentInfo().has_line()) {
-        parent.addFileCommentBox(box);
-      }
+      addReplyBox();
     } else {
       openReplyBox();
     }
@@ -171,22 +163,18 @@
   void onReplyDone(ClickEvent e) {
     e.stopPropagation();
     if (!Gerrit.isSignedIn()) {
-      Gerrit.doSignIn(parent.getToken());
+      Gerrit.doSignIn(getCommentManager().getSideBySide2().getToken());
     } else if (replyBox == null) {
       done.setEnabled(false);
-      CommentInput input = CommentInput.create(parent.createReply(comment));
-      input.setMessage(PatchUtil.C.cannedReplyDone());
+      CommentInfo input = CommentInfo.createReply(comment);
+      input.message(PatchUtil.C.cannedReplyDone());
       CommentApi.createDraft(psId, input,
           new GerritCallback<CommentInfo>() {
             @Override
             public void onSuccess(CommentInfo result) {
               done.setEnabled(true);
               setOpen(false);
-              DraftBox box = parent.addDraftBox(result, getSide());
-              registerReplyBox(box);
-              if (!getCommentInfo().has_line()) {
-                parent.addFileCommentBox(box);
-              }
+              getCommentManager().addDraftBox(getCm().side(), result);
             }
           });
     } else {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
index f3beda1..6e88c5b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
@@ -53,7 +53,8 @@
         <div ui:field='summary' class='{res.style.summary}'/>
         <div ui:field='date' class='{res.style.date}'/>
       </g:HTMLPanel>
-      <div ui:field='message' aria-hidden='true' style='display: NONE'/>
+      <div ui:field='message' class='{res.style.message}'
+           aria-hidden='true' style='display: NONE'/>
       <div ui:field='buttons' aria-hidden='true' style='display: NONE'>
         <g:Button ui:field='reply' styleName=''
             title='Reply to this comment'>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java
index b7840c2..8c4fc51 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java
@@ -17,18 +17,29 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
 
 /** Resources used by diff. */
 interface Resources extends ClientBundle {
   static final Resources I = GWT.create(Resources.class);
 
   @Source("CommentBoxUi.css") Style style();
+  @Source("go-prev.png") ImageResource go_prev();
+  @Source("go-next.png") ImageResource go_next();
+  @Source("go-up.png") ImageResource go_up();
+  @Source("gear.png") ImageResource gear();
 
   interface Style extends CssResource {
+    String commentWidgets();
     String commentBox();
     String contents();
+    String message();
     String header();
     String summary();
     String date();
+
+    String go_prev();
+    String go_next();
+    String go_up();
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java
index 425878c..ba74202 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java
@@ -14,34 +14,33 @@
 
 package com.google.gerrit.client.diff;
 
-import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
 import com.google.gwt.user.client.Timer;
 
 import net.codemirror.lib.CodeMirror;
-import net.codemirror.lib.CodeMirror.Viewport;
 import net.codemirror.lib.ScrollInfo;
 
 class ScrollSynchronizer {
   private DiffTable diffTable;
   private LineMapper mapper;
+  private OverviewBar overview;
   private ScrollCallback active;
 
-  void init(DiffTable diffTable,
+  ScrollSynchronizer(DiffTable diffTable,
       CodeMirror cmA, CodeMirror cmB,
       LineMapper mapper) {
     this.diffTable = diffTable;
     this.mapper = mapper;
+    this.overview = diffTable.overview;
 
     cmA.on("scroll", new ScrollCallback(cmA, cmB, DisplaySide.A));
     cmB.on("scroll", new ScrollCallback(cmB, cmA, DisplaySide.B));
   }
 
   private void updateScreenHeader(ScrollInfo si) {
-    if (si.getTop() == 0 && !Gerrit.isHeaderVisible()) {
+    if (si.getTop() == 0 && !diffTable.isHeaderVisible()) {
       diffTable.setHeaderVisible(true);
     } else if (si.getTop() > 0.5 * si.getClientHeight()
-        && Gerrit.isHeaderVisible()) {
+        && diffTable.isHeaderVisible()) {
       diffTable.setHeaderVisible(false);
     }
   }
@@ -76,7 +75,8 @@
       if (active == this) {
         ScrollInfo si = src.getScrollInfo();
         updateScreenHeader(si);
-        dst.scrollTo(si.getLeft(), si.getTop());
+        overview.update(si);
+        dst.scrollTo(si.getLeft(), align(si.getTop()));
         state = 0;
       }
     }
@@ -85,37 +85,37 @@
       switch (state) {
         case 0:
           state = 1;
+          dst.scrollToY(align(src.getScrollInfo().getTop()));
           break;
         case 1:
           state = 2;
-          return;
+          break;
         case 2:
           active = null;
           fixup.cancel();
-          return;
+          break;
       }
+    }
 
+    private double align(double srcTop) {
       // Since CM doesn't always take the height of line widgets into
       // account when calculating scrollInfo when scrolling too fast (e.g.
       // throw scrolling), simply setting scrollTop to be the same doesn't
       // guarantee alignment.
-      //
-      // Iterate over the viewport to find the first line that isn't part of
-      // an insertion or deletion gap, for which isAligned() will be true.
-      // We then manually examine if the lines that should be aligned are at
-      // the same height. If not, perform additional scrolling.
-      Viewport fromTo = src.getViewport();
-      for (int line = fromTo.getFrom(); line <= fromTo.getTo(); line++) {
-        LineOnOtherInfo info = mapper.lineOnOther(srcSide, line);
-        if (info.isAligned()) {
-          double sy = src.heightAtLine(line);
-          double dy = dst.heightAtLine(info.getLine());
-          if (Math.abs(dy - sy) >= 1) {
-            dst.scrollToY(dst.getScrollInfo().getTop() + (dy - sy));
-          }
-          break;
-        }
+
+      int line = src.lineAtHeight(srcTop, "local");
+      if (line == 0) {
+        // Padding for insert at start of file occurs above line 0,
+        // and CM3 doesn't always compute heightAtLine correctly.
+        return srcTop;
       }
+
+      // Find a pair of lines that are aligned and near the top of
+      // the viewport. Use that distance to correct the Y coordinate.
+      LineMapper.AlignedPair p = mapper.align(srcSide, line);
+      double sy = src.heightAtLine(p.src, "local");
+      double dy = dst.heightAtLine(p.dst, "local");
+      return Math.max(0, dy + (srcTop - sy));
     }
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
index 4bbcc0e..364d94c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
@@ -14,44 +14,38 @@
 
 package com.google.gerrit.client.diff;
 
+import static java.lang.Double.POSITIVE_INFINITY;
+
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.JumpKeys;
+import com.google.gerrit.client.account.DiffPreferences;
 import com.google.gerrit.client.change.ChangeScreen2;
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
 import com.google.gerrit.client.changes.ChangeList;
-import com.google.gerrit.client.changes.CommentApi;
-import com.google.gerrit.client.changes.CommentInfo;
-import com.google.gerrit.client.diff.DiffInfo.Region;
-import com.google.gerrit.client.diff.DiffInfo.Span;
 import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
-import com.google.gerrit.client.diff.PaddingManager.LinePaddingWidgetWrapper;
-import com.google.gerrit.client.diff.PaddingManager.PaddingWidgetWrapper;
 import com.google.gerrit.client.patches.PatchUtil;
-import com.google.gerrit.client.patches.SkippedLine;
 import com.google.gerrit.client.projects.ConfigInfoCache;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
-import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.changes.ListChangesOption;
-import com.google.gerrit.common.changes.Side;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
-import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.dom.client.Style.Unit;
+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.logical.shared.ResizeEvent;
@@ -59,46 +53,36 @@
 import com.google.gwt.event.shared.HandlerRegistration;
 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.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.globalkey.client.KeyCommand;
 import com.google.gwtexpui.globalkey.client.KeyCommandSet;
 import com.google.gwtexpui.globalkey.client.ShowHelpCommand;
 
 import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.BeforeSelectionChangeHandler;
 import net.codemirror.lib.CodeMirror.GutterClickHandler;
 import net.codemirror.lib.CodeMirror.LineClassWhere;
 import net.codemirror.lib.CodeMirror.LineHandle;
-import net.codemirror.lib.CodeMirror.RenderLineHandler;
-import net.codemirror.lib.CodeMirror.Viewport;
 import net.codemirror.lib.Configuration;
 import net.codemirror.lib.KeyMap;
 import net.codemirror.lib.LineCharacter;
-import net.codemirror.lib.LineWidget;
 import net.codemirror.lib.ModeInjector;
-import net.codemirror.lib.TextMarker.FromTo;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 public class SideBySide2 extends Screen {
+  private static final KeyMap RENDER_ENTIRE_FILE_KEYMAP = KeyMap.create()
+      .on("Ctrl-F", false);
+
   interface Binder extends UiBinder<FlowPanel, SideBySide2> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
-  private static final JsArrayString EMPTY =
-      JavaScriptObject.createArray().cast();
-
   @UiField(provided = true)
   Header header;
 
@@ -109,58 +93,48 @@
   private final PatchSet.Id base;
   private final PatchSet.Id revision;
   private final String path;
-  private AccountDiffPreference pref;
+  private DisplaySide startSide;
+  private int startLine;
+  private DiffPreferences prefs;
 
   private CodeMirror cmA;
   private CodeMirror cmB;
-  private ScrollSynchronizer scrollingGlue;
+  private Element columnMarginA;
+  private Element columnMarginB;
   private HandlerRegistration resizeHandler;
-  private JsArray<CommentInfo> publishedBase;
-  private JsArray<CommentInfo> publishedRevision;
-  private JsArray<CommentInfo> draftsBase;
-  private JsArray<CommentInfo> draftsRevision;
   private DiffInfo diff;
-  private LineMapper mapper;
-  private CommentLinkProcessor commentLinkProcessor;
-  private Map<String, PublishedBox> publishedMap;
-  private Map<LineHandle, CommentBox> lineActiveBoxMap;
-  private Map<LineHandle, List<PublishedBox>> linePublishedBoxesMap;
-  private Map<LineHandle, PaddingManager> linePaddingManagerMap;
-  private Map<LineHandle, LinePaddingWidgetWrapper> linePaddingOnOtherSideMap;
-  private List<DiffChunkInfo> diffChunks;
-  private List<SkippedLine> skips;
-  private Set<DraftBox> unsaved;
-  private int context;
+  private boolean largeFile;
+  private ChunkManager chunkManager;
+  private CommentManager commentManager;
+  private SkipManager skipManager;
 
   private KeyCommandSet keysNavigation;
   private KeyCommandSet keysAction;
   private KeyCommandSet keysComment;
   private List<HandlerRegistration> handlers;
-  private List<Runnable> deferred;
+  private PreferencesAction prefsAction;
+  private int reloadVersionId;
 
   public SideBySide2(
       PatchSet.Id base,
       PatchSet.Id revision,
-      String path) {
+      String path,
+      DisplaySide startSide,
+      int startLine) {
     this.base = base;
     this.revision = revision;
     this.changeId = revision.getParentKey();
     this.path = path;
+    this.startSide = startSide;
+    this.startLine = startLine;
 
-    pref = Gerrit.getAccountDiffPreference();
-    if (pref == null) {
-      pref = AccountDiffPreference.createDefault(null);
-    }
-    context = pref.getContext();
-
-    unsaved = new HashSet<DraftBox>();
-    handlers = new ArrayList<HandlerRegistration>(6);
-    // TODO: Re-implement necessary GlobalKey bindings.
-    addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
+    prefs = DiffPreferences.create(Gerrit.getAccountDiffPreference());
+    handlers = new ArrayList<>(6);
     keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
     header = new Header(keysNavigation, base, revision, path);
     diffTable = new DiffTable(this, base, revision, path);
     add(uiBinder.createAndBindUi(this));
+    addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
   }
 
   @Override
@@ -182,30 +156,27 @@
     DiffApi.diff(revision, path)
       .base(base)
       .wholeFile()
-      .intraline(pref.isIntralineDifference())
-      .ignoreWhitespace(pref.getIgnoreWhitespace())
+      .intraline(prefs.intralineDifference())
+      .ignoreWhitespace(prefs.ignoreWhitespace())
       .get(cmGroup.addFinal(new GerritCallback<DiffInfo>() {
         @Override
         public void onSuccess(DiffInfo diffInfo) {
           diff = diffInfo;
-          new ModeInjector()
-            .add(getContentType(diff.meta_a()))
-            .add(getContentType(diff.meta_b()))
-            .inject(modeInjectorCb);
+          if (prefs.syntaxHighlighting()) {
+            largeFile = isLargeFile(diffInfo);
+            if (largeFile) {
+              modeInjectorCb.onSuccess(null);
+            } else {
+              injectMode(diffInfo, modeInjectorCb);
+            }
+          } else {
+            modeInjectorCb.onSuccess(null);
+          }
         }
       }));
 
-    if (base != null) {
-      CommentApi.comments(base, group.add(getCommentCallback(DisplaySide.A, false)));
-    }
-    CommentApi.comments(revision, group.add(getCommentCallback(DisplaySide.B, false)));
-
-    if (Gerrit.isSignedIn()) {
-      if (base != null) {
-        CommentApi.drafts(base, group.add(getCommentCallback(DisplaySide.A, true)));
-      }
-      CommentApi.drafts(revision, group.add(getCommentCallback(DisplaySide.B, true)));
-    }
+    final CommentsCollections comments = new CommentsCollections();
+    comments.load(base, revision, path, group);
 
     RestApi call = ChangeApi.detail(changeId.get());
     ChangeList.addOptions(call, EnumSet.of(
@@ -217,18 +188,19 @@
         JsArray<RevisionInfo> list = info.revisions().values();
         RevisionInfo.sortRevisionInfoByNumber(list);
         diffTable.setUpPatchSetNav(list, diff);
+        header.setChangeInfo(info);
       }}));
 
     ConfigInfoCache.get(changeId, group.addFinal(
         new ScreenLoadCallback<ConfigInfoCache.Entry>(SideBySide2.this) {
           @Override
           protected void preDisplay(ConfigInfoCache.Entry result) {
-            commentLinkProcessor = result.getCommentLinkProcessor();
+            commentManager = new CommentManager(
+                SideBySide2.this,
+                base, revision, path,
+                result.getCommentLinkProcessor());
             setTheme(result.getTheme());
-
-            DiffInfo diffInfo = diff;
-            diff = null;
-            display(diffInfo);
+            display(comments);
           }
         }));
   }
@@ -237,6 +209,16 @@
   public void onShowView() {
     super.onShowView();
     Window.enableScrolling(false);
+    JumpKeys.enable(false);
+    if (prefs.hideTopMenu()) {
+      Gerrit.setHeaderVisible(false);
+    }
+    resizeHandler = Window.addResizeHandler(new ResizeHandler() {
+      @Override
+      public void onResize(ResizeEvent event) {
+        resizeCodeMirror();
+      }
+    });
 
     final int height = getCodeMirrorHeight();
     operation(new Runnable() {
@@ -246,12 +228,33 @@
         cmB.setHeight(height);
         cmA.refresh();
         cmB.refresh();
-        cmB.setCursor(LineCharacter.create(0));
-        cmB.focus();
       }
     });
-    diffTable.sidePanel.adjustGutters(cmB);
+    setLineLength(prefs.lineLength());
+    diffTable.overview.refresh();
 
+    if (startLine == 0 && diff.meta_b() != null) {
+      DiffChunkInfo d = chunkManager.getFirst();
+      if (d != null) {
+        startSide = d.getSide();
+        startLine = d.getStart() + 1;
+      }
+    }
+    if (startSide != null && startLine > 0) {
+      int line = startLine - 1;
+      CodeMirror cm = getCmFromSide(startSide);
+      if (cm.lineAtHeight(height - 20) < line) {
+        cm.scrollToY(cm.heightAtLine(line, "local") - 0.5 * height);
+      }
+      cm.setCursor(LineCharacter.create(line));
+      cm.focus();
+    } else {
+      cmA.setCursor(LineCharacter.create(0));
+      cmA.focus();
+    }
+    if (Gerrit.isSignedIn() && prefs.autoReview()) {
+      header.autoReview();
+    }
     prefetchNextFile();
   }
 
@@ -259,8 +262,12 @@
   protected void onUnload() {
     super.onUnload();
 
-    saveAllDrafts(null);
-    removeKeyHandlerRegs();
+    removeKeyHandlerRegistrations();
+    if (commentManager != null) {
+      CallbackGroup group = new CallbackGroup();
+      commentManager.saveAllDrafts(group);
+      group.done();
+    }
     if (resizeHandler != null) {
       resizeHandler.removeHandler();
       resizeHandler = null;
@@ -271,12 +278,16 @@
     if (cmB != null) {
       cmB.getWrapperElement().removeFromParent();
     }
+    if (prefsAction != null) {
+      prefsAction.hide();
+    }
 
     Window.enableScrolling(true);
     Gerrit.setHeaderVisible(true);
+    JumpKeys.enable(true);
   }
 
-  private void removeKeyHandlerRegs() {
+  private void removeKeyHandlerRegistrations() {
     for (HandlerRegistration h : handlers) {
       h.removeHandler();
     }
@@ -284,40 +295,43 @@
   }
 
   private void registerCmEvents(final CodeMirror cm) {
+    cm.on("beforeSelectionChange", onSelectionChange(cm));
     cm.on("cursorActivity", updateActiveLine(cm));
     cm.on("gutterClick", onGutterClick(cm));
-    cm.on("renderLine", resizeLinePadding(getSideFromCm(cm)));
-    cm.on("viewportChange", adjustGutters(cm));
-    cm.on("focus", new Runnable() {
-      @Override
-      public void run() {
-        updateActiveLine(cm).run();
-      }
-    });
+    cm.on("focus", updateActiveLine(cm));
     cm.addKeyMap(KeyMap.create()
         .on("A", upToChange(true))
         .on("U", upToChange(false))
-        .on("R", toggleReviewed())
-        .on("O", toggleOpenBox(cm))
-        .on("Enter", toggleOpenBox(cm))
-        .on("C", insertNewDraft(cm))
-        .on("Alt-U", new Runnable() {
+        .on("[", header.navigate(Direction.PREV))
+        .on("]", header.navigate(Direction.NEXT))
+        .on("R", header.toggleReviewed())
+        .on("O", commentManager.toggleOpenBox(cm))
+        .on("Enter", commentManager.toggleOpenBox(cm))
+        .on("C", commentManager.insertNewDraft(cm))
+        .on("N", maybeNextVimSearch(cm))
+        .on("P", chunkManager.diffChunkNav(cm, Direction.PREV))
+        .on("Shift-M", header.reviewedAndNext())
+        .on("Shift-N", commentManager.commentNav(cm, Direction.NEXT))
+        .on("Shift-P", commentManager.commentNav(cm, Direction.PREV))
+        .on("Shift-O", commentManager.openCloseAll(cm))
+        .on("Shift-Left", moveCursorToSide(cm, DisplaySide.A))
+        .on("Shift-Right", moveCursorToSide(cm, DisplaySide.B))
+        .on("I", new Runnable() {
           public void run() {
-            cm.getInputField().blur();
-            clearActiveLine(cm);
-            clearActiveLine(otherCm(cm));
+            switch (getIntraLineStatus()) {
+              case OFF:
+              case OK:
+                toggleShowIntraline();
+                break;
+              default:
+                break;
+            }
           }
         })
-        .on("[", new Runnable() {
+        .on("','", new Runnable() {
           @Override
           public void run() {
-            (header.hasPrev() ? header.prev : header.up).go();
-          }
-        })
-        .on("]", new Runnable() {
-          @Override
-          public void run() {
-            (header.hasNext() ? header.next : header.up).go();
+            prefsAction.show();
           }
         })
         .on("Shift-/", new Runnable() {
@@ -326,29 +340,62 @@
             new ShowHelpCommand().onKeyPress(null);
           }
         })
+        .on("Space", new Runnable() {
+          @Override
+          public void run() {
+            CodeMirror.handleVimKey(cm, "<C-d>");
+          }
+        })
+        .on("Shift-Space", new Runnable() {
+          @Override
+          public void run() {
+            CodeMirror.handleVimKey(cm, "<C-u>");
+          }
+        })
         .on("Ctrl-F", new Runnable() {
           @Override
           public void run() {
             CodeMirror.handleVimKey(cm, "/");
           }
         })
-        .on("Space", new Runnable() {
-          @Override
-          public void run() {
-            CodeMirror.handleVimKey(cm, "<PageDown>");
-          }
-        })
         .on("Ctrl-A", new Runnable() {
           @Override
           public void run() {
             cm.execCommand("selectAll");
           }
-        })
-        .on("N", maybeNextVimSearch(cm))
-        .on("P", diffChunkNav(cm, true))
-        .on("Shift-O", openClosePublished(cm))
-        .on("Shift-Left", flipCursorSide(cm, true))
-        .on("Shift-Right", flipCursorSide(cm, false)));
+        }));
+    if (prefs.renderEntireFile()) {
+      cm.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
+    }
+  }
+
+  private BeforeSelectionChangeHandler onSelectionChange(final CodeMirror cm) {
+    return new BeforeSelectionChangeHandler() {
+      private InsertCommentBubble bubble;
+
+      @Override
+      public void handle(CodeMirror cm, LineCharacter anchor, LineCharacter head) {
+        if (anchor == head
+            || (anchor.getLine() == head.getLine()
+             && anchor.getCh() == head.getCh())) {
+          if (bubble != null) {
+            bubble.setVisible(false);
+          }
+          return;
+        } else if (bubble == null) {
+          init(anchor);
+        } else {
+          bubble.setVisible(true);
+        }
+        bubble.position(cm.charCoords(head, "local"));
+      }
+
+      private void init(LineCharacter anchor) {
+        bubble = new InsertCommentBubble(commentManager, cm);
+        add(bubble);
+        cm.addWidget(anchor, bubble.getElement(), false);
+      }
+    };
   }
 
   @Override
@@ -356,10 +403,17 @@
     super.registerKeys();
 
     keysNavigation.add(new UpToChangeCommand2(revision, 0, 'u'));
-    keysNavigation.add(new NoOpKeyCommand(0, 'j', PatchUtil.C.lineNext()));
-    keysNavigation.add(new NoOpKeyCommand(0, 'k', PatchUtil.C.linePrev()));
-    keysNavigation.add(new NoOpKeyCommand(0, 'n', PatchUtil.C.chunkNext2()));
-    keysNavigation.add(new NoOpKeyCommand(0, 'p', PatchUtil.C.chunkPrev2()));
+    keysNavigation.add(
+        new NoOpKeyCommand(0, 'j', PatchUtil.C.lineNext()),
+        new NoOpKeyCommand(0, 'k', PatchUtil.C.linePrev()));
+    keysNavigation.add(
+        new NoOpKeyCommand(0, 'n', PatchUtil.C.chunkNext2()),
+        new NoOpKeyCommand(0, 'p', PatchUtil.C.chunkPrev2()));
+    keysNavigation.add(
+        new NoOpKeyCommand(KeyCommand.M_SHIFT, 'n', PatchUtil.C.commentNext()),
+        new NoOpKeyCommand(KeyCommand.M_SHIFT, 'p', PatchUtil.C.commentPrev()));
+    keysNavigation.add(
+        new NoOpKeyCommand(KeyCommand.M_CTRL, 'f', Gerrit.C.keySearch()));
 
     keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
     keysAction.add(new NoOpKeyCommand(0, KeyCodes.KEY_ENTER,
@@ -370,7 +424,14 @@
     keysAction.add(new KeyCommand(0, 'r', PatchUtil.C.toggleReviewed()) {
       @Override
       public void onKeyPress(KeyPressEvent event) {
-        toggleReviewed().run();
+        header.toggleReviewed().run();
+      }
+    });
+    keysAction.add(new KeyCommand(
+        KeyCommand.M_SHIFT, 'm', PatchUtil.C.markAsReviewedAndGoToNext()) {
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        header.reviewedAndNext().run();
       }
     });
     keysAction.add(new KeyCommand(0, 'a', PatchUtil.C.openReply()) {
@@ -379,6 +440,21 @@
         upToChange(true).run();
       }
     });
+    keysAction.add(new KeyCommand(0, ',', PatchUtil.C.showPreferences()) {
+      @Override
+      public void onKeyPress(KeyPressEvent event) {
+        prefsAction.show();
+      }
+    });
+    if (getIntraLineStatus() == DiffInfo.IntraLineStatus.OFF
+        || getIntraLineStatus() == DiffInfo.IntraLineStatus.OK) {
+      keysAction.add(new KeyCommand(0, 'i', PatchUtil.C.toggleIntraline()) {
+        @Override
+        public void onKeyPress(KeyPressEvent event) {
+          toggleShowIntraline();
+        }
+      });
+    }
 
     if (Gerrit.isSignedIn()) {
       keysAction.add(new NoOpKeyCommand(0, 'c', PatchUtil.C.commentInsert()));
@@ -390,582 +466,201 @@
     } else {
       keysComment = null;
     }
-    removeKeyHandlerRegs();
+
+    removeKeyHandlerRegistrations();
     handlers.add(GlobalKey.add(this, keysNavigation));
     if (keysComment != null) {
       handlers.add(GlobalKey.add(this, keysComment));
     }
     handlers.add(GlobalKey.add(this, keysAction));
-  }
-
-  private GerritCallback<NativeMap<JsArray<CommentInfo>>> getCommentCallback(
-      final DisplaySide side, final boolean toDrafts) {
-    return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+    handlers.add(ShowHelpCommand.addFocusHandler(new FocusHandler() {
       @Override
-      public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
-        JsArray<CommentInfo> in = result.get(path);
-        if (in != null) {
-          if (toDrafts) {
-            if (side == DisplaySide.A) {
-              draftsBase = in;
-            } else {
-              draftsRevision = in;
-            }
-          } else {
-            if (side == DisplaySide.A) {
-              publishedBase = in;
-            } else {
-              publishedRevision = in;
-            }
-          }
-        }
+      public void onFocus(FocusEvent event) {
+        cmB.focus();
       }
-    };
+    }));
   }
 
-  private void display(final DiffInfo diffInfo) {
-    skips = new ArrayList<SkippedLine>();
-    linePaddingOnOtherSideMap = new HashMap<LineHandle, LinePaddingWidgetWrapper>();
-    diffChunks = new ArrayList<DiffChunkInfo>();
-    lineActiveBoxMap = new HashMap<LineHandle, CommentBox>();
-    linePublishedBoxesMap = new HashMap<LineHandle, List<PublishedBox>>();
-    linePaddingManagerMap = new HashMap<LineHandle, PaddingManager>();
-    if (publishedBase != null || publishedRevision != null) {
-      publishedMap = new HashMap<String, PublishedBox>();
+  private void display(final CommentsCollections comments) {
+    setThemeStyles(prefs.theme().isDark());
+    setShowTabs(prefs.showTabs());
+    setShowIntraline(prefs.intralineDifference());
+    if (prefs.showLineNumbers()) {
+      diffTable.addStyleName(DiffTable.style.showLineNumbers());
     }
 
-    if (pref.isShowTabs()) {
-      diffTable.addStyleName(DiffTable.style.showtabs());
-    }
+    cmA = newCM(diff.meta_a(), diff.text_a(), DisplaySide.A, diffTable.cmA);
+    cmB = newCM(diff.meta_b(), diff.text_b(), DisplaySide.B, diffTable.cmB);
+    diffTable.overview.init(cmB);
+    chunkManager = new ChunkManager(this, cmA, cmB, diffTable.overview);
+    skipManager = new SkipManager(this, commentManager);
 
-    cmA = createCodeMirror(diffInfo.meta_a(), diffInfo.text_a(), diffTable.cmA);
-    cmB = createCodeMirror(diffInfo.meta_b(), diffInfo.text_b(), diffTable.cmB);
+    columnMarginA = DOM.createDiv();
+    columnMarginB = DOM.createDiv();
+    columnMarginA.setClassName(DiffTable.style.columnMargin());
+    columnMarginB.setClassName(DiffTable.style.columnMargin());
+    cmA.getMoverElement().appendChild(columnMarginA);
+    cmB.getMoverElement().appendChild(columnMarginB);
 
-    cmA.operation(new Runnable() {
-      @Override
+    operation(new Runnable() {
       public void run() {
-        cmB.operation(new Runnable() {
-          @Override
-          public void run() {
-            // Estimate initial CM3 height, fixed up in onShowView.
-            int height = Window.getClientHeight()
-                - (Gerrit.getHeaderFooterHeight() + 18);
-            cmA.setHeight(height);
-            cmB.setHeight(height);
+        // Estimate initial CM3 height, fixed up in onShowView.
+        int height = Window.getClientHeight()
+            - (Gerrit.getHeaderFooterHeight() + 18);
+        cmA.setHeight(height);
+        cmB.setHeight(height);
 
-            render(diffInfo);
-            if (publishedBase != null) {
-              renderPublished(publishedBase);
-            }
-            if (publishedRevision != null) {
-              renderPublished(publishedRevision);
-            }
-            if (draftsBase != null) {
-              renderDrafts(draftsBase);
-            }
-            if (draftsRevision != null) {
-              renderDrafts(draftsRevision);
-            }
-            renderSkips();
-          }
-        });
+        render(diff);
+        commentManager.render(comments, prefs.expandAllComments());
+        skipManager.render(prefs.context(), diff);
       }
     });
 
     registerCmEvents(cmA);
     registerCmEvents(cmB);
+    new ScrollSynchronizer(diffTable, cmA, cmB, chunkManager.getLineMapper());
 
-    scrollingGlue = GWT.create(ScrollSynchronizer.class);
-    scrollingGlue.init(diffTable, cmA, cmB, mapper);
-    resizeHandler = Window.addResizeHandler(new ResizeHandler() {
-      @Override
-      public void onResize(ResizeEvent event) {
-        resizeCodeMirror();
-      }
-    });
+    prefsAction = new PreferencesAction(this, prefs);
+    header.init(prefsAction);
+
+    if (largeFile && prefs.syntaxHighlighting()) {
+      Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
+        @Override
+        public boolean execute() {
+          if (prefs.syntaxHighlighting() && isAttached()) {
+            setSyntaxHighlighting(prefs.syntaxHighlighting());
+          }
+          return false;
+        }
+      }, 250);
+    }
   }
 
-  private CodeMirror createCodeMirror(
+  private CodeMirror newCM(
       DiffInfo.FileMeta meta,
       String contents,
+      DisplaySide side,
       Element parent) {
-    Configuration cfg = Configuration.create()
+    return CodeMirror.create(side, parent, Configuration.create()
       .set("readOnly", true)
       .set("cursorBlinkRate", 0)
       .set("cursorHeight", 0.85)
-      .set("lineNumbers", true)
-      .set("tabSize", pref.getTabSize())
-      .set("mode", getContentType(meta))
+      .set("lineNumbers", prefs.showLineNumbers())
+      .set("tabSize", prefs.tabSize())
+      .set("mode", largeFile ? null : getContentType(meta))
       .set("lineWrapping", false)
       .set("styleSelectedText", true)
-      .set("showTrailingSpace", pref.isShowWhitespaceErrors())
+      .set("showTrailingSpace", prefs.showWhitespaceErrors())
       .set("keyMap", "vim_ro")
-      .set("value", meta != null ? contents : "");
-    return CodeMirror.create(parent, cfg);
+      .set("theme", prefs.theme().name().toLowerCase())
+      .set("value", meta != null ? contents : "")
+      .set("viewportMargin", prefs.renderEntireFile() ? POSITIVE_INFINITY : 10));
+  }
+
+  DiffInfo.IntraLineStatus getIntraLineStatus() {
+    return diff.intraline_status();
+  }
+
+  void setThemeStyles(boolean d) {
+    if (d) {
+      diffTable.addStyleName(DiffTable.style.dark());
+    } else {
+      diffTable.removeStyleName(DiffTable.style.dark());
+    }
+  }
+
+  void setShowTabs(boolean b) {
+    if (b) {
+      diffTable.addStyleName(DiffTable.style.showTabs());
+    } else {
+      diffTable.removeStyleName(DiffTable.style.showTabs());
+    }
+  }
+
+  void setLineLength(int columns) {
+    columnMarginA.getStyle().setMarginLeft(
+        columns * cmA.defaultCharWidth()
+        + cmA.getGutterElement().getOffsetWidth(), Unit.PX);
+
+    columnMarginB.getStyle().setMarginLeft(
+        columns * cmB.defaultCharWidth()
+        + cmB.getGutterElement().getOffsetWidth(), Unit.PX);
+  }
+
+  void setShowLineNumbers(boolean b) {
+    cmA.setOption("lineNumbers", b);
+    cmB.setOption("lineNumbers", b);
+    if (b) {
+      diffTable.addStyleName(DiffTable.style.showLineNumbers());
+    } else {
+      diffTable.removeStyleName(DiffTable.style.showLineNumbers());
+    }
+  }
+
+  void setShowIntraline(boolean b) {
+    if (b && getIntraLineStatus() == DiffInfo.IntraLineStatus.OFF) {
+      reloadDiffInfo();
+    } else if (b) {
+      diffTable.removeStyleName(DiffTable.style.noIntraline());
+    } else {
+      diffTable.addStyleName(DiffTable.style.noIntraline());
+    }
+  }
+
+  private void toggleShowIntraline() {
+    prefs.intralineDifference(!prefs.intralineDifference());
+    setShowIntraline(prefs.intralineDifference());
+    prefsAction.update();
+  }
+
+  void setSyntaxHighlighting(boolean b) {
+    if (b) {
+      injectMode(diff, new AsyncCallback<Void>() {
+        @Override
+        public void onSuccess(Void result) {
+          if (prefs.syntaxHighlighting()) {
+            cmA.setOption("mode", getContentType(diff.meta_a()));
+            cmB.setOption("mode", getContentType(diff.meta_b()));
+          }
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+          prefs.syntaxHighlighting(false);
+        }
+      });
+    } else {
+      cmA.setOption("mode", (String) null);
+      cmB.setOption("mode", (String) null);
+    }
+  }
+
+  void setContext(final int context) {
+    operation(new Runnable() {
+      @Override
+      public void run() {
+        skipManager.removeAll();
+        skipManager.render(context, diff);
+        diffTable.overview.refresh();
+      }
+    });
   }
 
   private void render(DiffInfo diff) {
-    JsArray<Region> regions = diff.content();
-    if (!(regions.length() == 0 ||
-        regions.length() == 1 && regions.get(0).ab() != null)) {
-      header.removeNoDiff();
-    }
-
-    String diffColor = diff.meta_a() == null || diff.meta_b() == null
-        ? DiffTable.style.intralineBg()
-        : DiffTable.style.diff();
-    mapper = new LineMapper();
-    for (int i = 0; i < regions.length(); i++) {
-      Region current = regions.get(i);
-      int origLineA = mapper.getLineA();
-      int origLineB = mapper.getLineB();
-      if (current.ab() != null) { // Common
-        int length = current.ab().length();
-        mapper.appendCommon(length);
-        if (i == 0 && length > context + 1) {
-          skips.add(new SkippedLine(0, 0, length - context));
-        } else if (i == regions.length() - 1 && length > context + 1) {
-          skips.add(new SkippedLine(origLineA + context, origLineB + context,
-              length - context));
-        } else if (length > 2 * context + 1) {
-          skips.add(new SkippedLine(origLineA + context, origLineB + context,
-              length - 2 * context));
-        }
-      } else { // Insert, Delete or Edit
-        JsArrayString currentA = current.a() == null ? EMPTY : current.a();
-        JsArrayString currentB = current.b() == null ? EMPTY : current.b();
-        int aLength = currentA.length();
-        int bLength = currentB.length();
-        String color = currentA == EMPTY || currentB == EMPTY
-            ? diffColor
-            : DiffTable.style.intralineBg();
-        colorLines(cmA, color, origLineA, aLength);
-        colorLines(cmB, color, origLineB, bLength);
-        int commonCnt = Math.min(aLength, bLength);
-        mapper.appendCommon(commonCnt);
-        if (aLength < bLength) { // Edit with insertion
-          int insertCnt = bLength - aLength;
-          mapper.appendInsert(insertCnt);
-        } else if (aLength > bLength) { // Edit with deletion
-          int deleteCnt = aLength - bLength;
-          mapper.appendDelete(deleteCnt);
-        }
-        int chunkEndA = mapper.getLineA() - 1;
-        int chunkEndB = mapper.getLineB() - 1;
-        if (aLength > 0) {
-          addDiffChunkAndPadding(cmB, chunkEndB, chunkEndA, aLength, bLength > 0);
-        }
-        if (bLength > 0) {
-          addDiffChunkAndPadding(cmA, chunkEndA, chunkEndB, bLength, aLength > 0);
-        }
-        markEdit(cmA, currentA, current.edit_a(), origLineA);
-        markEdit(cmB, currentB, current.edit_b(), origLineB);
-        if (aLength == 0) {
-          diffTable.sidePanel.addGutter(cmB, origLineB, SidePanel.GutterType.INSERT);
-        } else if (bLength == 0) {
-          diffTable.sidePanel.addGutter(cmA, origLineA, SidePanel.GutterType.DELETE);
-        } else {
-          diffTable.sidePanel.addGutter(cmB, origLineB, SidePanel.GutterType.EDIT);
-        }
-      }
-    }
+    header.setNoDiff(diff);
+    chunkManager.render(diff);
   }
 
-  private DraftBox addNewDraft(CodeMirror cm, int line, FromTo fromTo) {
-    DisplaySide side = getSideFromCm(cm);
-    return addDraftBox(CommentInfo.createRange(
-        path,
-        getStoredSideFromDisplaySide(side),
-        line + 1,
-        null,
-        null,
-        CommentRange.create(fromTo)), side);
-  }
-
-  CommentInfo createReply(CommentInfo replyTo) {
-    if (!replyTo.has_line() && replyTo.range() == null) {
-      return CommentInfo.createFile(path, replyTo.side(), replyTo.id(), null);
-    } else {
-      return CommentInfo.createRange(path, replyTo.side(), replyTo.line(),
-          replyTo.id(), null, replyTo.range());
-    }
-  }
-
-  DraftBox addDraftBox(CommentInfo info, DisplaySide side) {
-    CodeMirror cm = getCmFromSide(side);
-    final DraftBox box = new DraftBox(this, cm, side, commentLinkProcessor,
-        getPatchSetIdFromSide(side), info);
-    if (info.id() == null) {
-      Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-        @Override
-        public void execute() {
-          box.setOpen(true);
-          box.setEdit(true);
-        }
-      });
-    }
-    if (!info.has_line()) {
-      return box;
-    }
-    addCommentBox(info, box);
-    box.setVisible(true);
-    LineHandle handle = cm.getLineHandle(info.line() - 1);
-    lineActiveBoxMap.put(handle, box);
-    return box;
-  }
-
-  CommentBox addCommentBox(CommentInfo info, final CommentBox box) {
-    box.setParent(this);
-    diffTable.add(box);
-    DisplaySide side = box.getSide();
-    CodeMirror cm = getCmFromSide(side);
-    CodeMirror other = otherCm(cm);
-    int line = info.line() - 1; // CommentInfo is 1-based, but CM is 0-based
-    LineHandle handle = cm.getLineHandle(line);
-    PaddingManager manager;
-    if (linePaddingManagerMap.containsKey(handle)) {
-      manager = linePaddingManagerMap.get(handle);
-    } else {
-      // Estimated height at 28px, fixed by deferring after display
-      manager = new PaddingManager(addPaddingWidget(cm, line, 0, Unit.PX, 0));
-      linePaddingManagerMap.put(handle, manager);
-    }
-    int lineToPad = mapper.lineOnOther(side, line).getLine();
-    LineHandle otherHandle = other.getLineHandle(lineToPad);
-    DiffChunkInfo myChunk = getDiffChunk(side, line);
-    DiffChunkInfo otherChunk = getDiffChunk(getSideFromCm(other), lineToPad);
-    PaddingManager otherManager;
-    if (linePaddingManagerMap.containsKey(otherHandle)) {
-      otherManager = linePaddingManagerMap.get(otherHandle);
-    } else {
-      otherManager =
-          new PaddingManager(addPaddingWidget(other, lineToPad, 0, Unit.PX, 0));
-      linePaddingManagerMap.put(otherHandle, otherManager);
-    }
-    if ((myChunk == null && otherChunk == null) || (myChunk != null && otherChunk != null)) {
-      PaddingManager.link(manager, otherManager);
-    }
-    int index = manager.getCurrentCount();
-    manager.insert(box, index);
-    Configuration config = Configuration.create()
-      .set("coverGutter", true)
-      .set("insertAt", index)
-      .set("noHScroll", true);
-    LineWidget boxWidget = addLineWidget(cm, line, box, config);
-    box.setPaddingManager(manager);
-    box.setSelfWidgetWrapper(new PaddingWidgetWrapper(boxWidget, box.getElement()));
-    if (otherChunk == null) {
-      box.setDiffChunkInfo(myChunk);
-    }
-    box.setGutterWrapper(diffTable.sidePanel.addGutter(cm, info.line() - 1,
-        box instanceof DraftBox
-          ? SidePanel.GutterType.DRAFT
-          : SidePanel.GutterType.COMMENT));
-    if (box instanceof DraftBox) {
-      boxWidget.onRedraw(new Runnable() {
-        @Override
-        public void run() {
-          DraftBox draftBox = (DraftBox) box;
-          if (draftBox.isEdit()) {
-            draftBox.editArea.setFocus(true);
-          }
-        }
-      });
-    }
-    return box;
-  }
-
-  void removeDraft(DraftBox box, int line) {
-    LineHandle handle = getCmFromSide(box.getSide()).getLineHandle(line);
-    lineActiveBoxMap.remove(handle);
-    if (linePublishedBoxesMap.containsKey(handle)) {
-      List<PublishedBox> list = linePublishedBoxesMap.get(handle);
-      lineActiveBoxMap.put(handle, list.get(list.size() - 1));
-    }
-    unsaved.remove(box);
-  }
-
-  void updateUnsaved(DraftBox box, boolean isUnsaved) {
-    if (isUnsaved) {
-      unsaved.add(box);
-    } else {
-      unsaved.remove(box);
-    }
-  }
-
-  private void saveAllDrafts(CallbackGroup cb) {
-    for (DraftBox box : unsaved) {
-      box.save(cb);
-    }
-  }
-
-  void addFileCommentBox(CommentBox box) {
-    diffTable.addFileCommentBox(box);
-  }
-
-  void removeFileCommentBox(DraftBox box) {
-    diffTable.onRemoveDraftBox(box);
-  }
-
-  private List<CommentInfo> sortComment(JsArray<CommentInfo> unsorted) {
-    List<CommentInfo> sorted = new ArrayList<CommentInfo>();
-    for (int i = 0; i < unsorted.length(); i++) {
-      sorted.add(unsorted.get(i));
-    }
-    Collections.sort(sorted, new Comparator<CommentInfo>() {
-      @Override
-      public int compare(CommentInfo o1, CommentInfo o2) {
-        return o1.updated().compareTo(o2.updated());
-      }
-    });
-    return sorted;
-  }
-
-  private void renderPublished(JsArray<CommentInfo> published) {
-    List<CommentInfo> sorted = sortComment(published);
-    for (CommentInfo info : sorted) {
-      DisplaySide side;
-      if (info.side() == Side.PARENT) {
-        if (base != null) {
-          continue;
-        }
-        side = DisplaySide.A;
-      } else {
-        side = published == publishedBase ? DisplaySide.A : DisplaySide.B;
-      }
-      CodeMirror cm = getCmFromSide(side);
-      PublishedBox box = new PublishedBox(this, cm, side, commentLinkProcessor,
-          getPatchSetIdFromSide(side), info);
-      publishedMap.put(info.id(), box);
-      if (!info.has_line()) {
-        diffTable.addFileCommentBox(box);
-        continue;
-      }
-      int line = info.line() - 1;
-      LineHandle handle = cm.getLineHandle(line);
-      if (linePublishedBoxesMap.containsKey(handle)) {
-        linePublishedBoxesMap.get(handle).add(box);
-      } else {
-        List<PublishedBox> list = new ArrayList<PublishedBox>();
-        list.add(box);
-        linePublishedBoxesMap.put(handle, list);
-      }
-      lineActiveBoxMap.put(handle, box);
-      addCommentBox(info, box);
-    }
-  }
-
-  private void renderDrafts(JsArray<CommentInfo> drafts) {
-    List<CommentInfo> sorted = sortComment(drafts);
-    for (CommentInfo info : sorted) {
-      DisplaySide side;
-      if (info.side() == Side.PARENT) {
-        if (base != null) {
-          continue;
-        }
-        side = DisplaySide.A;
-      } else {
-        side = drafts == draftsBase ? DisplaySide.A : DisplaySide.B;
-      }
-      DraftBox box = new DraftBox(
-          this, getCmFromSide(side), side, commentLinkProcessor,
-          getPatchSetIdFromSide(side), info);
-      if (publishedBase != null || publishedRevision != null) {
-        PublishedBox replyToBox = publishedMap.get(info.in_reply_to());
-        if (replyToBox != null) {
-          replyToBox.registerReplyBox(box);
-        }
-      }
-      if (!info.has_line()) {
-        diffTable.addFileCommentBox(box);
-        continue;
-      }
-      lineActiveBoxMap.put(
-          getCmFromSide(side).getLineHandle(info.line() - 1), box);
-      addCommentBox(info, box);
-    }
-  }
-
-  private void renderSkips() {
-    if (context == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
-      return;
-    }
-
-    /**
-     * TODO: This is not optimal, but shouldn't bee too costly in most cases.
-     * Maybe rewrite after done keeping track of diff chunk positions.
-     */
-    for (CommentBox box : lineActiveBoxMap.values()) {
-      List<SkippedLine> temp = new ArrayList<SkippedLine>();
-      for (SkippedLine skip : skips) {
-        CommentInfo info = box.getCommentInfo();
-        int startLine = box.getSide() == DisplaySide.A
-            ? skip.getStartA()
-            : skip.getStartB();
-        int boxLine = info.line();
-        int deltaBefore = boxLine - startLine;
-        int deltaAfter = startLine + skip.getSize() - boxLine;
-        if (deltaBefore < -context || deltaAfter < -context) {
-          temp.add(skip); // Size guaranteed to be greater than 1
-        } else if (deltaBefore > context && deltaAfter > context) {
-          SkippedLine before = new SkippedLine(
-              skip.getStartA(), skip.getStartB(),
-              skip.getSize() - deltaAfter - context);
-          skip.incrementStart(deltaBefore + context);
-          checkAndAddSkip(temp, before);
-          checkAndAddSkip(temp, skip);
-        } else if (deltaAfter > context) {
-          skip.incrementStart(deltaBefore + context);
-          checkAndAddSkip(temp, skip);
-        } else if (deltaBefore > context) {
-          skip.reduceSize(deltaAfter + context);
-          checkAndAddSkip(temp, skip);
-        }
-      }
-      if (temp.isEmpty()) {
-        return;
-      }
-      skips = temp;
-    }
-    for (SkippedLine skip : skips) {
-      SkipBar barA = renderSkipHelper(cmA, skip);
-      SkipBar barB = renderSkipHelper(cmB, skip);
-      SkipBar.link(barA, barB);
-    }
-  }
-
-  private void checkAndAddSkip(List<SkippedLine> list, SkippedLine toAdd) {
-    if (toAdd.getSize() > 1) {
-      list.add(toAdd);
-    }
-  }
-
-  private SkipBar renderSkipHelper(CodeMirror cm, SkippedLine skip) {
-    int size = skip.getSize();
-    int markStart = cm == cmA ? skip.getStartA() : skip.getStartB();
-    int markEnd = markStart + size - 1;
-    SkipBar bar = new SkipBar(cm);
-    diffTable.add(bar);
-    Configuration markerConfig = Configuration.create()
-        .set("collapsed", true)
-        .set("inclusiveLeft", true)
-        .set("inclusiveRight", true);
-    Configuration lineWidgetConfig = Configuration.create()
-        .set("coverGutter", true)
-        .set("noHScroll", true);
-    if (markStart == 0) {
-      bar.setWidget(addLineWidget(
-          cm, markEnd + 1, bar, lineWidgetConfig.set("above", true)));
-    } else {
-      bar.setWidget(addLineWidget(
-          cm, markStart - 1, bar, lineWidgetConfig));
-    }
-    bar.setMarker(cm.markText(CodeMirror.pos(markStart, 0),
-        CodeMirror.pos(markEnd), markerConfig), size);
-    return bar;
-  }
-
-  private CodeMirror otherCm(CodeMirror me) {
+  CodeMirror otherCm(CodeMirror me) {
     return me == cmA ? cmB : cmA;
   }
 
-  private PatchSet.Id getPatchSetIdFromSide(DisplaySide side) {
-    return side == DisplaySide.A && base != null ? base : revision;
-  }
-
-  private CodeMirror getCmFromSide(DisplaySide side) {
+  CodeMirror getCmFromSide(DisplaySide side) {
     return side == DisplaySide.A ? cmA : cmB;
   }
 
-  private DisplaySide getSideFromCm(CodeMirror cm) {
-    return cm == cmA ? DisplaySide.A : DisplaySide.B;
-  }
-
-  Side getStoredSideFromDisplaySide(DisplaySide side) {
-    return side == DisplaySide.A && base == null ? Side.PARENT : Side.REVISION;
-  }
-
-  private void markEdit(CodeMirror cm, JsArrayString lines,
-      JsArray<Span> edits, int startLine) {
-    if (edits == null) {
-      return;
-    }
-    EditIterator iter = new EditIterator(lines, startLine);
-    Configuration intralineBgOpt = Configuration.create()
-        .set("className", DiffTable.style.intralineBg())
-        .set("readOnly", true);
-    Configuration diffOpt = Configuration.create()
-        .set("className", DiffTable.style.diff())
-        .set("readOnly", true);
-    LineCharacter last = CodeMirror.pos(0, 0);
-    for (int i = 0; i < edits.length(); i++) {
-      Span span = edits.get(i);
-      LineCharacter from = iter.advance(span.skip());
-      LineCharacter to = iter.advance(span.mark());
-      int fromLine = from.getLine();
-      if (last.getLine() == fromLine) {
-        cm.markText(last, from, intralineBgOpt);
-      } else {
-        cm.markText(CodeMirror.pos(fromLine, 0), from, intralineBgOpt);
-      }
-      cm.markText(from, to, diffOpt);
-      last = to;
-      for (int line = fromLine; line < to.getLine(); line++) {
-        cm.addLineClass(line, LineClassWhere.BACKGROUND,
-            DiffTable.style.diff());
-      }
-    }
-  }
-
-  private void colorLines(CodeMirror cm, String color, int line, int cnt) {
-    for (int i = 0; i < cnt; i++) {
-      cm.addLineClass(line + i, LineClassWhere.WRAP, color);
-    }
-  }
-
-  private void addDiffChunkAndPadding(CodeMirror cmToPad, int lineToPad,
-      int lineOnOther, int chunkSize, boolean edit) {
-    CodeMirror otherCm = otherCm(cmToPad);
-    linePaddingOnOtherSideMap.put(otherCm.getLineHandle(lineOnOther),
-        new LinePaddingWidgetWrapper(addPaddingWidget(cmToPad,
-            lineToPad, 0, Unit.EM, null), lineToPad, chunkSize));
-    diffChunks.add(new DiffChunkInfo(getSideFromCm(otherCm),
-        lineOnOther - chunkSize + 1, lineOnOther, edit));
-  }
-
-  private PaddingWidgetWrapper addPaddingWidget(CodeMirror cm,
-      int line, double height, Unit unit, Integer index) {
-    SimplePanel padding = new SimplePanel();
-    padding.getElement().getStyle().setHeight(height, unit);
-    Configuration config = Configuration.create()
-        .set("coverGutter", true)
-        .set("above", line == -1)
-        .set("noHScroll", true);
-    if (index != null) {
-      config = config.set("insertAt", index);
-    }
-    LineWidget widget = addLineWidget(cm, line == -1 ? 0 : line, padding, config);
-    return new PaddingWidgetWrapper(widget, padding.getElement());
-  }
-
-  /**
-   * A LineWidget needs to be added to diffTable in order to respond to browser
-   * events, but CodeMirror doesn't render the widget until the containing line
-   * is scrolled into viewportMargin, causing it to appear at the bottom of the
-   * DOM upon loading. Fix by hiding the widget until it is first scrolled into
-   * view (when CodeMirror fires a "redraw" event on the widget).
-   */
-  private LineWidget addLineWidget(CodeMirror cm, int line,
-      final Widget widget, Configuration options) {
-    widget.setVisible(false);
-    LineWidget lineWidget = cm.addLineWidget(line, widget.getElement(), options);
-    lineWidget.onFirstRedraw(new Runnable() {
-      @Override
-      public void run() {
-        widget.setVisible(true);
-      }
-    });
-    return lineWidget;
+  LineOnOtherInfo lineOnOther(DisplaySide side, int line) {
+    return chunkManager.getLineMapper().lineOnOther(side, line);
   }
 
   private void clearActiveLine(CodeMirror cm) {
@@ -977,53 +672,41 @@
     }
   }
 
-  private Runnable adjustGutters(final CodeMirror cm) {
-    return new Runnable() {
-      @Override
-      public void run() {
-        Viewport fromTo = cm.getViewport();
-        int size = fromTo.getTo() - fromTo.getFrom() + 1;
-        if (cm.getOldViewportSize() == size) {
-          return;
-        }
-        cm.setOldViewportSize(size);
-        diffTable.sidePanel.adjustGutters(cmB);
-      }
-    };
-  }
-
   private Runnable updateActiveLine(final CodeMirror cm) {
     final CodeMirror other = otherCm(cm);
     return new Runnable() {
       public void run() {
-        /**
-         * The rendering of active lines has to be deferred. Reflow
-         * caused by adding and removing styles chokes Firefox when arrow
-         * key (or j/k) is held down. Performance on Chrome is fine
-         * without the deferral.
-         */
-        defer(new Runnable() {
+        // The rendering of active lines has to be deferred. Reflow
+        // caused by adding and removing styles chokes Firefox when arrow
+        // key (or j/k) is held down. Performance on Chrome is fine
+        // without the deferral.
+        //
+        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
           @Override
-          public void run() {
-            LineHandle handle = cm.getLineHandleVisualStart(
-                cm.getCursor("end").getLine());
-            if (cm.hasActiveLine() && cm.getActiveLine().equals(handle)) {
-              return;
-            }
+          public void execute() {
+            operation(new Runnable() {
+              public void run() {
+                LineHandle handle = cm.getLineHandleVisualStart(
+                    cm.getCursor("end").getLine());
+                if (cm.hasActiveLine() && cm.getActiveLine().equals(handle)) {
+                  return;
+                }
 
-            clearActiveLine(cm);
-            clearActiveLine(other);
-            cm.setActiveLine(handle);
-            cm.addLineClass(
-                handle, LineClassWhere.WRAP, DiffTable.style.activeLine());
-            LineOnOtherInfo info =
-                mapper.lineOnOther(getSideFromCm(cm), cm.getLineNumber(handle));
-            if (info.isAligned()) {
-              LineHandle oLineHandle = other.getLineHandle(info.getLine());
-              other.setActiveLine(oLineHandle);
-              other.addLineClass(oLineHandle, LineClassWhere.WRAP,
-                  DiffTable.style.activeLine());
-            }
+                clearActiveLine(cm);
+                clearActiveLine(other);
+                cm.setActiveLine(handle);
+                cm.addLineClass(
+                    handle, LineClassWhere.WRAP, DiffTable.style.activeLine());
+                LineOnOtherInfo info =
+                    lineOnOther(cm.side(), cm.getLineNumber(handle));
+                if (info.isAligned()) {
+                  LineHandle oLineHandle = other.getLineHandle(info.getLine());
+                  other.setActiveLine(oLineHandle);
+                  other.addLineClass(oLineHandle, LineClassWhere.WRAP,
+                      DiffTable.style.activeLine());
+                }
+              }
+            });
           }
         });
       }
@@ -1035,55 +718,21 @@
       @Override
       public void handle(CodeMirror instance, int line, String gutter,
           NativeEvent clickEvent) {
-        if (!(cm.hasActiveLine() &&
-            cm.getLineNumber(cm.getActiveLine()) == line)) {
-          cm.setCursor(LineCharacter.create(line));
-        }
-        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-          @Override
-          public void execute() {
-            insertNewDraft(cm).run();
+        if (clickEvent.getButton() == NativeEvent.BUTTON_LEFT
+            && !clickEvent.getMetaKey()
+            && !clickEvent.getAltKey()
+            && !clickEvent.getCtrlKey()
+            && !clickEvent.getShiftKey()) {
+          if (!(cm.hasActiveLine() &&
+              cm.getLineNumber(cm.getActiveLine()) == line)) {
+            cm.setCursor(LineCharacter.create(line));
           }
-        });
-      }
-    };
-  }
-
-  private Runnable insertNewDraft(final CodeMirror cm) {
-    if (!Gerrit.isSignedIn()) {
-      return new Runnable() {
-        @Override
-        public void run() {
-          Gerrit.doSignIn(getToken());
-        }
-     };
-    }
-    return new Runnable() {
-      public void run() {
-        LineHandle handle = cm.getActiveLine();
-        int line = cm.getLineNumber(handle);
-        CommentBox box = lineActiveBoxMap.get(handle);
-        FromTo fromTo = cm.getSelectedRange();
-        if (cm.somethingSelected()) {
-          lineActiveBoxMap.put(handle,
-              addNewDraft(cm, line, fromTo.getTo().getLine() == line ? fromTo : null));
-        } else if (box == null) {
-          lineActiveBoxMap.put(handle, addNewDraft(cm, line, null));
-        } else if (box instanceof DraftBox) {
-          ((DraftBox) box).setEdit(true);
-        } else {
-          ((PublishedBox) box).doReply();
-        }
-      }
-    };
-  }
-
-  private Runnable toggleOpenBox(final CodeMirror cm) {
-    return new Runnable() {
-      public void run() {
-        CommentBox box = lineActiveBoxMap.get(cm.getActiveLine());
-        if (box != null) {
-          box.setOpen(!box.isOpen());
+          Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+            @Override
+            public void execute() {
+              commentManager.insertNewDraft(cm).run();
+            }
+          });
         }
       }
     };
@@ -1092,73 +741,42 @@
   private Runnable upToChange(final boolean openReplyBox) {
     return new Runnable() {
       public void run() {
-        if (unsaved.isEmpty()) {
-          goUpToChange(openReplyBox);
-        } else {
-          CallbackGroup group = new CallbackGroup();
-          saveAllDrafts(group);
-          group.addFinal(new GerritCallback<Void>() {
-            @Override
-            public void onSuccess(Void result) {
-              goUpToChange(openReplyBox);
-            }
-          }).onSuccess(null);
-        }
+        CallbackGroup group = new CallbackGroup();
+        commentManager.saveAllDrafts(group);
+        group.done();
+        group.addListener(new GerritCallback<Void>() {
+          @Override
+          public void onSuccess(Void result) {
+            String b = base != null ? String.valueOf(base.get()) : null;
+            String rev = String.valueOf(revision.get());
+            Gerrit.display(
+              PageLinks.toChange(changeId, b, rev),
+              new ChangeScreen2(changeId, b, rev, openReplyBox));
+          }
+        });
       }
     };
   }
 
-  private void goUpToChange(boolean openReplyBox) {
-    String b = base != null ? String.valueOf(base.get()) : null;
-    String rev = String.valueOf(revision.get());
-    Gerrit.display(
-      PageLinks.toChange(changeId, rev),
-      new ChangeScreen2(changeId, b, rev, openReplyBox));
-  }
-
-  private Runnable openClosePublished(final CodeMirror cm) {
-    return new Runnable() {
-      @Override
-      public void run() {
-        if (cm.hasActiveLine()) {
-          List<PublishedBox> list =
-              linePublishedBoxesMap.get(cm.getActiveLine());
-          if (list == null) {
-            return;
-          }
-          boolean open = false;
-          for (PublishedBox box : list) {
-            if (!box.isOpen()) {
-              open = true;
-              break;
-            }
-          }
-          for (PublishedBox box : list) {
-            box.setOpen(open);
-          }
+  private Runnable moveCursorToSide(final CodeMirror cmSrc, DisplaySide sideDst) {
+    final CodeMirror cmDst = getCmFromSide(sideDst);
+    if (cmDst == cmSrc) {
+      return new Runnable() {
+        @Override
+        public void run() {
         }
-      }
-    };
-  }
+      };
+    }
 
-  private Runnable toggleReviewed() {
-    return new Runnable() {
-     public void run() {
-       header.setReviewed(!header.isReviewed());
-     }
-    };
-  }
-
-  private Runnable flipCursorSide(final CodeMirror cm, final boolean toLeft) {
+    final DisplaySide sideSrc = cmSrc.side();
     return new Runnable() {
       public void run() {
-        if (cm.hasActiveLine() && (toLeft && cm == cmB || !toLeft && cm == cmA)) {
-          CodeMirror other = otherCm(cm);
-          other.setCursor(LineCharacter.create(
-              mapper.lineOnOther(
-                  getSideFromCm(cm), cm.getLineNumber(cm.getActiveLine())).getLine()));
-          other.focus();
+        if (cmSrc.hasActiveLine()) {
+          cmDst.setCursor(LineCharacter.create(lineOnOther(
+              sideSrc,
+              cmSrc.getLineNumber(cmSrc.getActiveLine())).getLine()));
         }
+        cmDst.focus();
       }
     };
   }
@@ -1170,189 +788,29 @@
         if (cm.hasVimSearchHighlight()) {
           CodeMirror.handleVimKey(cm, "n");
         } else {
-          diffChunkNav(cm, false).run();
+          chunkManager.diffChunkNav(cm, Direction.NEXT).run();
         }
       }
     };
   }
 
-  private Runnable diffChunkNav(final CodeMirror cm, final boolean prev) {
-    return new Runnable() {
-      @Override
-      public void run() {
-        int line = cm.hasActiveLine() ? cm.getLineNumber(cm.getActiveLine()) : 0;
-        int res = Collections.binarySearch(
-                diffChunks,
-                new DiffChunkInfo(getSideFromCm(cm), line, 0, false),
-                getDiffChunkComparator());
-        if (res < 0) {
-          res = -res - (prev ? 1 : 2);
-        }
-
-        res = res + (prev ? -1 : 1);
-        DiffChunkInfo lookUp = diffChunks.get(getWrapAroundDiffChunkIndex(res));
-        // If edit, skip the deletion chunk and set focus on the insertion one.
-        if (lookUp.isEdit() && lookUp.getSide() == DisplaySide.A) {
-          res = res + (prev ? -1 : 1);
-        }
-        DiffChunkInfo target = diffChunks.get(getWrapAroundDiffChunkIndex(res));
-        CodeMirror targetCm = getCmFromSide(target.getSide());
-        targetCm.setCursor(LineCharacter.create(target.getStart()));
-        targetCm.focus();
-        targetCm.scrollToY(
-            targetCm.heightAtLine(target.getStart(), "local") -
-            0.5 * cmB.getScrollbarV().getClientHeight());
-      }
-    };
-  }
-
-  /**
-   * Diff chunks are ordered by their starting lines. If it's a deletion,
-   * use its corresponding line on the revision side for comparison. In
-   * the edit case, put the deletion chunk right before the insertion chunk.
-   * This placement guarantees well-ordering.
-   */
-  private Comparator<DiffChunkInfo> getDiffChunkComparator() {
-    return new Comparator<DiffChunkInfo>() {
-      @Override
-      public int compare(DiffChunkInfo o1, DiffChunkInfo o2) {
-        if (o1.getSide() == o2.getSide()) {
-          return o1.getStart() - o2.getStart();
-        } else if (o1.getSide() == DisplaySide.A) {
-          int comp = mapper.lineOnOther(o1.getSide(), o1.getStart())
-              .getLine() - o2.getStart();
-          return comp == 0 ? -1 : comp;
-        } else {
-          int comp = o1.getStart() -
-              mapper.lineOnOther(o2.getSide(), o2.getStart()).getLine();
-          return comp == 0 ? 1 : comp;
-        }
-      }
-    };
-  }
-
-  private DiffChunkInfo getDiffChunk(DisplaySide side, int line) {
-    int res = Collections.binarySearch(
-        diffChunks,
-        new DiffChunkInfo(side, line, 0, false), // Dummy DiffChunkInfo
-        getDiffChunkComparator());
-    if (res >= 0) {
-      return diffChunks.get(res);
-    } else { // The line might be within a DiffChunk
-      res = -res - 1;
-      if (res > 0) {
-        DiffChunkInfo info = diffChunks.get(res - 1);
-        if (info.getSide() == side && info.getStart() <= line &&
-            line <= info.getEnd()) {
-          return info;
-        }
-      }
+  void updateRenderEntireFile() {
+    cmA.removeKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
+    cmB.removeKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
+    if (prefs.renderEntireFile()) {
+      cmA.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
+      cmB.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
     }
-    return null;
-  }
 
-  private int getWrapAroundDiffChunkIndex(int index) {
-    return (index + diffChunks.size()) % diffChunks.size();
-  }
-
-  void defer(Runnable thunk) {
-    if (deferred == null) {
-      final ArrayList<Runnable> list = new ArrayList<Runnable>();
-      deferred = list;
-      Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-        @Override
-        public void execute() {
-          deferred = null;
-          cmA.operation(new Runnable() {
-            @Override
-            public void run() {
-              cmB.operation(new Runnable() {
-                @Override
-                public void run() {
-                  for (Runnable thunk : list) {
-                    thunk.run();
-                  }
-                }
-              });
-            }
-          });
-        }
-      });
-    }
-    deferred.add(thunk);
-  }
-
-  void resizePaddingOnOtherSide(DisplaySide mySide, int line) {
-    CodeMirror cm = getCmFromSide(mySide);
-    LineHandle handle = cm.getLineHandle(line);
-    final LinePaddingWidgetWrapper otherWrapper = linePaddingOnOtherSideMap.get(handle);
-    double myChunkHeight = cm.heightAtLine(line + 1) -
-        cm.heightAtLine(line - otherWrapper.getChunkLength() + 1);
-    Element otherPadding = otherWrapper.getElement();
-    int otherPaddingHeight = otherPadding.getOffsetHeight();
-    CodeMirror otherCm = otherCm(cm);
-    int otherLine = otherWrapper.getOtherLine();
-    LineHandle other = otherCm.getLineHandle(otherLine);
-    if (linePaddingOnOtherSideMap.containsKey(other)) {
-      LinePaddingWidgetWrapper myWrapper = linePaddingOnOtherSideMap.get(other);
-      Element myPadding = linePaddingOnOtherSideMap.get(other).getElement();
-      int myPaddingHeight = myPadding.getOffsetHeight();
-      myChunkHeight -= myPaddingHeight;
-      double otherChunkHeight = otherCm.heightAtLine(otherLine + 1) -
-          otherCm.heightAtLine(otherLine - myWrapper.getChunkLength() + 1) -
-          otherPaddingHeight;
-      double delta = myChunkHeight - otherChunkHeight;
-      if (delta > 0) {
-        if (myPaddingHeight != 0) {
-          setHeightInPx(myPadding, 0);
-          myWrapper.getWidget().changed();
-        }
-        if (otherPaddingHeight != delta) {
-          setHeightInPx(otherPadding, delta);
-          otherWrapper.getWidget().changed();
-        }
-      } else {
-        if (myPaddingHeight != -delta) {
-          setHeightInPx(myPadding, -delta);
-          myWrapper.getWidget().changed();
-        }
-        if (otherPaddingHeight != 0) {
-          setHeightInPx(otherPadding, 0);
-          otherWrapper.getWidget().changed();
-        }
-      }
-    } else if (otherPaddingHeight != myChunkHeight) {
-      setHeightInPx(otherPadding, myChunkHeight);
-      otherWrapper.getWidget().changed();
-    }
-  }
-
-  // TODO: Maybe integrate this with PaddingManager.
-  private RenderLineHandler resizeLinePadding(final DisplaySide side) {
-    return new RenderLineHandler() {
-      @Override
-      public void handle(final CodeMirror instance, final LineHandle handle,
-          Element element) {
-        if (lineActiveBoxMap.containsKey(handle)) {
-          lineActiveBoxMap.get(handle).resizePaddingWidget();
-        }
-        if (linePaddingOnOtherSideMap.containsKey(handle)) {
-          defer(new Runnable() {
-            @Override
-            public void run() {
-              resizePaddingOnOtherSide(side, instance.getLineNumber(handle));
-            }
-          });
-        }
-      }
-    };
+    cmA.setOption("viewportMargin", prefs.renderEntireFile() ? POSITIVE_INFINITY : 10);
+    cmB.setOption("viewportMargin", prefs.renderEntireFile() ? POSITIVE_INFINITY : 10);
   }
 
   void resizeCodeMirror() {
     int height = getCodeMirrorHeight();
     cmA.setHeight(height);
     cmB.setHeight(height);
-    diffTable.sidePanel.adjustGutters(cmB);
+    diffTable.overview.refresh();
   }
 
   private int getCodeMirrorHeight() {
@@ -1363,24 +821,35 @@
     return Window.getClientHeight() - rest;
   }
 
-  static void setHeightInPx(Element ele, double height) {
-    ele.getStyle().setHeight(height, Unit.PX);
-  }
-
   private String getContentType(DiffInfo.FileMeta meta) {
-    return pref.isSyntaxHighlighting()
+    return prefs.syntaxHighlighting()
           && meta != null
           && meta.content_type() != null
         ? ModeInjector.getContentType(meta.content_type())
         : null;
   }
 
-  CodeMirror getCmA() {
-    return cmA;
+  private void injectMode(DiffInfo diffInfo, AsyncCallback<Void> cb) {
+    new ModeInjector()
+      .add(getContentType(diffInfo.meta_a()))
+      .add(getContentType(diffInfo.meta_b()))
+      .inject(cb);
   }
 
-  CodeMirror getCmB() {
-    return cmB;
+  DiffPreferences getPrefs() {
+    return prefs;
+  }
+
+  ChunkManager getChunkManager() {
+    return chunkManager;
+  }
+
+  CommentManager getCommentManager() {
+    return commentManager;
+  }
+
+  SkipManager getSkipManager() {
+    return skipManager;
   }
 
   void operation(final Runnable apply) {
@@ -1403,8 +872,8 @@
       DiffApi.diff(revision, nextPath)
         .base(base)
         .wholeFile()
-        .intraline(pref.isIntralineDifference())
-        .ignoreWhitespace(pref.getIgnoreWhitespace())
+        .intraline(prefs.intralineDifference())
+        .ignoreWhitespace(prefs.ignoreWhitespace())
         .get(new AsyncCallback<DiffInfo>() {
           @Override
           public void onSuccess(DiffInfo info) {
@@ -1420,4 +889,37 @@
         });
     }
   }
+
+  void reloadDiffInfo() {
+    final int id = ++reloadVersionId;
+    DiffApi.diff(revision, path)
+      .base(base)
+      .wholeFile()
+      .intraline(prefs.intralineDifference())
+      .ignoreWhitespace(prefs.ignoreWhitespace())
+      .get(new GerritCallback<DiffInfo>() {
+        @Override
+        public void onSuccess(DiffInfo diffInfo) {
+          if (id == reloadVersionId && isAttached()) {
+            diff = diffInfo;
+            operation(new Runnable() {
+              @Override
+              public void run() {
+                skipManager.removeAll();
+                chunkManager.reset();
+                diffTable.overview.clearDiffMarkers();
+                setShowIntraline(prefs.intralineDifference());
+                render(diff);
+                skipManager.render(prefs.context(), diff);
+              }
+            });
+          }
+        }
+      });
+  }
+
+  private static boolean isLargeFile(DiffInfo diffInfo) {
+    return (diffInfo.meta_a() != null && diffInfo.meta_a().lines() > 500)
+        || (diffInfo.meta_b() != null && diffInfo.meta_b().lines() > 500);
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml
index 1bc707a..8c26873 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.ui.xml
@@ -17,7 +17,13 @@
 <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'
     xmlns:d='urn:import:com.google.gerrit.client.diff'>
-  <g:FlowPanel>
+  <ui:style>
+    .sbs2 {
+      margin-left: -5px;
+      margin-right: -5px;
+    }
+  </ui:style>
+  <g:FlowPanel styleName='{style.sbs2}'>
     <d:Header ui:field='header'/>
     <d:DiffTable ui:field='diffTable'/>
   </g:FlowPanel>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.java
deleted file mode 100644
index 6a19b30..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SidePanel.java
+++ /dev/null
@@ -1,165 +0,0 @@
-//Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.client.diff;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-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.ui.Composite;
-import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.Label;
-
-import net.codemirror.lib.CodeMirror;
-import net.codemirror.lib.LineCharacter;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** The Widget that handles the scrollbar gutters */
-class SidePanel extends Composite {
-  interface Binder extends UiBinder<HTMLPanel, SidePanel> {}
-  private static final Binder uiBinder = GWT.create(Binder.class);
-
-  interface SidePanelStyle extends CssResource {
-    String gutter();
-    String halfGutter();
-    String comment();
-    String draft();
-    String insert();
-    String delete();
-  }
-
-  enum GutterType {
-    COMMENT, DRAFT, INSERT, DELETE, EDIT;
-  }
-
-  @UiField
-  SidePanelStyle style;
-
-  private List<GutterWrapper> gutters;
-  private CodeMirror cmB;
-
-  SidePanel() {
-    initWidget(uiBinder.createAndBindUi(this));
-    this.gutters = new ArrayList<GutterWrapper>();
-  }
-
-  GutterWrapper addGutter(CodeMirror cm, int line, GutterType type) {
-    Label gutter = new Label();
-    GutterWrapper info = new GutterWrapper(this, gutter, cm, line, type);
-    adjustGutter(info);
-    gutter.addStyleName(style.gutter());
-    switch (type) {
-      case COMMENT:
-        gutter.addStyleName(style.comment());
-        break;
-      case DRAFT:
-        gutter.addStyleName(style.draft());
-        gutter.setText("*");
-        break;
-      case INSERT:
-        gutter.addStyleName(style.insert());
-        break;
-      case DELETE:
-        gutter.addStyleName(style.delete());
-        break;
-      case EDIT:
-        gutter.addStyleName(style.insert());
-        Label labelLeft = new Label();
-        labelLeft.addStyleName(style.halfGutter());
-        gutter.getElement().appendChild(labelLeft.getElement());
-    }
-    ((HTMLPanel) getWidget()).add(gutter);
-    gutters.add(info);
-    return info;
-  }
-
-  void adjustGutters(CodeMirror cmB) {
-    this.cmB = cmB;
-    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-      @Override
-      public void execute() {
-        for (GutterWrapper info : gutters) {
-          adjustGutter(info);
-        }
-      }
-    });
-  }
-
-  private void adjustGutter(GutterWrapper wrapper) {
-    if (cmB == null) {
-      return;
-    }
-    final CodeMirror cm = wrapper.cm;
-    final int line = wrapper.line;
-    Label gutter = wrapper.gutter;
-    final double height = cm.heightAtLine(line, "local");
-    final double scrollbarHeight = cmB.getScrollbarV().getClientHeight();
-    double top = height / (double) cmB.getSizer().getClientHeight() *
-        scrollbarHeight +
-        cmB.getScrollbarV().getAbsoluteTop();
-    if (top == 0) {
-      top = -10;
-    }
-    gutter.getElement().getStyle().setTop(top, Unit.PX);
-    wrapper.replaceClickHandler(new ClickHandler() {
-      @Override
-      public void onClick(ClickEvent event) {
-        cm.setCursor(LineCharacter.create(line));
-        cm.scrollToY(height - 0.5 * scrollbarHeight);
-        cm.focus();
-      }
-    });
-  }
-
-  void removeGutter(GutterWrapper wrapper) {
-    gutters.remove(wrapper);
-  }
-
-  static class GutterWrapper {
-    private SidePanel host;
-    private Label gutter;
-    private CodeMirror cm;
-    private int line;
-    private HandlerRegistration regClick;
-
-    GutterWrapper(SidePanel host, Label anchor, CodeMirror cm, int line,
-        GutterType type) {
-      this.host = host;
-      this.gutter = anchor;
-      this.cm = cm;
-      this.line = line;
-    }
-
-    private void replaceClickHandler(ClickHandler newHandler) {
-      if (regClick != null) {
-        regClick.removeHandler();
-      }
-      regClick = gutter.addClickHandler(newHandler);
-    }
-
-    void remove() {
-      gutter.removeFromParent();
-      host.removeGutter(this);
-    }
-  }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
index 9da142e..4efaf54 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
@@ -16,8 +16,6 @@
 
 import com.google.gerrit.client.patches.PatchUtil;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
@@ -41,36 +39,27 @@
   private static final Binder uiBinder = GWT.create(Binder.class);
   private static final int NUM_ROWS_TO_EXPAND = 10;
   private static final int UP_DOWN_THRESHOLD = 30;
-  private static final Configuration MARKER_CONFIG = Configuration.create()
-      .set("collapsed", true)
-      .set("inclusiveLeft", true)
-      .set("inclusiveRight", true);
-
-  private LineWidget widget;
 
   interface SkipBarStyle extends CssResource {
     String noExpand();
   }
 
-  @UiField(provided=true)
-  Anchor skipNum;
+  @UiField(provided=true) Anchor skipNum;
+  @UiField(provided=true) Anchor upArrow;
+  @UiField(provided=true) Anchor downArrow;
+  @UiField SkipBarStyle style;
 
-  @UiField(provided=true)
-  Anchor upArrow;
+  private final SkipManager manager;
+  private final CodeMirror cm;
 
-  @UiField(provided=true)
-  Anchor downArrow;
-
-  @UiField
-  SkipBarStyle style;
-
-  private TextMarker marker;
+  private LineWidget lineWidget;
+  private TextMarker textMarker;
   private SkipBar otherBar;
-  private CodeMirror cm;
-  private int numSkipLines;
 
-  SkipBar(CodeMirror cmInstance) {
-    cm = cmInstance;
+  SkipBar(SkipManager manager, final CodeMirror cm) {
+    this.manager = manager;
+    this.cm = cm;
+
     skipNum = new Anchor(true);
     upArrow = new Anchor(true);
     downArrow = new Anchor(true);
@@ -83,26 +72,52 @@
     }, ClickEvent.getType());
   }
 
-  void setWidget(LineWidget lineWidget) {
-    widget = lineWidget;
-    Scheduler.get().scheduleDeferred(new ScheduledCommand(){
+  void collapse(int start, int end, boolean attach) {
+    if (attach) {
+      boolean isNew = lineWidget == null;
+      Configuration cfg = Configuration.create()
+          .set("coverGutter", true)
+          .set("noHScroll", true);
+      if (start == 0) { // First line workaround
+        lineWidget = cm.addLineWidget(end + 1, getElement(), cfg.set("above", true));
+      } else {
+        lineWidget = cm.addLineWidget(start - 1, getElement(), cfg);
+      }
+      if (isNew) {
+        lineWidget.onFirstRedraw(new Runnable() {
+          @Override
+          public void run() {
+            int w = cm.getGutterElement().getOffsetWidth();
+            getElement().getStyle().setPaddingLeft(w, Unit.PX);
+          }
+        });
+      }
+    }
+
+    textMarker = cm.markText(
+        CodeMirror.pos(start, 0),
+        CodeMirror.pos(end),
+        Configuration.create()
+          .set("collapsed", true)
+          .set("inclusiveLeft", true)
+          .set("inclusiveRight", true));
+
+    textMarker.on("beforeCursorEnter", new Runnable() {
       @Override
-      public void execute() {
-        getElement().getStyle().setPaddingLeft(
-            cm.getGutterElement().getOffsetWidth(), Unit.PX);
+      public void run() {
+        expandAll();
       }
     });
-  }
 
-  void setMarker(TextMarker marker, int length) {
-    this.marker = marker;
-    numSkipLines = length;
-    if (checkAndUpdateArrows()) {
+    int skipped = end - start + 1;
+    if (skipped <= UP_DOWN_THRESHOLD) {
+      addStyleName(style.noExpand());
+    } else {
       upArrow.setHTML(PatchUtil.M.expandBefore(NUM_ROWS_TO_EXPAND));
       downArrow.setHTML(PatchUtil.M.expandAfter(NUM_ROWS_TO_EXPAND));
     }
     skipNum.setText(PatchUtil.M.patchSkipRegion(Integer
-        .toString(length)));
+        .toString(skipped)));
   }
 
   static void link(SkipBar barA, SkipBar barB) {
@@ -110,84 +125,80 @@
     barB.otherBar = barA;
   }
 
-  private void updateSkipNum() {
-    numSkipLines -= NUM_ROWS_TO_EXPAND;
-    skipNum.setText(PatchUtil.M.patchSkipRegion(Integer
-        .toString(numSkipLines)));
-    checkAndUpdateArrows();
-  }
-
-  private boolean checkAndUpdateArrows() {
-    if (numSkipLines <= UP_DOWN_THRESHOLD) {
-      addStyleName(style.noExpand());
-      return false;
-    }
-    return true;
-  }
-
   private void clearMarkerAndWidget() {
-    marker.clear();
-    assert (widget != null);
-    widget.clear();
+    textMarker.clear();
+    lineWidget.clear();
   }
 
-  private void expandAll() {
+  void expandBefore(int cnt) {
+    expandSideBefore(cnt);
+    otherBar.expandSideBefore(cnt);
+    manager.getOverviewBar().refresh();
+  }
+
+  private void expandSideBefore(int cnt) {
+    FromTo range = textMarker.find();
+    int oldStart = range.getFrom().getLine();
+    int newStart = oldStart + cnt;
+    int end = range.getTo().getLine();
+    clearMarkerAndWidget();
+    collapse(newStart, end, true);
+    updateSelection();
+  }
+
+  void expandSideAll() {
     clearMarkerAndWidget();
     removeFromParent();
-    cm.focus();
-  }
-
-  private void expandBefore() {
-    FromTo fromTo = marker.find();
-    int oldStart = fromTo.getFrom().getLine();
-    int newStart = oldStart + NUM_ROWS_TO_EXPAND;
-    int end = fromTo.getTo().getLine();
-    clearMarkerAndWidget();
-    marker = cm.markText(
-        CodeMirror.pos(newStart, 0), CodeMirror.pos(end), MARKER_CONFIG);
-    Configuration config = Configuration.create()
-        .set("coverGutter", true)
-        .set("noHScroll", true);
-    setWidget(cm.addLineWidget(newStart - 1, getElement(), config));
-    updateSkipNum();
-    cm.focus();
   }
 
   private void expandAfter() {
-    FromTo fromTo = marker.find();
-    int start = fromTo.getFrom().getLine();
-    int oldEnd = fromTo.getTo().getLine();
+    FromTo range = textMarker.find();
+    int start = range.getFrom().getLine();
+    int oldEnd = range.getTo().getLine();
     int newEnd = oldEnd - NUM_ROWS_TO_EXPAND;
-    marker.clear();
-    marker = cm.markText(
-        CodeMirror.pos(start, 0), CodeMirror.pos(newEnd), MARKER_CONFIG);
-    if (start == 0) { // First line workaround
-      Configuration config = Configuration.create()
-          .set("coverGutter", true)
-          .set("noHScroll", true)
-          .set("above", true);
-      widget.clear();
-      setWidget(cm.addLineWidget(newEnd + 1, getElement(), config));
+    boolean attach = start == 0;
+    if (attach) {
+      clearMarkerAndWidget();
+    } else {
+      textMarker.clear();
     }
-    updateSkipNum();
-    cm.focus();
+    collapse(start, newEnd, attach);
+    updateSelection();
+  }
+
+  private void updateSelection() {
+    if (cm.somethingSelected()) {
+      FromTo sel = cm.getSelectedRange();
+      cm.setSelection(sel.getFrom(), sel.getTo());
+    }
   }
 
   @UiHandler("skipNum")
   void onExpandAll(ClickEvent e) {
-    otherBar.expandAll();
     expandAll();
+    updateSelection();
+    otherBar.updateSelection();
+    cm.focus();
+  }
+
+  private void expandAll() {
+    expandSideAll();
+    otherBar.expandSideAll();
+    manager.remove(this, otherBar);
+    manager.getOverviewBar().refresh();
   }
 
   @UiHandler("upArrow")
   void onExpandBefore(ClickEvent e) {
-    otherBar.expandBefore();
-    expandBefore();
+    expandBefore(NUM_ROWS_TO_EXPAND);
+    cm.focus();
   }
 
   @UiHandler("downArrow")
   void onExpandAfter(ClickEvent e) {
-    otherBar.expandAfter();
     expandAfter();
+    otherBar.expandAfter();
+    manager.getOverviewBar().refresh();
+    cm.focus();
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java
new file mode 100644
index 0000000..5ba275f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java
@@ -0,0 +1,140 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.diff;
+
+import com.google.gerrit.client.diff.DiffInfo.Region;
+import com.google.gerrit.client.patches.SkippedLine;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gwt.core.client.JsArray;
+
+import net.codemirror.lib.CodeMirror;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Collapses common regions with {@link SkipBar} for {@link SideBySide2}. */
+class SkipManager {
+  private final SideBySide2 host;
+  private final CommentManager commentManager;
+  private Set<SkipBar> skipBars;
+  private SkipBar line0;
+
+  SkipManager(SideBySide2 host, CommentManager commentManager) {
+    this.host = host;
+    this.commentManager = commentManager;
+  }
+
+  OverviewBar getOverviewBar() {
+    return host.diffTable.overview;
+  }
+
+  void render(int context, DiffInfo diff) {
+    if (context == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
+      return;
+    }
+
+    JsArray<Region> regions = diff.content();
+    List<SkippedLine> skips = new ArrayList<>();
+    int lineA = 0, lineB = 0;
+    for (int i = 0; i < regions.length(); i++) {
+      Region current = regions.get(i);
+      if (current.ab() != null || current.common() || current.skip() > 0) {
+        int len = current.skip() > 0
+            ? current.skip()
+            : (current.ab() != null ? current.ab() : current.b()).length();
+        if (i == 0 && len > context + 1) {
+          skips.add(new SkippedLine(0, 0, len - context));
+        } else if (i == regions.length() - 1 && len > context + 1) {
+          skips.add(new SkippedLine(lineA + context, lineB + context,
+              len - context));
+        } else if (len > 2 * context + 1) {
+          skips.add(new SkippedLine(lineA + context, lineB + context,
+              len - 2 * context));
+        }
+        lineA += len;
+        lineB += len;
+      } else {
+        lineA += current.a() != null ? current.a().length() : 0;
+        lineB += current.b() != null ? current.b().length() : 0;
+      }
+    }
+    skips = commentManager.splitSkips(context, skips);
+
+    if (!skips.isEmpty()) {
+      CodeMirror cmA = host.getCmFromSide(DisplaySide.A);
+      CodeMirror cmB = host.getCmFromSide(DisplaySide.B);
+
+      skipBars = new HashSet<>();
+      for (SkippedLine skip : skips) {
+        SkipBar barA = newSkipBar(cmA, DisplaySide.A, skip);
+        SkipBar barB = newSkipBar(cmB, DisplaySide.B, skip);
+        SkipBar.link(barA, barB);
+        skipBars.add(barA);
+        skipBars.add(barB);
+
+        if (skip.getStartA() == 0 || skip.getStartB() == 0) {
+          barA.upArrow.setVisible(false);
+          barB.upArrow.setVisible(false);
+          line0 = barB;
+        } else if (skip.getStartA() + skip.getSize() == lineA
+            || skip.getStartB() + skip.getSize() == lineB) {
+          barA.downArrow.setVisible(false);
+          barB.downArrow.setVisible(false);
+        }
+      }
+    }
+  }
+
+  void ensureFirstLineIsVisible() {
+    if (line0 != null) {
+      line0.expandBefore(1);
+      line0 = null;
+    }
+  }
+
+  void removeAll() {
+    if (skipBars != null) {
+      for (SkipBar bar : skipBars) {
+        bar.expandSideAll();
+      }
+      getOverviewBar().refresh();
+      skipBars = null;
+      line0 = null;
+    }
+  }
+
+  void remove(SkipBar a, SkipBar b) {
+    skipBars.remove(a);
+    skipBars.remove(b);
+    if (line0 == a || line0 == b) {
+      line0 = null;
+    }
+    if (skipBars.isEmpty()) {
+      skipBars = null;
+    }
+  }
+
+  private SkipBar newSkipBar(CodeMirror cm, DisplaySide side, SkippedLine skip) {
+    int start = side == DisplaySide.A ? skip.getStartA() : skip.getStartB();
+    int end = start + skip.getSize() - 1;
+
+    SkipBar bar = new SkipBar(this, cm);
+    host.diffTable.add(bar);
+    bar.collapse(start, end, true);
+    return bar;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/gear.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/gear.png
new file mode 100644
index 0000000..2f84e47
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/gear.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-next.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-next.png
new file mode 100644
index 0000000..872c197
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-next.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-prev.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-prev.png
new file mode 100644
index 0000000..d68f29b
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-prev.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-up.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-up.png
new file mode 100644
index 0000000..f75bed4
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/go-up.png
Binary files differ
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocConstants.java
similarity index 70%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocConstants.java
index c48f968..f6b7a9d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocConstants.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.client.documentation;
 
-public class GroupInfo {
-  public String id;
-  public String name;
-  public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+import com.google.gwt.i18n.client.Constants;
+
+public interface DocConstants extends Constants {
+  String keyReloadSearch();
+
+  String docItemHelp();
+  String docTableColumnTitle();
+  String docTableNone();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocConstants.properties
new file mode 100644
index 0000000..b48c507
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocConstants.properties
@@ -0,0 +1,5 @@
+keyReloadSearch = Reload documentation list
+
+docItemHelp = documentation
+docTableColumnTitle = Title
+docTableNone = (None)
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocInfo.java
new file mode 100644
index 0000000..6235186
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocInfo.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.documentation;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class DocInfo extends JavaScriptObject {
+
+  public final native String title() /*-{ return this.title; }-*/;
+  public final native String url() /*-{ return this.url; }-*/;
+
+  protected DocInfo() {
+  }
+
+  public final String getFullUrl() {
+    return GWT.getHostPageBaseURL() + url();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocMessages.java
similarity index 72%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocMessages.java
index 4457fb6..df62b92 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupOptionsInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocMessages.java
@@ -12,8 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.client.documentation;
 
-public class GroupOptionsInfo {
-  public Boolean visible_to_all;
+import com.google.gwt.i18n.client.Messages;
+
+public interface DocMessages extends Messages {
+  String docQueryWindowTitle(String query);
+  String docQueryPageTitle(String query);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocMessages.properties
new file mode 100644
index 0000000..8810a4a
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocMessages.properties
@@ -0,0 +1,2 @@
+docQueryWindowTitle = {0}
+docQueryPageTitle = Search for {0} in documentation
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocScreen.java
new file mode 100644
index 0000000..0a87d29
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocScreen.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.documentation;
+
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.client.ui.Screen;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwtorm.client.KeyUtil;
+
+public class DocScreen extends Screen {
+  private static final String URI = "/Documentation/";
+
+  private DocTable table;
+  private final String query;
+
+  public DocScreen(String query) {
+    this.query = KeyUtil.decode(query);
+  }
+
+  @Override
+  protected void onInitUI() {
+    super.onInitUI();
+
+    table = new DocTable();
+    table.setSavePointerId(query);
+    add(table);
+
+    setWindowTitle(Util.M.docQueryWindowTitle(query));
+    setPageTitle(Util.M.docQueryPageTitle(query));
+  }
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    doQuery();
+  }
+
+  @Override
+  public void registerKeys() {
+    super.registerKeys();
+    table.setRegisterKeys(true);
+  }
+
+  private AsyncCallback<JsArray<DocInfo>> loadCallback() {
+    return new GerritCallback<JsArray<DocInfo>>() {
+      @Override
+      public void onSuccess(JsArray<DocInfo> result) {
+        displayResults(result);
+        display();
+      }
+    };
+  }
+
+  private void displayResults(JsArray<DocInfo> result) {
+    table.display(result);
+    table.finishDisplay();
+  }
+
+  private void doQuery() {
+    RestApi call = new RestApi(URI);
+    call.addParameterRaw("q", KeyUtil.encode(query));
+    call.get(loadCallback());
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java
new file mode 100644
index 0000000..f176372
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/DocTable.java
@@ -0,0 +1,125 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.documentation;
+
+import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.ui.NavigationTable;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+import com.google.gwt.user.client.ui.HTMLTable.Cell;
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+
+class DocTable extends NavigationTable<DocInfo> {
+  private static final int C_TITLE = 1;
+
+  private int rows = 0;
+  private int dataBeginRow = 0;
+
+  public DocTable() {
+    super(Util.C.docItemHelp());
+
+    table.setText(0, C_TITLE, Util.C.docTableColumnTitle());
+
+    FlexCellFormatter fmt = table.getFlexCellFormatter();
+    fmt.addStyleName(0, C_TITLE, Gerrit.RESOURCES.css().dataHeader());
+
+    table.addClickHandler(new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        Cell cell = table.getCellForEvent(event);
+        if (cell == null) {
+          return;
+        }
+        if (getRowItem(cell.getRowIndex()) != null) {
+          movePointerTo(cell.getRowIndex());
+        }
+      }
+    });
+  }
+
+  @Override
+  protected Object getRowItemKey(DocInfo item) {
+    return item.url();
+  }
+
+  @Override
+  protected void onOpenRow(int row) {
+    DocInfo d = getRowItem(row);
+    Window.Location.assign(d.getFullUrl());
+  }
+
+  private void insertNoneRow(int row) {
+    table.insertRow(row);
+    table.setText(row, 0, Util.C.docTableNone());
+    FlexCellFormatter fmt = table.getFlexCellFormatter();
+    fmt.setStyleName(row, 0, Gerrit.RESOURCES.css().emptySection());
+  }
+
+  private void insertDocRow(int row) {
+    table.insertRow(row);
+    applyDataRowStyle(row);
+  }
+
+  @Override
+  protected void applyDataRowStyle(int row) {
+    super.applyDataRowStyle(row);
+    CellFormatter fmt = table.getCellFormatter();
+    fmt.addStyleName(row, C_TITLE, Gerrit.RESOURCES.css().dataCell());
+    fmt.addStyleName(row, C_TITLE, Gerrit.RESOURCES.css().cSUBJECT());
+  }
+
+  private void populateDocRow(int row, DocInfo d) {
+    table.setWidget(row, C_TITLE, new DocLink(d));
+    setRowItem(row, d);
+  }
+
+  public void display(JsArray<DocInfo> docList) {
+    int sz = docList != null ? docList.length() : 0;
+    boolean hadData = rows > 0;
+
+    if (hadData) {
+      while (sz < rows) {
+        table.removeRow(dataBeginRow);
+        rows--;
+      }
+    } else {
+      table.removeRow(dataBeginRow);
+    }
+
+    if (sz == 0) {
+      insertNoneRow(dataBeginRow);
+      return;
+    }
+
+    while (rows < sz) {
+      insertDocRow(dataBeginRow + rows);
+      rows++;
+    }
+    for (int i = 0; i < sz; i++) {
+      populateDocRow(dataBeginRow + i, docList.get(i));
+    }
+  }
+
+  public static class DocLink extends Anchor {
+    public DocLink(DocInfo d) {
+      super(com.google.gerrit.client.changes.Util.cropSubject(d.title()));
+      setHref(d.getFullUrl());
+    }
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/Util.java
similarity index 70%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
copy to gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/Util.java
index c48f968..273ead8 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/documentation/Util.java
@@ -12,14 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.client.documentation;
 
-public class GroupInfo {
-  public String id;
-  public String name;
-  public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+import com.google.gwt.core.client.GWT;
+
+public class Util {
+  public static final DocConstants C = GWT.create(DocConstants.class);
+  public static final DocMessages M = GWT.create(DocMessages.class);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java
index 7a477fa..a323a76 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadCommandLink.java
@@ -17,9 +17,11 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.aria.client.Roles;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
@@ -115,6 +117,55 @@
       }
     }
 
+    public class CloneWithCommitMsgHookCommandLink extends DownloadCommandLink {
+      private final Project.NameKey project;
+
+      public CloneWithCommitMsgHookCommandLink(Project.NameKey project) {
+        super(DownloadCommand.CHECKOUT, "clone with commit-msg hook");
+        this.project = project;
+      }
+
+      @Override
+      protected void setCurrentUrl(DownloadUrlLink link) {
+        widget.setVisible(true);
+
+        String sshPort = "29418";
+        String sshAddr = Gerrit.getConfig().getSshdAddress();
+        int p = sshAddr.lastIndexOf(':');
+        if (p != -1 && !sshAddr.endsWith(":")) {
+          sshPort = sshAddr.substring(p + 1);
+        }
+
+        StringBuilder cmd = new StringBuilder();
+        cmd.append("git clone ");
+        cmd.append(link.getUrlData());
+        cmd.append(" && scp -p -P ");
+        cmd.append(sshPort);
+        cmd.append(" ");
+        cmd.append(Gerrit.getUserAccount().getUserName());
+        cmd.append("@");
+
+        if (sshAddr.startsWith("*:") || p == -1) {
+          cmd.append(Window.Location.getHostName());
+        } else {
+          cmd.append(sshAddr.substring(0, p));
+        }
+
+        cmd.append(":hooks/commit-msg ");
+
+        p = project.get().lastIndexOf('/');
+        if (p != -1) {
+          cmd.append(project.get().substring(p + 1));
+        } else {
+          cmd.append(project.get());
+        }
+
+        cmd.append("/.git/hooks/");
+
+        copyLabel.setText(cmd.toString());
+      }
+    }
+
     public CopyableCommandLinkFactory(CopyableLabel label, Widget widget) {
       copyLabel = label;
       this.widget = widget;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
index 8bc897f..845537e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/download/DownloadUrlLink.java
@@ -158,7 +158,7 @@
 
   public static List<DownloadUrlLink> createDownloadUrlLinks(String project,
       String ref, boolean allowAnonymous) {
-    List<DownloadUrlLink> urls = new ArrayList<DownloadUrlLink>();
+    List<DownloadUrlLink> urls = new ArrayList<>();
     Set<DownloadScheme> allowedSchemes = Gerrit.getConfig().getDownloadSchemes();
 
     if (allowAnonymous
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
index 22ff495..1a6520e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/downloadIcon.png
+++ 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/draftComments.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/draftComments.png
index 31c770f..276912a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/draftComments.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/draftComments.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editText.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editText.png
index 188e1c1..2927275 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editText.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editText.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gear.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gear.png
new file mode 100644
index 0000000..2f84e47
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gear.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 48cabe7..77daccf 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
@@ -158,9 +158,15 @@
   border-top: 1px solid lightgray;
   border-left: 1px solid lightgray;
   border-right: 1px solid lightgray;
+  border-top-left-radius: 8px;
+  border-top-right-radius: 8px;
+  border-bottom-left-radius: 0px;
+  border-bottom-right-radius: 0px;
 }
 .commentPanelBorder.commentPanelLast {
   border-bottom: 1px solid lightgray;
+  border-bottom-left-radius: 8px;
+  border-bottom-right-radius: 8px;
 }
 
 @if user.agent safari {
@@ -371,7 +377,7 @@
   text-align: center;
   font-weight: bold;
   background: #FFF1A8;
-  z-index: 10;
+  z-index: 200;
 }
 
 
@@ -431,6 +437,11 @@
   font-size: 15px;
   font-family: verdana;
 }
+.loadingPluginsDialog {
+  background: #fff;
+  color: #000;
+  width: auto;
+}
 
 
 /** Screen **/
@@ -534,10 +545,17 @@
   height: 20px;
 }
 
-.changeTable a.gwt-InlineHyperlink {
+.changeTable a.gwt-InlineHyperlink,
+.changeTable a.gwt-Anchor {
   color: #222 !important;
 }
 
+.changeTable .changeSize {
+  height: 10px;
+  display: inline-block;
+  opacity: 0.6;
+}
+
 .accountDashboard.changeTable tr {
   color: #444444;
 }
@@ -557,13 +575,19 @@
   background: selectionColor !important;
 }
 
+.changeTable .cSIZE {
+  width: 70px;
+  text-align: right;
+}
+
 .changeTable .cSUBJECT div {
   text-overflow: ellipsis;
   overflow: hidden;
   white-space: nowrap;
 }
 
-.changeTable .cOWNER {
+.changeTable .cOWNER,
+.changeTable .cSTATUS {
   white-space: nowrap;
 }
 
@@ -1571,6 +1595,11 @@
   padding: 2px 6px 1px;
 }
 
+.editHeadButton {
+  float: right;
+  cursor: pointer;
+}
+
 /** PluginListScreen **/
 .pluginsTable {
 }
@@ -1590,6 +1619,12 @@
 }
 
 /** ProjectSettings */
-.maxObjectSizeLimitPanel td {
-  padding-right: 5px;
+.maxObjectSizeLimitEffectiveLabel {
+  padding-top: 5px;
+  padding-left: 5px;
+}
+
+.pluginProjectConfigInheritedValue {
+  padding-top: 5px;
+  padding-left: 5px;
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/greenCheck.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/greenCheck.png
index cd70687..207c0e7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/greenCheck.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/greenCheck.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/info.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/info.png
new file mode 100644
index 0000000..8851b99
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/info.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
index fd3e35c..26dd173 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/AbstractPatchContentTable.java
@@ -18,6 +18,8 @@
 import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.account.AccountInfo;
+import com.google.gerrit.client.changes.CommentApi;
+import com.google.gerrit.client.changes.CommentInfo;
 import com.google.gerrit.client.changes.PatchTable;
 import com.google.gerrit.client.changes.Util;
 import com.google.gerrit.client.rpc.GerritCallback;
@@ -38,6 +40,7 @@
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.BlurEvent;
 import com.google.gwt.event.dom.client.BlurHandler;
 import com.google.gwt.event.dom.client.ClickEvent;
@@ -50,7 +53,6 @@
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.History;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.Focusable;
@@ -795,9 +797,8 @@
   }
 
   protected static class CommentList {
-    final List<PatchLineComment> comments = new ArrayList<PatchLineComment>();
-    final List<PublishedCommentPanel> panels =
-        new ArrayList<PublishedCommentPanel>();
+    final List<PatchLineComment> comments = new ArrayList<>();
+    final List<PublishedCommentPanel> panels = new ArrayList<>();
   }
 
   public static class NoOpKeyCommand extends NeedsSignInKeyCommand {
@@ -937,13 +938,17 @@
       CommentEditorPanel p = findOrCreateEditor(newComment, false);
       if (p == null) {
         enableButtons(false);
-        PatchUtil.DETAIL_SVC.saveDraft(newComment,
-            new GerritCallback<PatchLineComment>() {
+        final PatchSet.Id psId = newComment.getKey().getParentKey().getParentKey();
+        CommentInfo in = CommentEditorPanel.toInput(newComment);
+        CommentApi.createDraft(psId, in,
+            new GerritCallback<CommentInfo>() {
               @Override
-              public void onSuccess(final PatchLineComment result) {
+              public void onSuccess(CommentInfo result) {
                 enableButtons(true);
                 notifyDraftDelta(1);
-                findOrCreateEditor(result, true).setOpen(false);
+                findOrCreateEditor(CommentEditorPanel.toComment(
+                    psId, newComment.getKey().getParentKey().get(), result),
+                  true).setOpen(false);
               }
 
               @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
index 9ed3f18..32302f4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/CommentEditorPanel.java
@@ -15,10 +15,16 @@
 package com.google.gerrit.client.patches;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.CommentApi;
+import com.google.gerrit.client.changes.CommentInfo;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.client.ui.CommentPanel;
+import com.google.gerrit.common.changes.Side;
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.DoubleClickEvent;
@@ -236,30 +242,37 @@
     cancel.setEnabled(false);
     discard.setEnabled(false);
 
-    PatchUtil.DETAIL_SVC.saveDraft(comment,
-        new GerritCallback<PatchLineComment>() {
-          public void onSuccess(final PatchLineComment result) {
-            notifyDraftDelta(isNew() ? 1 : 0);
-            comment = result;
-            text.setReadOnly(false);
-            save.setEnabled(true);
-            cancel.setEnabled(true);
-            discard.setEnabled(true);
-            render();
-            onSave.onSuccess(VoidResult.INSTANCE);
-          }
+    final PatchSet.Id psId = comment.getKey().getParentKey().getParentKey();
+    final boolean wasNew = isNew();
+    GerritCallback<CommentInfo> cb = new GerritCallback<CommentInfo>() {
+      public void onSuccess(CommentInfo result) {
+        notifyDraftDelta(wasNew ? 1 : 0);
+        comment = toComment(psId, comment.getKey().getParentKey().get(), result);
+        text.setReadOnly(false);
+        save.setEnabled(true);
+        cancel.setEnabled(true);
+        discard.setEnabled(true);
+        render();
+        onSave.onSuccess(VoidResult.INSTANCE);
+      }
 
-          @Override
-          public void onFailure(final Throwable caught) {
-            text.setReadOnly(false);
-            text.setFocus(true);
-            save.setEnabled(true);
-            cancel.setEnabled(true);
-            discard.setEnabled(true);
-            super.onFailure(caught);
-            onSave.onFailure(caught);
-          }
-        });
+      @Override
+      public void onFailure(final Throwable caught) {
+        text.setReadOnly(false);
+        text.setFocus(true);
+        save.setEnabled(true);
+        cancel.setEnabled(true);
+        discard.setEnabled(true);
+        super.onFailure(caught);
+        onSave.onFailure(caught);
+      }
+    };
+    CommentInfo input = toInput(comment);
+    if (wasNew) {
+      CommentApi.createDraft(psId, input, cb);
+    } else {
+      CommentApi.updateDraft(psId, input.id(), input, cb);
+    }
   }
 
   private void notifyDraftDelta(final int delta) {
@@ -283,9 +296,11 @@
     cancel.setEnabled(false);
     discard.setEnabled(false);
 
-    PatchUtil.DETAIL_SVC.deleteDraft(comment.getKey(),
-        new GerritCallback<VoidResult>() {
-          public void onSuccess(final VoidResult result) {
+    CommentApi.deleteDraft(
+        comment.getKey().getParentKey().getParentKey(),
+        comment.getKey().get(),
+        new GerritCallback<JavaScriptObject>() {
+          public void onSuccess(JavaScriptObject result) {
             notifyDraftDelta(-1);
             removeUI();
           }
@@ -319,4 +334,33 @@
     }
     return null;
   }
+
+  public static CommentInfo toInput(PatchLineComment c) {
+    CommentInfo i = CommentInfo.createObject().cast();
+    i.id(c.getKey().get());
+    i.path(c.getKey().getParentKey().get());
+    i.side(c.getSide() == 0 ? Side.PARENT : Side.REVISION);
+    if (c.getLine() > 0) {
+      i.line(c.getLine());
+    }
+    i.in_reply_to(c.getParentUuid());
+    i.message(c.getMessage());
+    return i;
+  }
+
+  public static PatchLineComment toComment(PatchSet.Id ps,
+      String path,
+      CommentInfo i) {
+    PatchLineComment p = new PatchLineComment(
+        new PatchLineComment.Key(
+            new Patch.Key(ps, path),
+            i.id()),
+        i.line(),
+        Gerrit.getUserAccount().getId(),
+        i.in_reply_to(),
+        i.updated());
+    p.setMessage(i.message());
+    p.setSide((short) (i.side() == Side.PARENT ? 0 : 1));
+    return p;
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java
index cb54411..6875a96 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/HistoryTable.java
@@ -34,7 +34,7 @@
  */
 class HistoryTable extends FancyFlexTable<Patch> {
   private final PatchScreen screen;
-  final List<HistoryRadio> all = new ArrayList<HistoryRadio>();
+  final List<HistoryRadio> all = new ArrayList<>();
 
   HistoryTable(final PatchScreen parent) {
     setStyleName(Gerrit.RESOURCES.css().patchHistoryTable());
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 908801b..ae0b786 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
@@ -52,6 +52,8 @@
   String fileList();
   String expandComment();
   String expandAllCommentsOnCurrentLine();
+  String toggleIntraline();
+  String showPreferences();
 
   String toggleReviewed();
   String markAsReviewedAndGoToNext();
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 a1b6192..3e021c7 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
@@ -34,6 +34,8 @@
 fileList = Browse files in patch set
 expandComment = Expand or collapse comment
 expandAllCommentsOnCurrentLine = Expand or collapse all comments on current line
+toggleIntraline = Toggle intraline difference
+showPreferences = Show diff preferences
 
 toggleReviewed = Toggle the reviewed flag
 markAsReviewedAndGoToNext = Mark patch as reviewed and go to next unreviewed patch
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchLine.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchLine.java
index 9726bbb..4863af2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchLine.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchLine.java
@@ -16,7 +16,7 @@
 
 class PatchLine {
   static enum Type {
-    DELETE, INSERT, REPLACE, CONTEXT;
+    DELETE, INSERT, REPLACE, CONTEXT
   }
 
   private PatchLine.Type type;
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 1153999..2fe2d45 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
@@ -150,7 +150,7 @@
         }
       }
       toggleEnabledStatus(on);
-    };
+    }
   }
 
   public void setEnableSmallFileFeatures(final boolean on) {
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 0ac06d0..e45ee1f 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
@@ -108,7 +108,7 @@
       linkPanel.add(sideMarker);
     }
 
-    Anchor baseLink = null;
+    Anchor baseLink;
     if (detail.getInfo().getParents().size() > 1) {
       baseLink = createLink(PatchUtil.C.patchBaseAutoMerge(), null);
     } else {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
index e0c5143..2d58816 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
@@ -28,10 +28,11 @@
 import com.google.gerrit.prettify.client.SparseHtmlFile;
 import com.google.gerrit.prettify.common.EditList;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
@@ -40,6 +41,7 @@
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+
 import org.eclipse.jgit.diff.Edit;
 
 import java.util.ArrayList;
@@ -57,12 +59,10 @@
 
   protected void createFileCommentEditorOnSideA() {
     createCommentEditor(R_HEAD + 1, A, R_HEAD, FILE_SIDE_A);
-    return;
   }
 
   protected void createFileCommentEditorOnSideB() {
     createCommentEditor(R_HEAD + 1, B, R_HEAD, FILE_SIDE_B);
-    return;
   }
 
   @Override
@@ -78,8 +78,8 @@
   }
 
   @Override
-  protected void onCellSingleClick(int row, int column) {
-    super.onCellSingleClick(row, column);
+  protected void onCellSingleClick(Event event, int row, int column) {
+    super.onCellSingleClick(event, row, column);
     if (column == 1 || column == 4) {
       onCellDoubleClick(row, column);
     }
@@ -93,7 +93,7 @@
 
   @Override
   protected void render(final PatchScript script, final PatchSetDetail detail) {
-    final ArrayList<Object> lines = new ArrayList<Object>();
+    final ArrayList<Object> lines = new ArrayList<>();
     final SafeHtmlBuilder nc = new SafeHtmlBuilder();
     isHugeFile = script.isHugeFile();
     allocateTableHeader(script, nc);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
index cacedfd..9ec4a8b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UnifiedDiffTable.java
@@ -28,8 +28,9 @@
 import com.google.gerrit.prettify.common.EditList.Hunk;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
@@ -90,19 +91,16 @@
           if (idSideA != null && idSideA.equals(psId)) {
             rowOfTableHeaderB++;
             borderRowOfFileComment++;
-            return;
-          }
-          if (idSideB.equals(psId)) {
+          } else if (idSideB.equals(psId)) {
             borderRowOfFileComment++;
-            return;
           }
       }
     }
   }
 
   @Override
-  protected void onCellSingleClick(int row, int column) {
-    super.onCellSingleClick(row, column);
+  protected void onCellSingleClick(Event event, int row, int column) {
+    super.onCellSingleClick(event, row, column);
     if (column == 1 || column == 2) {
       if (!"".equals(table.getText(row, column))) {
         onCellDoubleClick(row, column);
@@ -137,11 +135,8 @@
           if (idSideA != null && idSideA.equals(psId)) {
             rowOfTableHeaderB--;
             borderRowOfFileComment--;
-            return;
-          }
-          if (idSideB.equals(psId)) {
+          } else if (idSideB.equals(psId)) {
             borderRowOfFileComment--;
-            return;
           }
       }
     }
@@ -171,7 +166,6 @@
 
   protected void createFileCommentEditorOnSideA() {
     createCommentEditor(R_HEAD + 1, PC, R_HEAD, FILE_SIDE_A);
-    return;
   }
 
   protected void createFileCommentEditorOnSideB() {
@@ -222,7 +216,7 @@
     for (final String line : script.getPatchHeader()) {
       appendFileHeader(nc, line);
     }
-    final ArrayList<PatchLine> lines = new ArrayList<PatchLine>();
+    final ArrayList<PatchLine> lines = new ArrayList<>();
 
     if (hasDifferences(script)) {
       if (script.getDisplayMethodA() == DisplayMethod.IMG
@@ -363,7 +357,7 @@
     }
     setAccountInfoCache(cd.getAccounts());
 
-    final ArrayList<PatchLineComment> all = new ArrayList<PatchLineComment>();
+    final ArrayList<PatchLineComment> all = new ArrayList<>();
     for (int row = 0; row < table.getRowCount();) {
       final List<PatchLineComment> fora;
       final List<PatchLineComment> forb;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UpToChangeCommand.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UpToChangeCommand.java
index 72318dd..970e33d3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UpToChangeCommand.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/UpToChangeCommand.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.client.patches;
 
 import com.google.gerrit.client.Gerrit;
-import com.google.gerrit.client.changes.ChangeScreen;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.event.dom.client.KeyPressEvent;
@@ -31,6 +30,6 @@
 
   @Override
   public void onKeyPress(final KeyPressEvent event) {
-    Gerrit.display(PageLinks.toChange(patchSetId), new ChangeScreen(patchSetId));
+    Gerrit.display(PageLinks.toChange(patchSetId));
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java
index ace4a49..ed175cb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/plugins/PluginInfo.java
@@ -17,11 +17,10 @@
 import com.google.gwt.core.client.JavaScriptObject;
 
 public class PluginInfo extends JavaScriptObject {
-
-  public final native String name() /*-{ return this.name; }-*/;
-  public final native String version() /*-{ return this.version; }-*/;
-  public final native boolean isDisabled()
-      /*-{ return this.disabled ? true : false; }-*/;
+  public final native String name() /*-{ return this.name }-*/;
+  public final native String version() /*-{ return this.version }-*/;
+  public final native String indexUrl() /*-{ return this.index_url }-*/;
+  public final native boolean disabled() /*-{ return this.disabled || false }-*/;
 
   protected PluginInfo() {
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index 090ad3c..1d6e6b2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
 import com.google.gwtexpui.safehtml.client.FindReplace;
 import com.google.gwtexpui.safehtml.client.LinkFindReplace;
 import com.google.gwtexpui.safehtml.client.RawFindReplace;
@@ -50,6 +51,12 @@
     return SubmitType.valueOf(submit_typeRaw());
   }
 
+  public final native NativeMap<NativeMap<ConfigParameterInfo>> pluginConfig()
+  /*-{ return this.plugin_config || {}; }-*/;
+
+  public final native NativeMap<ConfigParameterInfo> pluginConfig(String p)
+  /*-{ return this.plugin_config[p]; }-*/;
+
   public final native NativeMap<ActionInfo> actions()
   /*-{ return this.actions; }-*/;
 
@@ -72,7 +79,7 @@
   /*-{ return this.commentlinks; }-*/;
   final List<FindReplace> commentlinks() {
     JsArray<CommentLinkInfo> cls = commentlinks0().values();
-    List<FindReplace> commentLinks = new ArrayList<FindReplace>(cls.length());
+    List<FindReplace> commentLinks = new ArrayList<>(cls.length());
     for (int i = 0; i < cls.length(); i++) {
       CommentLinkInfo cl = cls.get(i);
       if (!cl.enabled()) {
@@ -148,4 +155,48 @@
     protected MaxObjectSizeLimitInfo() {
     }
   }
+
+  public static class ConfigParameterInfo extends JavaScriptObject {
+    public final native String name() /*-{ return this.name; }-*/;
+    public final native String displayName() /*-{ return this.display_name; }-*/;
+    public final native String description() /*-{ return this.description; }-*/;
+    public final native String warning() /*-{ return this.warning; }-*/;
+    public final native String type() /*-{ return this.type; }-*/;
+    public final native String value() /*-{ return this.value; }-*/;
+    public final native boolean editable() /*-{ return this.editable ? true : false; }-*/;
+    public final native boolean inheritable() /*-{ return this.inheritable ? true : false; }-*/;
+    public final native String configuredValue() /*-{ return this.configured_value; }-*/;
+    public final native String inheritedValue() /*-{ return this.inherited_value; }-*/;
+    public final native JsArrayString permittedValues()  /*-{ return this.permitted_values; }-*/;
+    public final native JsArrayString values()  /*-{ return this.values; }-*/;
+
+    protected ConfigParameterInfo() {
+    }
+  }
+
+  public static class ConfigParameterValue extends JavaScriptObject {
+    final native void init() /*-{ this.values = []; }-*/;
+    final native void add_value(String v) /*-{ this.values.push(v); }-*/;
+    final native void set_value(String v) /*-{ if(v)this.value = v; }-*/;
+    public static ConfigParameterValue create() {
+      ConfigParameterValue v = createObject().cast();
+      return v;
+    }
+
+    public final ConfigParameterValue values(String[] values) {
+      init();
+      for (String v : values) {
+        add_value(v);
+      }
+      return this;
+    }
+
+    public final ConfigParameterValue value(String v) {
+      set_value(v);
+      return this;
+    }
+
+    protected ConfigParameterValue() {
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index 62dbf1c..63f7e15 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -14,7 +14,9 @@
 package com.google.gerrit.client.projects;
 
 import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterValue;
 import com.google.gerrit.client.rpc.CallbackGroup;
+import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.reviewdb.client.Project;
@@ -24,6 +26,8 @@
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 public class ProjectApi {
@@ -81,7 +85,9 @@
       InheritableBoolean useContributorAgreements,
       InheritableBoolean useContentMerge, InheritableBoolean useSignedOffBy,
       InheritableBoolean requireChangeId, String maxObjectSizeLimit,
-      SubmitType submitType, Project.State state, AsyncCallback<ConfigInfo> cb) {
+      SubmitType submitType, Project.State state,
+      Map<String, Map<String, ConfigParameterValue>> pluginConfigValues,
+      AsyncCallback<ConfigInfo> cb) {
     ConfigInput in = ConfigInput.create();
     in.setDescription(description);
     in.setUseContributorAgreements(useContributorAgreements);
@@ -91,6 +97,8 @@
     in.setMaxObjectSizeLimit(maxObjectSizeLimit);
     in.setSubmitType(submitType);
     in.setState(state);
+    in.setPluginConfigValues(pluginConfigValues);
+
     project(name).view("config").put(in, cb);
   }
 
@@ -110,6 +118,15 @@
         });
   }
 
+  public static void getChildren(Project.NameKey name, boolean recursive,
+      AsyncCallback<JsArray<ProjectInfo>> cb) {
+    RestApi view = project(name).view("children");
+    if (recursive) {
+      view.addParameterTrue("recursive");
+    }
+    view.get(cb);
+  }
+
   public static void getDescription(Project.NameKey name,
       AsyncCallback<NativeString> cb) {
     project(name).view("description").get(cb);
@@ -127,6 +144,14 @@
     }
   }
 
+  public static void setHead(Project.NameKey name, String ref,
+      AsyncCallback<NativeString> cb) {
+    RestApi call = project(name).view("HEAD");
+    HeadInput input = HeadInput.create();
+    input.setRef(ref);
+    call.put(input, cb);
+  }
+
   public static RestApi project(Project.NameKey name) {
     return new RestApi("/projects/").id(name.get());
   }
@@ -197,6 +222,33 @@
     }
     private final native void setStateRaw(String s)
     /*-{ if(s)this.state=s; }-*/;
+
+    final void setPluginConfigValues(Map<String, Map<String, ConfigParameterValue>> pluginConfigValues) {
+      if (!pluginConfigValues.isEmpty()) {
+        NativeMap<ConfigParameterValueMap> configValues = NativeMap.create().cast();
+        for (Entry<String, Map<String, ConfigParameterValue>> e : pluginConfigValues.entrySet()) {
+          ConfigParameterValueMap values = ConfigParameterValueMap.create();
+          configValues.put(e.getKey(), values);
+          for (Entry<String, ConfigParameterValue> e2 : e.getValue().entrySet()) {
+            values.put(e2.getKey(), e2.getValue());
+          }
+        }
+        setPluginConfigValuesRaw(configValues);
+      }
+    }
+    private final native void setPluginConfigValuesRaw(NativeMap<ConfigParameterValueMap> v)
+    /*-{ this.plugin_config_values=v; }-*/;
+  }
+
+  private static class ConfigParameterValueMap extends JavaScriptObject {
+    static ConfigParameterValueMap create() {
+      return createObject().cast();
+    }
+
+    protected ConfigParameterValueMap() {
+    }
+
+    public final native void put(String n, ConfigParameterValue v) /*-{ this[n] = v; }-*/;
   }
 
   private static class BranchInput extends JavaScriptObject {
@@ -220,4 +272,15 @@
 
     final native void setDescription(String d) /*-{ if(d)this.description=d; }-*/;
   }
+
+  private static class HeadInput extends JavaScriptObject {
+    static HeadInput create() {
+      return createObject().cast();
+    }
+
+    protected HeadInput() {
+    }
+
+    final native void setRef(String r) /*-{ if(r)this.ref=r; }-*/;
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
index 80c1feb..cab45b5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectInfo.java
@@ -28,6 +28,12 @@
   public final native String name() /*-{ return this.name; }-*/;
   public final native String description() /*-{ return this.description; }-*/;
 
+  public final Project.State state() {
+    return Project.State.valueOf(getStringState());
+  }
+
+  private final native String getStringState() /*-{ return this.state; }-*/;
+
   @Override
   public final String getDisplayString() {
     if (description() != null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/queryIcon.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/queryIcon.png
index 5aace51..5ebf2cb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/queryIcon.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/queryIcon.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/readOnly.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/readOnly.png
new file mode 100644
index 0000000..62e89f9
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/readOnly.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/redNot.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/redNot.png
index 4e83a8f..99834fd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/redNot.png
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/redNot.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
index 073c949..7eaada0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
@@ -62,8 +62,8 @@
   }
 
   public CallbackGroup() {
-    callbacks = new ArrayList<CallbackImpl<?>>();
-    remaining = new HashSet<CallbackImpl<?>>();
+    callbacks = new ArrayList<>();
+    remaining = new HashSet<>();
   }
 
   public <T> Callback<T> add(final AsyncCallback<T> cb) {
@@ -82,6 +82,18 @@
     applyAllSuccess();
   }
 
+  public void addListener(AsyncCallback<Void> cb) {
+    if (!failed && finalAdded && remaining.isEmpty()) {
+      cb.onSuccess(null);
+    } else {
+      handleAdd(cb).onSuccess(null);
+    }
+  }
+
+  public void addListener(CallbackGroup group) {
+    addListener(group.add(CallbackGroup.<Void> emptyCallback()));
+  }
+
   private void applyAllSuccess() {
     if (!failed && finalAdded && remaining.isEmpty()) {
       for (CallbackImpl<?> cb : callbacks) {
@@ -97,7 +109,7 @@
       return emptyCallback();
     }
 
-    CallbackImpl<T> wrapper = new CallbackImpl<T>(cb);
+    CallbackImpl<T> wrapper = new CallbackImpl<>(cb);
     callbacks.add(wrapper);
     remaining.add(wrapper);
     return wrapper;
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 4666d34..da620d8 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
@@ -23,6 +23,7 @@
 import com.google.gerrit.client.RpcStatus;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.http.client.Request;
 import com.google.gwt.http.client.RequestBuilder;
 import com.google.gwt.http.client.RequestBuilder.Method;
@@ -31,7 +32,6 @@
 import com.google.gwt.http.client.Response;
 import com.google.gwt.http.client.URL;
 import com.google.gwt.json.client.JSONException;
-import com.google.gwt.json.client.JSONObject;
 import com.google.gwt.json.client.JSONParser;
 import com.google.gwt.json.client.JSONValue;
 import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -124,34 +124,51 @@
         }
 
       } else if (200 <= status && status < 300) {
-        if (!isJsonBody(res)) {
+        long start = System.currentTimeMillis();
+        final T data;
+        if (isTextBody(res)) {
+          data = NativeString.wrap(res.getText()).cast();
+        } else if (isJsonBody(res)) {
+          try {
+            // javac generics bug
+            data = RestApi.<T>cast(parseJson(res));
+          } catch (JSONException e) {
+            if (!background) {
+              RpcStatus.INSTANCE.onRpcComplete();
+            }
+            cb.onFailure(new StatusCodeException(SC_BAD_RESPONSE,
+                "Invalid JSON: " + e.getMessage()));
+            return;
+          }
+        } else {
           if (!background) {
             RpcStatus.INSTANCE.onRpcComplete();
           }
           cb.onFailure(new StatusCodeException(SC_BAD_RESPONSE, "Expected "
-              + JSON_TYPE + "; received Content-Type: "
+              + JSON_TYPE + " or " + TEXT_TYPE + "; received Content-Type: "
               + res.getHeader("Content-Type")));
           return;
         }
 
-        T data;
-        try {
-          // javac generics bug
-          data = RestApi.<T>cast(parseJson(res));
-        } catch (JSONException e) {
-          if (!background) {
-            RpcStatus.INSTANCE.onRpcComplete();
+        Scheduler.ScheduledCommand cmd = new Scheduler.ScheduledCommand() {
+          @Override
+          public void execute() {
+            try {
+              cb.onSuccess(data);
+            } finally {
+              if (!background) {
+                RpcStatus.INSTANCE.onRpcComplete();
+              }
+            }
           }
-          cb.onFailure(new StatusCodeException(SC_BAD_RESPONSE,
-              "Invalid JSON: " + e.getMessage()));
-          return;
-        }
+        };
 
-        cb.onSuccess(data);
-        if (!background) {
-          RpcStatus.INSTANCE.onRpcComplete();
+        // Defer handling the response if the parse took a while.
+        if ((System.currentTimeMillis() - start) > 75) {
+          Scheduler.get().scheduleDeferred(cmd);
+        } else {
+          cmd.execute();
         }
-
       } else {
         String msg;
         if (isTextBody(res)) {
@@ -336,6 +353,11 @@
     send(PUT, cb);
   }
 
+  public <T extends JavaScriptObject> void put(String content,
+      AsyncCallback<T> cb) {
+    sendRaw(PUT, content, cb);
+  }
+
   public <T extends JavaScriptObject> void put(
       JavaScriptObject content,
       AsyncCallback<T> cb) {
@@ -350,15 +372,16 @@
       if (!background) {
         RpcStatus.INSTANCE.onRpcStart();
       }
-      String body = new JSONObject(content).toString();
       RequestBuilder req = request(method);
       req.setHeader("Content-Type", JSON_UTF8);
-      req.sendRequest(body, httpCallback);
+      req.sendRequest(str(content), httpCallback);
     } catch (RequestException e) {
       httpCallback.onError(null, e);
     }
   }
 
+  private static native String str(JavaScriptObject jso) /*-{ return JSON.stringify(jso); }-*/;
+
   private <T extends JavaScriptObject> void sendRaw(Method method, String body,
       AsyncCallback<T> cb) {
     HttpCallback<T> httpCallback = new HttpCallback<T>(background, cb);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
index 278e159..6046995 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountGroupSuggestOracle.java
@@ -28,8 +28,7 @@
 
 /** Suggestion Oracle for AccountGroup entities. */
 public class AccountGroupSuggestOracle extends SuggestAfterTypingNCharsOracle {
-  private Map<String, AccountGroup.UUID> priorResults =
-      new HashMap<String, AccountGroup.UUID>();
+  private Map<String, AccountGroup.UUID> priorResults = new HashMap<>();
 
   private Project.NameKey projectName;
 
@@ -43,7 +42,7 @@
               public void onSuccess(final List<GroupReference> result) {
                 priorResults.clear();
                 final ArrayList<AccountGroupSuggestion> r =
-                    new ArrayList<AccountGroupSuggestion>(result.size());
+                    new ArrayList<>(result.size());
                 for (final GroupReference p : result) {
                   r.add(new AccountGroupSuggestion(p));
                   priorResults.put(p.getName(), p.getUUID());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
index 0bf0ea9..4ffcd18 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/AccountSuggestOracle.java
@@ -34,7 +34,7 @@
             new GerritCallback<List<AccountInfo>>() {
               public void onSuccess(final List<AccountInfo> result) {
                 final ArrayList<AccountSuggestion> r =
-                    new ArrayList<AccountSuggestion>(result.size());
+                    new ArrayList<>(result.size());
                 for (final AccountInfo p : result) {
                   r.add(new AccountSuggestion(p));
                 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
index 17eeb00..1d5a74f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CherryPickDialog.java
@@ -53,7 +53,7 @@
         LinkedList<BranchSuggestion> suggestions =
             new LinkedList<BranchSuggestion>();
         for (final BranchInfo b : branches) {
-          if (b.ref().indexOf(request.getQuery()) >= 0) {
+          if (b.ref().contains(request.getQuery())) {
             suggestions.add(new BranchSuggestion(b));
           }
         }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentLinkProcessor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentLinkProcessor.java
index 10cd1f0..fd7d40c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentLinkProcessor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/CommentLinkProcessor.java
@@ -38,9 +38,9 @@
       // One or more of the patterns isn't valid on this browser.
       // Try to filter the list down and remove the invalid ones.
 
-      List<FindReplace> safe = new ArrayList<FindReplace>(commentLinks.size());
+      List<FindReplace> safe = new ArrayList<>(commentLinks.size());
 
-      List<PatternError> bad = new ArrayList<PatternError>();
+      List<PatternError> bad = new ArrayList<>();
       for (FindReplace r : commentLinks) {
         try {
           buf.replaceAll(Collections.singletonList(r));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
index b5bfd15..eb62809 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ComplexDisclosurePanel.java
@@ -15,13 +15,13 @@
 package com.google.gerrit.client.ui;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.logical.shared.CloseHandler;
 import com.google.gwt.event.logical.shared.HasCloseHandlers;
 import com.google.gwt.event.logical.shared.HasOpenHandlers;
 import com.google.gwt.event.logical.shared.OpenHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.ui.ComplexPanel;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.DisclosurePanel;
@@ -54,21 +54,21 @@
 
     header = new ComplexPanel() {
       {
-        setElement(DOM.createTD());
+        setElement((Element)(DOM.createTD()));
         getElement().setInnerHTML("&nbsp;");
         addStyleName(Gerrit.RESOURCES.css().complexHeader());
       }
 
       @Override
       public void add(Widget w) {
-        add(w, getElement());
+        add(w, (Element)getElement());
       }
     };
 
     initWidget(new ComplexPanel() {
       {
         final DisclosurePanel main = ComplexDisclosurePanel.this.main;
-        setElement(main.getElement());
+        setElement((Element)(main.getElement()));
         getChildren().add(main);
         adopt(main);
 
@@ -108,7 +108,7 @@
   /**
    * Changes the visible state of this panel's content.
    *
-   * @param isOpen <code>true</code> to open, <code>false</code> to close
+   * @param isOpen {@code true} to open, {@code false} to close
    */
   public void setOpen(final boolean isOpen) {
     main.setOpen(isOpen);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
index 0793527..c7d9658 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
@@ -17,8 +17,8 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
@@ -65,7 +65,7 @@
    * @param comparator comparator by which the items in the table are sorted
    * @param item the item that should be found
    * @return if the item is found the number of the row that contains the item;
-   *         if the item is not found <code>-1</code>
+   *         if the item is not found {@code -1}
    */
   protected int findRowItem(Comparator<RowItem> comparator, RowItem item) {
     int row = lookupRowItem(comparator, item);
@@ -84,7 +84,7 @@
    * @param item the new item that should be inserted
    * @return if the item is not yet contained in the table, the number of the
    *         row where the new item should be inserted; if the item is already
-   *         contained in the table <code>-1</code>
+   *         contained in the table {@code -1}
    */
   protected int getInsertRow(Comparator<RowItem> comparator, RowItem item) {
     int row = lookupRowItem(comparator, item);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java
index 2836e0f..e6e5d8b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImpl.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.client.ui;
 
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.HTMLTable;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImplIE6.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImplIE6.java
index c85bb0e..59feba8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImplIE6.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTableImplIE6.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.client.ui;
 
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.HTMLTable;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java
index 02244d0..3ea7acc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FilteredUserInterface.java
@@ -19,7 +19,7 @@
    * Return the value by which the user interface is currently filtered.
    *
    * @return value by which the user interface is currently filtered,
-   *         <code>null</code> or empty String if currently no filter is applied
+   *         {@code null} or empty String if currently no filter is applied
    */
   public String getCurrentFilter();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Hyperlink.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Hyperlink.java
index fc1661a..99766c4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Hyperlink.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Hyperlink.java
@@ -44,7 +44,7 @@
    * Creates a hyperlink with its text and target history token specified.
    *
    * @param text the hyperlink's text
-   * @param asHTML <code>true</code> to treat the specified text as html
+   * @param asHTML {@code true} to treat the specified text as html
    * @param token the history token to which it will link
    * @see #setTargetHistoryToken
    */
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MorphingTabPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MorphingTabPanel.java
index 8c6fe07..db7ad3c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MorphingTabPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/MorphingTabPanel.java
@@ -29,11 +29,11 @@
  */
 public class MorphingTabPanel extends TabPanel {
   // Keep track of the order the widgets/texts should be in when not hidden.
-  private List<Widget> widgets = new ArrayList<Widget>();
-  private List<String> texts = new ArrayList<String>();
+  private List<Widget> widgets = new ArrayList<>();
+  private List<String> texts = new ArrayList<>();
 
   // currently visible widgets
-  private List<Widget> visibles = new ArrayList<Widget>();
+  private List<Widget> visibles = new ArrayList<>();
 
   private int selection;
 
@@ -76,7 +76,7 @@
 
   public void setVisible(Widget w, boolean visible) {
     if (visible) {
-      if (visibles.indexOf(w) == -1) {
+      if (!visibles.contains(w)) {
         int origPos = widgets.indexOf(w);
 
         /* Re-insert the widget right after the first visible widget found
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
index 788c977..83a22f5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
@@ -16,11 +16,11 @@
 
 import com.google.gerrit.client.Gerrit;
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
@@ -53,7 +53,7 @@
           }
           final int row = rowOf(td);
           if (getRowItem(row) != null) {
-            onCellSingleClick(rowOf(td), columnOf(td));
+            onCellSingleClick(event, rowOf(td), columnOf(td));
             return;
           }
           break;
@@ -94,8 +94,9 @@
 
   protected NavigationTable(String itemHelpName) {
     this();
-    keysNavigation.add(new PrevKeyCommand(0, 'k', Util.M.helpListPrev(itemHelpName)));
-    keysNavigation.add(new NextKeyCommand(0, 'j', Util.M.helpListNext(itemHelpName)));
+    keysNavigation.add(
+        new PrevKeyCommand(0, 'k', Util.M.helpListPrev(itemHelpName)),
+        new NextKeyCommand(0, 'j', Util.M.helpListNext(itemHelpName)));
     keysNavigation.add(new OpenKeyCommand(0, 'o', Util.M.helpListOpen(itemHelpName)));
     keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER,
                                                   Util.M.helpListOpen(itemHelpName)));
@@ -144,7 +145,7 @@
   }
 
   /** Invoked when the user clicks on a table cell. */
-  protected void onCellSingleClick(int row, int column) {
+  protected void onCellSingleClick(Event event, int row, int column) {
     movePointerTo(row);
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NpIntTextBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NpIntTextBox.java
index 820a631..b57a58e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NpIntTextBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NpIntTextBox.java
@@ -16,6 +16,9 @@
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyEvent;
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.dom.client.KeyPressHandler;
 import com.google.gwtexpui.globalkey.client.NpTextBox;
@@ -34,33 +37,52 @@
   }
 
   private void init() {
+    addKeyDownHandler(new KeyDownHandler() {
+      @Override
+      public void onKeyDown(KeyDownEvent event) {
+        int code = event.getNativeKeyCode();
+        onKey(event, code, code);
+      }
+    });
     addKeyPressHandler(new KeyPressHandler() {
       @Override
       public void onKeyPress(KeyPressEvent event) {
-        char c = event.getCharCode();
-        if (c < '0' || '9' < c) {
-          final int nativeCode = event.getNativeEvent().getKeyCode();
-          switch (nativeCode) {
-            case KeyCodes.KEY_BACKSPACE:
-            case KeyCodes.KEY_LEFT:
-            case KeyCodes.KEY_RIGHT:
-            case KeyCodes.KEY_HOME:
-            case KeyCodes.KEY_END:
-            case KeyCodes.KEY_TAB:
-            case KeyCodes.KEY_DELETE:
-              break;
-
-            default:
-              if (!event.isAnyModifierKeyDown()) {
-                event.preventDefault();
-              }
-              break;
-          }
-        }
+        int charCode = event.getCharCode();
+        int keyCode = event.getNativeEvent().getKeyCode();
+        onKey(event, charCode, keyCode);
       }
     });
   }
 
+  private void onKey(KeyEvent<?> event, int charCode, int keyCode) {
+    if ('0' <= charCode && charCode <= '9') {
+      if (event.isAnyModifierKeyDown()) {
+        event.preventDefault();
+      }
+    } else {
+      switch (keyCode) {
+        case KeyCodes.KEY_BACKSPACE:
+        case KeyCodes.KEY_LEFT:
+        case KeyCodes.KEY_RIGHT:
+        case KeyCodes.KEY_HOME:
+        case KeyCodes.KEY_END:
+        case KeyCodes.KEY_TAB:
+        case KeyCodes.KEY_DELETE:
+          break;
+
+        default:
+          // Allow copy and paste using ctl-c/ctrl-v,
+          // or whatever the platform's convention is.
+          if (!(event.isControlKeyDown()
+              || event.isMetaKeyDown()
+              || event.isAltKeyDown())) {
+            event.preventDefault();
+          }
+          break;
+      }
+    }
+  }
+
   public int getIntValue() {
     String txt = getText().trim();
     if (!txt.isEmpty()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
index 8082527..a2155d4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/OnEditEnabler.java
@@ -46,7 +46,7 @@
    MouseUpHandler, ChangeHandler, ValueChangeHandler<Object> {
 
   private final FocusWidget widget;
-  private Map<TextBoxBase, String> strings = new HashMap<TextBoxBase, String>();
+  private Map<TextBoxBase, String> strings = new HashMap<>();
   private String originalValue;
 
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java
new file mode 100644
index 0000000..4bf8fef
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ParentProjectBox.java
@@ -0,0 +1,101 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.ui;
+
+import com.google.gerrit.client.projects.ProjectApi;
+import com.google.gerrit.client.projects.ProjectInfo;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwtexpui.globalkey.client.NpTextBox;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class ParentProjectBox extends Composite {
+  private final NpTextBox textBox;
+  private final SuggestBox suggestBox;
+  private final ParentProjectNameSuggestOracle suggestOracle;
+
+  public ParentProjectBox() {
+    textBox = new NpTextBox();
+    suggestOracle = new ParentProjectNameSuggestOracle();
+    suggestBox = new SuggestBox(suggestOracle, textBox);
+    initWidget(suggestBox);
+  }
+
+  public void setVisibleLength(int len) {
+    textBox.setVisibleLength(len);
+  }
+
+  public void setProject(final Project.NameKey project) {
+    suggestOracle.setProject(project);
+  }
+
+  public void setParentProject(final Project.NameKey parent) {
+    suggestBox.setText(parent != null ? parent.get() : "");
+  }
+
+  public Project.NameKey getParentProjectName() {
+    final String projectName = suggestBox.getText().trim();
+    if (projectName.isEmpty()) {
+      return null;
+    }
+    return new Project.NameKey(projectName);
+  }
+
+  private static class ParentProjectNameSuggestOracle extends ProjectNameSuggestOracle {
+    private Set<String> exclude = new HashSet<>();
+
+    public void setProject(Project.NameKey project) {
+      exclude.clear();
+      exclude.add(project.get());
+      ProjectApi.getChildren(project, true, new AsyncCallback<JsArray<ProjectInfo>>() {
+        @Override
+        public void onSuccess(JsArray<ProjectInfo> result) {
+          for (ProjectInfo p : Natives.asList(result)) {
+            exclude.add(p.name());
+          }
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+        }
+      });
+    }
+
+    @Override
+    public void _onRequestSuggestions(Request req, final Callback callback) {
+      super._onRequestSuggestions(req, new Callback() {
+        public void onSuggestionsReady(Request request, Response response) {
+          if (exclude.size() > 0) {
+            Set<Suggestion> filteredSuggestions =
+                new HashSet<>(response.getSuggestions());
+            for (Suggestion s : response.getSuggestions()) {
+              if (exclude.contains(s.getReplacementString())) {
+                filteredSuggestions.remove(s);
+              }
+            }
+            response.setSuggestions(filteredSuggestions);
+          }
+          callback.onSuggestionsReady(request, response);
+        }
+      });
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
index e17ec3f..6637957 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectsTable.java
@@ -25,6 +25,10 @@
 import java.util.List;
 
 public class ProjectsTable extends NavigationTable<ProjectInfo> {
+  public static final int C_STATE = 1;
+  public static final int C_NAME = 2;
+  public static final int C_DESCRIPTION = 3;
+  public static final int C_REPO_BROWSER = 4;
 
   public ProjectsTable() {
     super(Util.C.projectItemHelp());
@@ -32,12 +36,16 @@
   }
 
   protected void initColumnHeaders() {
-    table.setText(0, 1, Util.C.projectName());
-    table.setText(0, 2, Util.C.projectDescription());
+    table.setText(0, C_STATE, Util.C.projectStateAbbrev());
+    table.getCellFormatter().getElement(0, C_STATE)
+        .setTitle(Util.C.projectStateHelp());
+    table.setText(0, C_NAME, Util.C.projectName());
+    table.setText(0, C_DESCRIPTION, Util.C.projectDescription());
 
     final FlexCellFormatter fmt = table.getFlexCellFormatter();
-    fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
-    fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
+    fmt.addStyleName(0, C_STATE, Gerrit.RESOURCES.css().iconHeader());
+    fmt.addStyleName(0, C_NAME, Gerrit.RESOURCES.css().dataHeader());
+    fmt.addStyleName(0, C_DESCRIPTION, Gerrit.RESOURCES.css().dataHeader());
   }
 
   @Override
@@ -79,16 +87,18 @@
     applyDataRowStyle(row);
 
     final FlexCellFormatter fmt = table.getFlexCellFormatter();
-    fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
-    fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().projectNameColumn());
-    fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
+    fmt.addStyleName(row, C_STATE, Gerrit.RESOURCES.css().iconCell());
+    fmt.addStyleName(row, C_NAME, Gerrit.RESOURCES.css().dataCell());
+    fmt.addStyleName(row, C_NAME, Gerrit.RESOURCES.css().projectNameColumn());
+    fmt.addStyleName(row, C_DESCRIPTION, Gerrit.RESOURCES.css().dataCell());
 
     populate(row, k);
   }
 
   protected void populate(final int row, final ProjectInfo k) {
-    table.setText(row, 1, k.name());
-    table.setText(row, 2, k.description());
+    table.setText(row, C_STATE, k.state().toString());
+    table.setText(row, C_NAME, k.name());
+    table.setText(row, C_DESCRIPTION, k.description());
 
     setRowItem(row, k);
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java
index 3f1de2b..12506da 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ReviewerSuggestOracle.java
@@ -39,8 +39,7 @@
             req.getLimit(), new GerritCallback<List<ReviewerInfo>>() {
               public void onSuccess(final List<ReviewerInfo> result) {
                 final List<ReviewerSuggestion> r =
-                    new ArrayList<ReviewerSuggestion>(result
-                        .size());
+                    new ArrayList<>(result.size());
                 for (final ReviewerInfo reviewer : result) {
                   r.add(new ReviewerSuggestion(reviewer));
                 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
index 0cabdda..5192d6d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java
@@ -71,7 +71,7 @@
   }
 
   private static enum Cols {
-    West, Title, East, FarEast;
+    West, Title, East, FarEast
   }
 
   protected void onInitUI() {
@@ -137,6 +137,10 @@
     body.add(w);
   }
 
+  protected FlowPanel getBody() {
+    return body;
+  }
+
   protected void setTheme(final ThemeInfo t) {
     theme = t;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java
index 1919cd3..bcfb394 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.java
@@ -23,4 +23,6 @@
   String projectName();
   String projectDescription();
   String projectItemHelp();
+  String projectStateAbbrev();
+  String projectStateHelp();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties
index 8a72355..1e0e185 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/UIConstants.properties
@@ -3,4 +3,6 @@
 
 projectName = Project Name
 projectDescription = Project Description
-projectItemHelp = project
\ No newline at end of file
+projectItemHelp = project
+projectStateAbbrev = S
+projectStateHelp = State
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/warning.png b/gerrit-gwtui/src/main/java/com/google/gerrit/client/warning.png
new file mode 100644
index 0000000..81e9ed2
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/warning.png
Binary files differ
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
index 4652f86..0e5f9a5 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -14,6 +14,7 @@
 
 package net.codemirror.lib;
 
+import com.google.gerrit.client.diff.DisplaySide;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
@@ -31,9 +32,13 @@
     Loader.initLibrary(cb);
   }
 
-  public static native CodeMirror create(Element parent,
+  public static native CodeMirror create(
+      DisplaySide side,
+      Element parent,
       Configuration cfg) /*-{
-    return $wnd.CodeMirror(parent, cfg);
+    var m = $wnd.CodeMirror(parent, cfg);
+    m._sbs2_side = side;
+    return m;
   }-*/;
 
   public final native void setOption(String option, boolean value) /*-{
@@ -44,6 +49,10 @@
     this.setOption(option, value);
   }-*/;
 
+  public final native void setOption(String option, String value) /*-{
+    this.setOption(option, value);
+  }-*/;
+
   public final native void setOption(String option, JavaScriptObject val) /*-{
     this.setOption(option, val);
   }-*/;
@@ -54,6 +63,8 @@
   public final native void setWidth(String w) /*-{ this.setSize(w, null); }-*/;
   public final native void setHeight(double h) /*-{ this.setSize(null, h); }-*/;
   public final native void setHeight(String h) /*-{ this.setSize(null, h); }-*/;
+  public final native double defaultCharWidth() /*-{ return this.defaultCharWidth() }-*/;
+  public final native String getLine(int n) /*-{ return this.getLine(n) }-*/;
 
   public final native void refresh() /*-{ this.refresh(); }-*/;
   public final native Element getWrapperElement() /*-{ return this.getWrapperElement(); }-*/;
@@ -64,7 +75,7 @@
   }-*/;
 
   public enum LineClassWhere {
-    TEXT, BACKGROUND, WRAP;
+    TEXT, BACKGROUND, WRAP
   }
 
   public final void addLineClass(int line, LineClassWhere where,
@@ -107,7 +118,6 @@
     this.removeLineClass(line, where, lineClass);
   }-*/;
 
-
   public final native void addWidget(LineCharacter pos, Element node,
       boolean scrollIntoView) /*-{
     this.addWidget(pos, node, scrollIntoView);
@@ -134,6 +144,10 @@
     return this.heightAtLine(line, mode);
   }-*/;
 
+  public final native Rect charCoords(LineCharacter pos, String mode) /*-{
+    return this.charCoords(pos, mode);
+  }-*/;
+
   public final native CodeMirrorDoc getDoc() /*-{
     return this.getDoc();
   }-*/;
@@ -154,14 +168,6 @@
     return this.getViewport();
   }-*/;
 
-  public final native int getOldViewportSize() /*-{
-    return this.state.oldViewportSize || 0;
-  }-*/;
-
-  public final native void setOldViewportSize(int lines) /*-{
-    this.state.oldViewportSize = lines;
-  }-*/;
-
   public final native void operation(Runnable thunk) /*-{
     this.operation(function() {
       thunk.@java.lang.Runnable::run()();
@@ -174,22 +180,34 @@
     }));
   }-*/;
 
-  /** TODO: Break this line after updating GWT */
   public final native void on(String event, EventHandler handler) /*-{
     this.on(event, $entry(function(cm, e) {
-      handler.@net.codemirror.lib.CodeMirror.EventHandler::handle(Lnet/codemirror/lib/CodeMirror;Lcom/google/gwt/dom/client/NativeEvent;)(cm, e);
+      handler.@net.codemirror.lib.CodeMirror.EventHandler::handle(
+        Lnet/codemirror/lib/CodeMirror;Lcom/google/gwt/dom/client/NativeEvent;)(cm, e);
     }));
   }-*/;
 
   public final native void on(String event, RenderLineHandler handler) /*-{
     this.on(event, $entry(function(cm, h, ele) {
-      handler.@net.codemirror.lib.CodeMirror.RenderLineHandler::handle(Lnet/codemirror/lib/CodeMirror;Lnet/codemirror/lib/CodeMirror$LineHandle;Lcom/google/gwt/dom/client/Element;)(cm, h, ele);
+      handler.@net.codemirror.lib.CodeMirror.RenderLineHandler::handle(
+        Lnet/codemirror/lib/CodeMirror;Lnet/codemirror/lib/CodeMirror$LineHandle;
+        Lcom/google/gwt/dom/client/Element;)(cm, h, ele);
     }));
   }-*/;
 
   public final native void on(String event, GutterClickHandler handler) /*-{
     this.on(event, $entry(function(cm, l, g, e) {
-      handler.@net.codemirror.lib.CodeMirror.GutterClickHandler::handle(Lnet/codemirror/lib/CodeMirror;ILjava/lang/String;Lcom/google/gwt/dom/client/NativeEvent;)(cm, l, g, e);
+      handler.@net.codemirror.lib.CodeMirror.GutterClickHandler::handle(
+        Lnet/codemirror/lib/CodeMirror;ILjava/lang/String;
+        Lcom/google/gwt/dom/client/NativeEvent;)(cm, l, g, e);
+    }));
+  }-*/;
+
+  public final native void on(String event, BeforeSelectionChangeHandler handler) /*-{
+    this.on(event, $entry(function(cm, e) {
+      handler.@net.codemirror.lib.CodeMirror.BeforeSelectionChangeHandler::handle(
+        Lnet/codemirror/lib/CodeMirror;Lnet/codemirror/lib/LineCharacter;
+        Lnet/codemirror/lib/LineCharacter;)(cm,e.anchor,e.head);
     }));
   }-*/;
 
@@ -203,7 +221,15 @@
 
   public final FromTo getSelectedRange() {
     return FromTo.create(getCursor("start"), getCursor("end"));
-  };
+  }
+
+  public final native void setSelection(LineCharacter anchor) /*-{
+    this.setSelection(anchor);
+  }-*/;
+
+  public final native void setSelection(LineCharacter anchor, LineCharacter head) /*-{
+    this.setSelection(anchor, head);
+  }-*/;
 
   public final native void setCursor(LineCharacter lineCh) /*-{
     this.setCursor(lineCh);
@@ -226,11 +252,8 @@
   }-*/;
 
   public final native void addKeyMap(KeyMap map) /*-{ this.addKeyMap(map); }-*/;
-
   public final native void removeKeyMap(KeyMap map) /*-{ this.removeKeyMap(map); }-*/;
 
-  public final native void removeKeyMap(String name) /*-{ this.removeKeyMap(name); }-*/;
-
   public static final native LineCharacter pos(int line, int ch) /*-{
     return $wnd.CodeMirror.Pos(line, ch);
   }-*/;
@@ -255,24 +278,16 @@
     this.focus();
   }-*/;
 
-  public final native int lineCount() /*-{
-    return this.lineCount();
-  }-*/;
-
   public final native Element getGutterElement() /*-{
     return this.getGutterElement();
   }-*/;
 
-  public final native Element getScrollerElement() /*-{
-    return this.getScrollerElement();
-  }-*/;
-
   public final native Element getSizer() /*-{
     return this.display.sizer;
   }-*/;
 
-  public final native Element getInputField() /*-{
-    return this.getInputField();
+  public final native Element getMoverElement() /*-{
+    return this.display.mover;
   }-*/;
 
   public final native Element getScrollbarV() /*-{
@@ -309,12 +324,17 @@
         !!this.state.vim.searchState_.getOverlay();
   }-*/;
 
+  public final native DisplaySide side() /*-{ return this._sbs2_side }-*/;
+
   protected CodeMirror() {
   }
 
   public static class Viewport extends JavaScriptObject {
     public final native int getFrom() /*-{ return this.from; }-*/;
     public final native int getTo() /*-{ return this.to; }-*/;
+    public final boolean contains(int line) {
+      return getFrom() <= line && line < getTo();
+    }
 
     protected Viewport() {
     }
@@ -337,4 +357,8 @@
     public void handle(CodeMirror instance, int line, String gutter,
         NativeEvent clickEvent);
   }
+
+  public interface BeforeSelectionChangeHandler {
+    public void handle(CodeMirror instance, LineCharacter anchor, LineCharacter head);
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
index 7a0bbea..4ec3dea 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
@@ -20,7 +20,7 @@
  * Simple map-like structure to pass configuration to CodeMirror.
  *
  * @see <a href="http://codemirror.net/doc/manual.html#config">CodeMirror config</a>
- * @see CodeMirror#create(com.google.gwt.dom.client.Element, Configuration)
+ * @see CodeMirror#create(com.google.gerrit.client.diff.DisplaySide, com.google.gwt.dom.client.Element, Configuration)
  */
 public class Configuration extends JavaScriptObject {
   public static Configuration create() {
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java
index 83350c5..fb9f770 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/KeyMap.java
@@ -27,6 +27,11 @@
     return this;
   }-*/;
 
+  public final native KeyMap on(String key, boolean b) /*-{
+    this[key] = b;
+    return this;
+  }-*/;
+
   public final native KeyMap remove(String key) /*-{ delete this[key]; }-*/;
 
   protected KeyMap() {
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
index b6ea1aa..91547fb 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
@@ -40,8 +40,6 @@
     w.on("redraw", h);
   }-*/;
 
-  public final native JavaScriptObject getLine() /*-{ return this.line; }-*/;
-
   protected LineWidget() {
   }
 }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
index d2954ba..4d894c6 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
@@ -91,10 +91,22 @@
     // TODO: Better custom keybindings, remove temporary navigation hacks.
     KeyMap km = CodeMirror.cloneKeyMap("vim");
     for (String s : new String[] {
-        "A", "C", "O", "R", "U", "Ctrl-C", "Ctrl-O"}) {
+        "A", "C", "I", "O", "R", "U",
+        "Ctrl-C", "Ctrl-O", "Ctrl-P", "Ctrl-S",
+        "Ctrl-F", "Ctrl-B", "Ctrl-R"}) {
       km.remove(s);
     }
+    for (int i = 0; i <= 9; i++) {
+      km.remove("Ctrl-" + i);
+    }
     CodeMirror.addKeyMap("vim_ro", km);
+
+    CodeMirror.mapVimKey("j", "gj");
+    CodeMirror.mapVimKey("k", "gk");
+    CodeMirror.mapVimKey("Down", "gj");
+    CodeMirror.mapVimKey("Up", "gk");
+    CodeMirror.mapVimKey("<PageUp>", "<C-u>");
+    CodeMirror.mapVimKey("<PageDown>", "<C-d>");
   }
 
   private static void error(Exception e) {
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java
index cc13fcd..02faccb 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java
@@ -58,9 +58,9 @@
       Modes.I.xml(),
     };
 
-    mimeAlias = new HashMap<String, String>();
-    mimeModes = new HashMap<String, String>();
-    modeUris = new HashMap<String, SafeUri>();
+    mimeAlias = new HashMap<>();
+    mimeModes = new HashMap<>();
+    modeUris = new HashMap<>();
 
     for (DataResource m : all) {
       modeUris.put(m.getName(), m.getSafeUri());
@@ -103,7 +103,7 @@
   private static native JsArrayString getDependencies(String n)
   /*-{ return $wnd.CodeMirror.modes[n].dependencies || []; }-*/;
 
-  private final Set<String> loading = new HashSet<String>(4);
+  private final Set<String> loading = new HashSet<>(4);
   private int pending;
   private AsyncCallback<Void> appCallback;
 
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Rect.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Rect.java
new file mode 100644
index 0000000..5691d9f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Rect.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/** {left, right, top, bottom} objects used within CodeMirror. */
+public class Rect extends JavaScriptObject {
+  public final native double left() /*-{ return this.left }-*/;
+  public final native double right() /*-{ return this.right }-*/;
+  public final native double top() /*-{ return this.top }-*/;
+  public final native double bottom() /*-{ return this.bottom }-*/;
+
+  protected Rect() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java
index 0639416..096f1ad 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/ScrollInfo.java
@@ -16,19 +16,22 @@
 
 import com.google.gwt.core.client.JavaScriptObject;
 
-/** {left, top, width, height, clientWidth, clientHeight} objects returned by
- * getScrollInfo(). */
+/** Returned by {@link CodeMirror#getScrollInfo()}. */
 public class ScrollInfo extends JavaScriptObject {
-  public static ScrollInfo create() {
-    return createObject().cast();
-  }
-
   public final native double getLeft() /*-{ return this.left; }-*/;
   public final native double getTop() /*-{ return this.top; }-*/;
-  public final native double getWidth() /*-{ return this.width; }-*/;
+
+  /**
+   * Pixel height of the full content being scrolled. This may only be an
+   * estimate given by CodeMirror. Line widgets further down in the document may
+   * not be measured, so line heights can be incorrect until drawn.
+   */
   public final native double getHeight() /*-{ return this.height; }-*/;
-  public final native double getClientWidth() /*-{ return this.clientWidth; }-*/;
+  public final native double getWidth() /*-{ return this.width; }-*/;
+
+  /** Visible height of the viewport, excluding scrollbars. */
   public final native double getClientHeight() /*-{ return this.clientHeight; }-*/;
+  public final native double getClientWidth() /*-{ return this.clientWidth; }-*/;
 
   protected ScrollInfo() {
   }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java
index f154e48..50db13c 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/TextMarker.java
@@ -26,6 +26,8 @@
   public final native void clear() /*-{ this.clear(); }-*/;
   public final native void changed() /*-{ this.changed(); }-*/;
   public final native FromTo find() /*-{ return this.find(); }-*/;
+  public final native void on(String event, Runnable thunk)
+  /*-{ this.on(event, function(){$entry(thunk.@java.lang.Runnable::run()())}) }-*/;
 
   protected TextMarker() {
   }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map b/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
index e186b8a..c62920d 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
@@ -44,6 +44,9 @@
 text/x-ini
 text/x-properties
 
+perl:
+text/x-perl
+
 python:
 text/x-python
 
diff --git a/gerrit-gwtui/src/main/webapp/WEB-INF/web.xml b/gerrit-gwtui/src/main/webapp/WEB-INF/web.xml
deleted file mode 100644
index 494f0c1..0000000
--- a/gerrit-gwtui/src/main/webapp/WEB-INF/web.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<web-app>
-</web-app>
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/LineMapperTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/LineMapperTest.java
index 756e879..25c8270 100644
--- a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/LineMapperTest.java
+++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/LineMapperTest.java
@@ -102,4 +102,31 @@
     assertEquals(new LineOnOtherInfo(10, true),
         mapper.lineOnOther(DisplaySide.B, 0));
   }
+
+  @Test
+  public void testReplaceWithInsertInB() {
+    // 0 c c
+    // 1 a b
+    // 2 a b
+    // 3 - b
+    // 4 - b
+    // 5 c c
+    LineMapper mapper = new LineMapper();
+    mapper.appendCommon(1);
+    mapper.appendReplace(2, 4);
+    mapper.appendCommon(1);
+
+    assertEquals(4, mapper.getLineA());
+    assertEquals(6, mapper.getLineB());
+
+    assertEquals(new LineOnOtherInfo(1, true),
+        mapper.lineOnOther(DisplaySide.B, 1));
+    assertEquals(new LineOnOtherInfo(3, true),
+        mapper.lineOnOther(DisplaySide.B, 5));
+
+    assertEquals(new LineOnOtherInfo(2, true),
+        mapper.lineOnOther(DisplaySide.B, 2));
+    assertEquals(new LineOnOtherInfo(2, false),
+        mapper.lineOnOther(DisplaySide.B, 3));
+  }
 }
diff --git a/gerrit-httpd/.gitignore b/gerrit-httpd/.gitignore
deleted file mode 100644
index 5bbeafd..0000000
--- a/gerrit-httpd/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-httpd.iml
\ No newline at end of file
diff --git a/gerrit-httpd/.settings/org.eclipse.core.runtime.prefs b/gerrit-httpd/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-httpd/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-httpd/.settings/org.eclipse.jdt.core.prefs b/gerrit-httpd/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 470942d..0000000
--- a/gerrit-httpd/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-httpd/.settings/org.eclipse.jdt.ui.prefs b/gerrit-httpd/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-httpd/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-httpd/BUCK b/gerrit-httpd/BUCK
index 512e6e6..6005e4a 100644
--- a/gerrit-httpd/BUCK
+++ b/gerrit-httpd/BUCK
@@ -1,4 +1,6 @@
-SRCS = glob(['src/main/java/**/*.java'])
+SRCS = glob(
+  ['src/main/java/**/*.java'],
+)
 RESOURCES = glob(['src/main/resources/**/*'])
 
 java_library2(
@@ -7,9 +9,9 @@
   resources = RESOURCES,
   deps = [
     '//gerrit-antlr:query_exception',
+    '//gerrit-common:annotations',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
-    '//gerrit-gwtexpui:linker_server',
     '//gerrit-gwtexpui:server',
     '//gerrit-patch-jgit:server',
     '//gerrit-prettify:server',
@@ -30,8 +32,9 @@
     '//lib/jgit:jgit',
     '//lib/jgit:jgit-servlet',
     '//lib/log:api',
+    '//lib/lucene:core',
   ],
-  compile_deps = ['//lib:servlet-api-3_0'],
+  compile_deps = ['//lib:servlet-api-3_1'],
   visibility = ['PUBLIC'],
 )
 
@@ -55,7 +58,7 @@
     '//lib:gson',
     '//lib:gwtorm',
     '//lib:guava',
-    '//lib:servlet-api-3_0',
+    '//lib:servlet-api-3_1',
     '//lib/guice:guice',
     '//lib/jgit:jgit',
     '//lib/jgit:junit',
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CookieBase64.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CookieBase64.java
index 3bcdcd2..183e015 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CookieBase64.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CookieBase64.java
@@ -29,12 +29,13 @@
     o = fill(enc, o, 'A', 'Z');
     o = fill(enc, o, '0', '9');
     enc[o++] = '-';
-    enc[o++] = '.';
+    enc[o] = '.';
   }
 
   private static int fill(final char[] out, int o, final char f, final int l) {
-    for (char c = f; c <= l; c++)
+    for (char c = f; c <= l; c++) {
       out[o++] = c;
+    }
     return o;
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/DirectChangeByCommit.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/DirectChangeByCommit.java
index efbef76..402ea1b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/DirectChangeByCommit.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/DirectChangeByCommit.java
@@ -46,7 +46,6 @@
     this.currentUser = currentUser;
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   protected void doGet(final HttpServletRequest req,
       final HttpServletResponse rsp) throws IOException {
@@ -59,9 +58,9 @@
       q = Predicate.and(q, builder.sortkey_before("z"), builder.limit(2), visibleToMe);
 
       ChangeQueryRewriter rewriter = queryRewriter.get();
-      Predicate<ChangeData> s = rewriter.rewrite(q);
+      Predicate<ChangeData> s = rewriter.rewrite(q, 0);
       if (!(s instanceof ChangeDataSource)) {
-        s = rewriter.rewrite(Predicate.and(builder.status_open(), q));
+        s = rewriter.rewrite(Predicate.and(builder.status_open(), q), 0);
       }
 
       if (s instanceof ChangeDataSource) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
index 2980159..7f6a2df 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritConfigProvider.java
@@ -119,24 +119,22 @@
     config.setWildProject(wildProject);
     config.setDocumentationAvailable(servletContext
         .getResource("/Documentation/index.html") != null);
-    config.setTestChangeMerge(cfg.getBoolean("changeMerge",
-        "test", false));
     config.setAnonymousCowardName(anonymousCowardName);
     config.setSuggestFrom(cfg.getInt("suggest", "from", 0));
     config.setChangeUpdateDelay((int) ConfigUtil.getTimeUnit(
         cfg, "change", null, "updateDelay", 30, TimeUnit.SECONDS));
     config.setChangeScreen(cfg.getEnum(
         "gerrit", null, "changeScreen",
-        AccountGeneralPreferences.ChangeScreen.OLD_UI));
+        AccountGeneralPreferences.ChangeScreen.CHANGE_SCREEN2));
+    config.setLargeChangeSize(cfg.getInt("change", "largeChange", 500));
+    config.setNewFeatures(cfg.getBoolean("gerrit", "enableNewFeatures", true));
 
-    config.setReportBugUrl(cfg.getString("gerrit", null, "reportBugUrl"));
-    if (config.getReportBugUrl() == null) {
-      config.setReportBugUrl("http://code.google.com/p/gerrit/issues/list");
-    } else if (config.getReportBugUrl().isEmpty()) {
-      config.setReportBugUrl(null);
-    }
+    final String reportBugUrl = cfg.getString("gerrit", null, "reportBugUrl");
+    config.setReportBugUrl(reportBugUrl != null ?
+        reportBugUrl : "http://code.google.com/p/gerrit/issues/list");
+    config.setReportBugText(cfg.getString("gerrit", null, "reportBugText"));
 
-    config.setGitBasicAuth(authConfig.isGitBasichAuth());
+    config.setGitBasicAuth(authConfig.isGitBasicAuth());
 
     final Set<Account.FieldName> fields = new HashSet<Account.FieldName>();
     for (final Account.FieldName n : Account.FieldName.values()) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
index 6ca9949..4f4c783 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpModule.java
@@ -34,7 +34,7 @@
     Class<? extends Filter> authFilter;
     if (authConfig.isTrustContainerAuth()) {
       authFilter = ContainerAuthFilter.class;
-    } else if (authConfig.isGitBasichAuth()) {
+    } else if (authConfig.isGitBasicAuth()) {
       authFilter = ProjectBasicAuthFilter.class;
     } else {
       authFilter = ProjectDigestFilter.class;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 4ecd020..03d54e4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -15,7 +15,9 @@
 package com.google.gerrit.httpd;
 
 import com.google.common.cache.Cache;
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.AccessPath;
@@ -27,6 +29,7 @@
 import com.google.gerrit.server.git.ChangeCache;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ReceiveCommits;
+import com.google.gerrit.server.git.ReceivePackInitializer;
 import com.google.gerrit.server.git.TagCache;
 import com.google.gerrit.server.git.TransferConfig;
 import com.google.gerrit.server.git.VisibleRefFilter;
@@ -46,6 +49,8 @@
 import org.eclipse.jgit.http.server.resolver.AsIsFileService;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PostReceiveHook;
+import org.eclipse.jgit.transport.PostReceiveHookChain;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.UploadPack;
 import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
@@ -244,11 +249,17 @@
   static class ReceiveFactory implements ReceivePackFactory<HttpServletRequest> {
     private final AsyncReceiveCommits.Factory factory;
     private final TransferConfig config;
+    private DynamicSet<ReceivePackInitializer> receivePackInitializers;
+    private DynamicSet<PostReceiveHook> postReceiveHooks;
 
     @Inject
-    ReceiveFactory(AsyncReceiveCommits.Factory factory, TransferConfig config) {
+    ReceiveFactory(AsyncReceiveCommits.Factory factory, TransferConfig config,
+        DynamicSet<ReceivePackInitializer> receivePackInitializers,
+        DynamicSet<PostReceiveHook> postReceiveHooks) {
       this.factory = factory;
       this.config = config;
+      this.receivePackInitializers = receivePackInitializers;
+      this.postReceiveHooks = postReceiveHooks;
     }
 
     @Override
@@ -267,9 +278,18 @@
       rp.setRefLogIdent(user.newRefLogIdent());
       rp.setTimeout(config.getTimeout());
       rp.setMaxObjectSizeLimit(config.getMaxObjectSizeLimit());
+      init(pc.getProject().getNameKey(), rp);
+      rp.setPostReceiveHook(PostReceiveHookChain.newChain(
+          Lists.newArrayList(postReceiveHooks)));
       req.setAttribute(ATT_RC, rc);
       return rp;
     }
+
+    private void init(Project.NameKey project, ReceivePack rp) {
+      for (ReceivePackInitializer initializer : receivePackInitializers) {
+        initializer.init(project, rp);
+      }
+    }
   }
 
   static class ReceiveFilter implements Filter {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
index dfb1cc2..9d47977 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
@@ -54,6 +54,8 @@
     type.setBranch(cfg.getString("gitweb", null, "branch"));
     type.setProject(cfg.getString("gitweb", null, "project"));
     type.setRevision(cfg.getString("gitweb", null, "revision"));
+    type.setRootTree(cfg.getString("gitweb", null, "roottree"));
+    type.setFile(cfg.getString("gitweb", null, "file"));
     type.setFileHistory(cfg.getString("gitweb", null, "filehistory"));
     type.setLinkDrafts(cfg.getBoolean("gitweb", null, "linkdrafts", true));
     type.setUrlEncode(cfg.getBoolean("gitweb", null, "urlencode", true));
@@ -80,6 +82,12 @@
     } else if (type.getRevision() == null) {
       log.warn("No Pattern specified for gitweb.revision, disabling.");
       type = null;
+    } else if (type.getRootTree() == null) {
+      log.warn("No Pattern specified for gitweb.roottree, disabling.");
+      type = null;
+    } else if (type.getFile() == null) {
+      log.warn("No Pattern specified for gitweb.file, disabling.");
+      type = null;
     } else if (type.getFileHistory() == null) {
       log.warn("No Pattern specified for gitweb.filehistory, disabling.");
       type = null;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java
index c47552d..1a2d3f6 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/HtmlDomUtil.java
@@ -267,7 +267,6 @@
     factory.setExpandEntityReferences(false);
     factory.setIgnoringComments(true);
     factory.setCoalescing(true);
-    final DocumentBuilder parser = factory.newDocumentBuilder();
-    return parser;
+    return factory.newDocumentBuilder();
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index c55f9d3..0e41ef7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.AuthResult;
+import com.google.gerrit.server.auth.NoSuchUserException;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -147,6 +148,18 @@
       ws.setAccessPathOk(AccessPath.GIT, true);
       ws.setAccessPathOk(AccessPath.REST_API, true);
       return true;
+    } catch (NoSuchUserException e) {
+      if (password.equals(who.getPassword(who.getUserName()))) {
+        WebSession ws = session.get();
+        ws.setUserAccountId(who.getAccount().getId());
+        ws.setAccessPathOk(AccessPath.GIT, true);
+        ws.setAccessPathOk(AccessPath.REST_API, true);
+        return true;
+      } else {
+        log.warn("Authentication failed for " + username, e);
+        rsp.sendError(SC_UNAUTHORIZED);
+        return false;
+      }
     } catch (AccountException e) {
       log.warn("Authentication failed for " + username, e);
       rsp.sendError(SC_UNAUTHORIZED);
@@ -169,7 +182,7 @@
       if (sc == SC_UNAUTHORIZED) {
         StringBuilder v = new StringBuilder();
         v.append(LIT_BASIC);
-        v.append("realm=\"" + REALM_NAME + "\"");
+        v.append("realm=\"").append(REALM_NAME).append("\"");
         setHeader(WWW_AUTHENTICATE, v.toString());
       } else if (containsHeader(WWW_AUTHENTICATE)) {
         setHeader(WWW_AUTHENTICATE, null);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
index 523309e..45e4504 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
@@ -223,8 +223,7 @@
 
   private static String LHEX(byte[] bin) {
     StringBuilder r = new StringBuilder(bin.length * 2);
-    for (int i = 0; i < bin.length; i++) {
-      byte b = bin[i];
+    for (byte b : bin) {
       r.append(LHEX[(b >>> 4) & 0x0f]);
       r.append(LHEX[b & 0x0f]);
     }
@@ -294,7 +293,7 @@
       if (sc == SC_UNAUTHORIZED) {
         StringBuilder v = new StringBuilder();
         v.append("Digest");
-        v.append(" realm=\"" + REALM_NAME + "\"");
+        v.append(" realm=\"").append(REALM_NAME).append("\"");
 
         String url = urlProvider.get();
         if (url == null) {
@@ -304,14 +303,14 @@
           }
         }
         if (url != null && !url.isEmpty()) {
-          v.append(", domain=\"" + url + "\"");
+          v.append(", domain=\"").append(url).append("\"");
         }
 
         v.append(", qop=\"auth\"");
         if (stale != null) {
-          v.append(", stale=" + stale);
+          v.append(", stale=").append(stale);
         }
-        v.append(", nonce=\"" + newNonce() + "\"");
+        v.append(", nonce=\"").append(newNonce()).append("\"");
         setHeader(WWW_AUTHENTICATE, v.toString());
 
       } else if (containsHeader(WWW_AUTHENTICATE)) {
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 bf39bfb..3c4dfc5 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
@@ -30,6 +30,7 @@
 import com.google.gerrit.httpd.rpc.change.ChangesRestApiServlet;
 import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
 import com.google.gerrit.httpd.rpc.config.ConfigRestApiServlet;
+import com.google.gerrit.httpd.rpc.doc.QueryDocumentationFilter;
 import com.google.gerrit.httpd.rpc.group.GroupsRestApiServlet;
 import com.google.gerrit.httpd.rpc.project.ProjectsRestApiServlet;
 import com.google.gerrit.reviewdb.client.Change;
@@ -110,6 +111,8 @@
     serveRegex("^/(?:a/)?groups/(.*)?$").with(GroupsRestApiServlet.class);
     serveRegex("^/(?:a/)?projects/(.*)?$").with(ProjectsRestApiServlet.class);
 
+    filter("/Documentation/").through(QueryDocumentationFilter.class);
+
     if (cfg.deprecatedQuery) {
       serve("/query").with(DeprecatedChangeQueryServlet.class);
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 5593baf..3443968 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -31,7 +31,6 @@
 import com.google.gerrit.server.RemotePeer;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritRequestModule;
 import com.google.gerrit.server.contact.ContactStore;
 import com.google.gerrit.server.contact.ContactStoreProvider;
@@ -46,7 +45,7 @@
 
 import java.net.SocketAddress;
 
-public class WebModule extends FactoryModule {
+public class WebModule extends LifecycleModule {
   private final AuthConfig authConfig;
   private final UrlModule.UrlConfig urlConfig;
   private final boolean wantSSL;
@@ -132,11 +131,6 @@
     bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
         HttpRemotePeerProvider.class).in(RequestScoped.class);
 
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().toInstance(registerInParentInjectors());
-      }
-    });
+    listener().toInstance(registerInParentInjectors());
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 28e361c..2dde16e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -168,7 +168,7 @@
     Element userlistElement = HtmlDomUtil.find(doc, "userlist");
     ReviewDb db = schema.open();
     try {
-      ResultSet<Account> accounts = db.accounts().firstNById(5);
+      ResultSet<Account> accounts = db.accounts().firstNById(100);
       for (Account a : accounts) {
         String displayName;
         if (a.getUserName() != null) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
index 75a7e07..eb6d1f7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpAuthFilter.java
@@ -159,13 +159,13 @@
         return c > 0 ? auth.substring(0, c) : null;
 
       } else if (auth.startsWith("Digest ")) {
-        int u = auth.indexOf("username=\"");
+        final int u = auth.indexOf("username=\"");
         if (u <= 0) {
           return null;
         }
         auth = auth.substring(u + 10);
-        int e = auth.indexOf('"');
-        return e > 0 ? auth.substring(0, auth.indexOf('"')) : null;
+        final int e = auth.indexOf('"');
+        return e > 0 ? auth.substring(0, e) : null;
 
       } else {
         return null;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index 0769865..c2b0cc8 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -123,7 +123,7 @@
 
     if (!_env.envMap.containsKey("SystemRoot")) {
       String os = System.getProperty("os.name");
-      if (os != null && os.toLowerCase().indexOf("windows") != -1) {
+      if (os != null && os.toLowerCase().contains("windows")) {
         String sysroot = System.getenv("SystemRoot");
         if (sysroot == null || sysroot.isEmpty()) {
           sysroot = "C:\\WINDOWS";
@@ -261,15 +261,15 @@
       p.print("  my $h = shift;\n");
       p.print("  my $q;\n");
       p.print("  if (!$h || $h eq 'HEAD') {\n");
-      p.print("    $q = qq{#q,project:$ENV{'GERRIT_PROJECT_NAME'},n,z};\n");
+      p.print("    $q = qq{#q,project:$ENV{'GERRIT_PROJECT_NAME'}};\n");
       p.print("  } elsif ($h =~ /^refs\\/heads\\/([-\\w]+)$/) {\n");
       p.print("    $q = qq{#q,project:$ENV{'GERRIT_PROJECT_NAME'}");
-      p.print("+branch:$1,n,z};\n"); // wrapped
+      p.print("+branch:$1};\n"); // wrapped
       p.print("  } elsif ($h =~ /^refs\\/changes\\/\\d{2}\\/(\\d+)\\/\\d+$/) ");
       p.print("{\n"); // wrapped
       p.print("    $q = qq{#/c/$1};\n");
       p.print("  } else {\n");
-      p.print("    $q = qq{#/q/$h,n,z};\n");
+      p.print("    $q = qq{#/q/$h};\n");
       p.print("  }\n");
       p.print("  my $r = qq{$ENV{'GERRIT_CONTEXT_PATH'}$q};\n");
       p.print("  push @{$feature{'actions'}{'default'}},\n");
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index b2972d9..8f66b05 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -45,6 +45,8 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -98,7 +100,8 @@
       = Maps.newConcurrentMap();
 
   @Inject
-  HttpPluginServlet(MimeUtilFileTypeRegistry mimeUtil,
+  HttpPluginServlet(
+      MimeUtilFileTypeRegistry mimeUtil,
       @CanonicalWebUrl Provider<String> webUrl,
       @Named(HttpPluginModule.PLUGIN_RESOURCES) Cache<ResourceKey, Resource> cache,
       @GerritServerConfig Config cfg,
@@ -277,12 +280,16 @@
 
     if (file.startsWith(holder.staticPrefix)) {
       JarFile jar = holder.plugin.getJarFile();
-      JarEntry entry = jar.getJarEntry(file);
-      if (exists(entry)) {
-        sendResource(jar, entry, key, res);
+      if (jar != null) {
+        JarEntry entry = jar.getJarEntry(file);
+        if (exists(entry)) {
+          sendResource(jar, entry, key, res);
+        } else {
+          resourceCache.put(key, Resource.NOT_FOUND);
+          Resource.NOT_FOUND.send(req, res);
+        }
       } else {
-        resourceCache.put(key, Resource.NOT_FOUND);
-        Resource.NOT_FOUND.send(req, res);
+        sendJsPlugin(holder.plugin, key, req, res);
       }
     } else if (file.equals(
         holder.docPrefix.substring(0, holder.docPrefix.length() - 1))) {
@@ -316,7 +323,7 @@
       String sectionTitle, StringBuilder md, String prefix,
       int nameOffset) throws IOException {
     if (!entries.isEmpty()) {
-      md.append("## " + sectionTitle +  " ##\n");
+      md.append("## ").append(sectionTitle).append(" ##\n");
       for(JarEntry entry : entries) {
         String rsrc = entry.getName().substring(prefix.length());
         String entryTitle;
@@ -581,21 +588,43 @@
           .setLastModified(time));
       res.getOutputStream().write(data);
     } else {
-      InputStream in = jar.getInputStream(entry);
+      writeToResponse(res, jar.getInputStream(entry));
+    }
+  }
+
+  private void sendJsPlugin(Plugin plugin, ResourceKey key,
+      HttpServletRequest req, HttpServletResponse res) throws IOException {
+    File pluginFile = plugin.getSrcFile();
+    if (req.getPathInfo().equals(getJsPluginPath(plugin)) && pluginFile.exists()) {
+      res.setHeader("Content-Length", Long.toString(pluginFile.length()));
+      res.setContentType("application/javascript");
+      writeToResponse(res, new FileInputStream(pluginFile));
+    } else {
+      resourceCache.put(key, Resource.NOT_FOUND);
+      Resource.NOT_FOUND.send(req, res);
+    }
+  }
+
+  private static String getJsPluginPath(Plugin plugin) {
+    return String.format("%s/static/%s", plugin.getName(), plugin.getSrcFile()
+        .getName());
+  }
+
+  private void writeToResponse(HttpServletResponse res, InputStream in)
+      throws IOException {
+    try {
+      OutputStream out = res.getOutputStream();
       try {
-        OutputStream out = res.getOutputStream();
-        try {
-          byte[] tmp = new byte[1024];
-          int n;
-          while ((n = in.read(tmp)) > 0) {
-            out.write(tmp, 0, n);
-          }
-        } finally {
-          out.close();
+        byte[] tmp = new byte[1024];
+        int n;
+        while ((n = in.read(tmp)) > 0) {
+          out.write(tmp, 0, n);
         }
       } finally {
-        in.close();
+        out.close();
       }
+    } finally {
+      in.close();
     }
   }
 
@@ -627,8 +656,12 @@
     }
 
     private static String getPrefix(Plugin plugin, String attr, String def) {
+      JarFile jarFile = plugin.getJarFile();
+      if (jarFile == null) {
+        return def;
+      }
       try {
-        String prefix = plugin.getJarFile().getManifest().getMainAttributes()
+        String prefix = jarFile.getManifest().getMainAttributes()
             .getValue(attr);
         if (prefix != null) {
           return CharMatcher.is('/').trimFrom(prefix) + "/";
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
index 47a9263..9aec609 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -231,6 +231,7 @@
     CacheHeaders.setNotCacheable(rsp);
 
     OutputStream out;
+    @SuppressWarnings("resource")
     ZipOutputStream zo;
 
     final MimeType contentType = registry.getMimeType(path, raw);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index 508c41a..fda2c0d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.httpd.raw;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
@@ -22,6 +23,7 @@
 import com.google.gerrit.common.data.GerritConfig;
 import com.google.gerrit.common.data.HostPageData;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.systemstatus.MessageOfTheDay;
 import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.httpd.HtmlDomUtil;
 import com.google.gerrit.httpd.WebSession;
@@ -29,8 +31,6 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gwtexpui.linker.server.Permutation;
-import com.google.gwtexpui.linker.server.PermutationSelector;
 import com.google.gwtexpui.server.CacheHeaders;
 import com.google.gwtjsonrpc.server.JsonServlet;
 import com.google.gwtjsonrpc.server.RPCServletUtils;
@@ -51,9 +51,8 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.StringWriter;
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -74,12 +73,12 @@
   private final Provider<WebSession> session;
   private final GerritConfig config;
   private final DynamicSet<WebUiPlugin> plugins;
+  private final DynamicSet<MessageOfTheDay> messages;
   private final HostPageData.Theme signedOutTheme;
   private final HostPageData.Theme signedInTheme;
   private final SitePaths site;
   private final Document template;
   private final String noCacheName;
-  private final PermutationSelector selector;
   private final boolean refreshHeaderFooter;
   private final StaticServlet staticServlet;
   private volatile Page page;
@@ -89,6 +88,7 @@
       final SitePaths sp, final ThemeFactory themeFactory,
       final GerritConfig gc, final ServletContext servletContext,
       final DynamicSet<WebUiPlugin> webUiPlugins,
+      final DynamicSet<MessageOfTheDay> motd,
       @GerritServerConfig final Config cfg,
       final StaticServlet ss)
       throws IOException, ServletException {
@@ -96,12 +96,12 @@
     session = w;
     config = gc;
     plugins = webUiPlugins;
+    messages = motd;
     signedOutTheme = themeFactory.getSignedOutTheme();
     signedInTheme = themeFactory.getSignedInTheme();
     site = sp;
     refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
     staticServlet = ss;
-    boolean checkUserAgent = cfg.getBoolean("site", "checkUserAgent", true);
 
     final String pageName = "HostPage.html";
     template = HtmlDomUtil.parseFile(getClass(), pageName);
@@ -146,10 +146,6 @@
     }
 
     noCacheName = src;
-    selector = new PermutationSelector("gerrit_ui");
-    if (checkUserAgent && !IS_DEV) {
-      selector.init(servletContext);
-    }
     page = new Page();
   }
 
@@ -201,6 +197,7 @@
       w.write(";");
     }
     plugins(w);
+    messages(w);
 
     final byte[] hpd = w.toString().getBytes("UTF-8");
     final byte[] raw = Bytes.concat(page.part1, hpd, page.part2);
@@ -238,17 +235,31 @@
     }
   }
 
+  private void messages(StringWriter w) {
+    List<HostPageData.Message> list = new ArrayList<>(2);
+    for (MessageOfTheDay motd : messages) {
+      String html = motd.getHtmlMessage();
+      if (!Strings.isNullOrEmpty(html)) {
+        HostPageData.Message m = new HostPageData.Message();
+        m.id = motd.getMessageId();
+        m.redisplay = motd.getRedisplay();
+        m.html = html;
+        list.add(m);
+      }
+    }
+    if (!list.isEmpty()) {
+      w.write(HPD_ID + ".messages=");
+      json(list, w);
+      w.write(";");
+    }
+  }
+
   private Page.Content select(HttpServletRequest req) {
     Page pg = get();
     if ("1".equals(req.getParameter("dbg"))) {
       return pg.debug;
-    } else if ("0".equals(req.getParameter("s"))) {
-      // If s=0 is used in the URL, the user has explicitly asked us
-      // to not perform selection on the server side, perhaps due to
-      // it incorrectly guessing their user agent.
-      return pg.get(null);
     }
-    return pg.get(selector.select(req));
+    return pg.opt;
   }
 
   private void insertETags(Element e) {
@@ -289,7 +300,7 @@
     private final FileInfo css;
     private final FileInfo header;
     private final FileInfo footer;
-    private final Map<Permutation, Content> permutations;
+    private final Content opt;
     private final Content debug;
 
     Page() throws IOException {
@@ -313,33 +324,16 @@
       data.appendChild(hostDoc.createTextNode(w.toString()));
       data.appendChild(hostDoc.createComment(HPD_ID));
 
-      permutations = new HashMap<Permutation, Content>();
-      for (Permutation p : selector.getPermutations()) {
-        final Document d = HtmlDomUtil.clone(hostDoc);
-        Element nocache = HtmlDomUtil.find(d, "gerrit_module");
-        nocache.getParentNode().removeChild(nocache);
-        p.inject(d);
-        permutations.put(p, new Content(d));
-      }
-
       Element nocache = HtmlDomUtil.find(hostDoc, "gerrit_module");
       asScript(nocache);
       nocache.removeAttribute("id");
       nocache.setAttribute("src", noCacheName);
-      permutations.put(null, new Content(hostDoc));
+      opt = new Content(hostDoc);
 
       nocache.setAttribute("src", "gerrit_ui/gerrit_dbg.nocache.js");
       debug = new Content(hostDoc);
     }
 
-    Content get(Permutation p) {
-      Content c = permutations.get(p);
-      if (c == null) {
-        c = permutations.get(null);
-      }
-      return c;
-    }
-
     boolean isStale() {
       return css.isStale() || header.isStale() || footer.isStale();
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RobotsServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RobotsServlet.java
index d19a0ce..1d8e74d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RobotsServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/RobotsServlet.java
@@ -37,7 +37,7 @@
 /**
  * This class provides a mechanism to use a configurable robots.txt file,
  * outside of the .war of the application. In order to configure it add the
- * following to the <code>httpd</code> section of the <code>gerrit.conf</code>
+ * following to the {@code httpd} section of the {@code gerrit.conf}
  * file:
  *
  * <pre>
@@ -49,7 +49,7 @@
  * the site directory, if it is absolute it will be used as is.
  *
  * If the specified file doesn't exist or isn't readable the servlet will
- * default to the <code>robots.txt</code> file bundled with the .war file of the
+ * default to the {@code robots.txt} file bundled with the .war file of the
  * application.
  */
 @SuppressWarnings("serial")
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SshInfoServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SshInfoServlet.java
index 5756880..83120e0 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SshInfoServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/SshInfoServlet.java
@@ -32,14 +32,14 @@
 /**
  * Servlet hosting an SSH daemon on another port. During a standard HTTP GET
  * request the servlet returns the hostname and port number back to the client
- * in the form <code>${host} ${port}</code>.
+ * in the form {@code ${host} ${port}}.
  * <p>
- * Use a Git URL such as <code>ssh://${email}@${host}:${port}/${path}</code>,
- * e.g. <code>ssh://sop@google.com@gerrit.com:8010/tools/gerrit.git</code> to
+ * Use a Git URL such as {@code ssh://${email}@${host}:${port}/${path}},
+ * e.g. {@code ssh://sop@google.com@gerrit.com:8010/tools/gerrit.git} to
  * access the SSH daemon itself.
  * <p>
  * Versions of Git before 1.5.3 may require setting the username and port
- * properties in the user's <code>~/.ssh/config</code> file, and using a host
+ * properties in the user's {@code ~/.ssh/config} file, and using a host
  * alias through a URL such as <code>gerrit-alias:/tools/gerrit.git:
  * <pre>
  * Host gerrit-alias
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java
index 94f8c1b..52b7a5c9 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticServlet.java
@@ -57,7 +57,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 
-/** Sends static content from the site 's <code>static/</code> subdirectory. */
+/** Sends static content from the site 's {@code static/} subdirectory. */
 @SuppressWarnings("serial")
 @Singleton
 public class StaticServlet extends HttpServlet {
@@ -151,7 +151,7 @@
 
   private static boolean isUnreasonableName(String name) {
     if (name.length() < 1) return true;
-    if (name.indexOf('\\') >= 0) return true; // no windows/dos stlye paths
+    if (name.contains("\\")) return true; // no windows/dos style paths
     if (name.startsWith("../")) return true; // no "../etc/passwd"
     if (name.contains("/../")) return true; // no "foo/../etc/passwd"
     if (name.contains("/./")) return true; // "foo/./foo" is insane to ask
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 517b017..ce450dc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -14,11 +14,10 @@
 
 package com.google.gerrit.httpd.restapi;
 
-import static com.google.common.base.Charsets.UTF_8;
 import static com.google.common.base.Preconditions.checkNotNull;
-
 import static java.math.RoundingMode.CEILING;
-
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
 import static javax.servlet.http.HttpServletResponse.SC_CREATED;
@@ -30,7 +29,6 @@
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
 
-import com.google.common.base.Charsets;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
@@ -64,6 +62,7 @@
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestCollection;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -237,7 +236,7 @@
           }
         }
         if (viewData.view == null) {
-          viewData = view(rc, req.getMethod(), path);
+          viewData = view(rsrc, rc, req.getMethod(), path);
         }
       }
       checkRequiresCapability(viewData);
@@ -271,14 +270,14 @@
                     || "PUT".equals(req.getMethod()))) {
               @SuppressWarnings("unchecked")
               AcceptsCreate<RestResource> ac = (AcceptsCreate<RestResource>) c;
-              viewData = new ViewData(null, ac.create(rsrc, id));
+              viewData = new ViewData(viewData.pluginName, ac.create(rsrc, id));
               status = SC_CREATED;
             } else {
               throw e;
             }
           }
           if (viewData.view == null) {
-            viewData = view(c, req.getMethod(), path);
+            viewData = view(rsrc, c, req.getMethod(), path);
           }
         }
         checkRequiresCapability(viewData);
@@ -607,7 +606,7 @@
     throw new InstantiationException("Cannot make " + type);
   }
 
-  private static void replyJson(@Nullable HttpServletRequest req,
+  public static void replyJson(@Nullable HttpServletRequest req,
       HttpServletResponse res,
       Multimap<String, String> config,
       Object result)
@@ -739,7 +738,7 @@
         @Override
         public void writeTo(OutputStream out) throws IOException {
           OutputStream e = BaseEncoding.base64().encodingStream(
-              new OutputStreamWriter(out, Charsets.ISO_8859_1));
+              new OutputStreamWriter(out, ISO_8859_1));
           src.writeTo(e);
           e.flush();
         }
@@ -775,9 +774,10 @@
   }
 
   private ViewData view(
+      RestResource rsrc,
       RestCollection<RestResource, RestResource> rc,
-      String method, List<IdString> path) throws ResourceNotFoundException,
-      MethodNotAllowedException, AmbiguousViewException {
+      String method, List<IdString> path) throws AmbiguousViewException,
+      RestApiException {
     DynamicMap<RestView<RestResource>> views = rc.views();
     final IdString projection = path.isEmpty()
         ? IdString.fromUrl("/")
@@ -800,6 +800,14 @@
       if (view != null) {
         return new ViewData(p.get(0), view);
       }
+      view = views.get(p.get(0), "GET." + viewname);
+      if (view != null) {
+        if (view instanceof AcceptsPost && "POST".equals(method)) {
+          @SuppressWarnings("unchecked")
+          AcceptsPost<RestResource> ap = (AcceptsPost<RestResource>) view;
+          return new ViewData(p.get(0), ap.post(rsrc));
+        }
+      }
       throw new ResourceNotFoundException(projection);
     }
 
@@ -933,7 +941,7 @@
   private static boolean acceptsGzip(HttpServletRequest req) {
     if (req != null) {
       String accepts = req.getHeader(HttpHeaders.ACCEPT_ENCODING);
-      return accepts != null && accepts.indexOf("gzip") != -1;
+      return accepts != null && accepts.contains("gzip");
     }
     return false;
   }
@@ -959,7 +967,7 @@
     int max = 4 * IntMath.divide((int) bin.getContentLength(), 3, CEILING);
     TemporaryBuffer.Heap buf = heap(max);
     OutputStream encoded = BaseEncoding.base64().encodingStream(
-        new OutputStreamWriter(buf, Charsets.ISO_8859_1));
+        new OutputStreamWriter(buf, ISO_8859_1));
     bin.writeTo(encoded);
     encoded.close();
     return asBinaryResult(buf);
@@ -974,6 +982,7 @@
     return asBinaryResult(buf).setContentType(bin.getContentType());
   }
 
+  @SuppressWarnings("resource")
   private static BinaryResult asBinaryResult(final TemporaryBuffer.Heap buf) {
     return new BinaryResult() {
       @Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
index 940e2e6..73b546b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/BaseServiceImplementation.java
@@ -53,7 +53,7 @@
   }
 
   /**
-   * Executes <code>action.run</code> with an active ReviewDb connection.
+   * Executes {@code action.run} with an active ReviewDb connection.
    * <p>
    * A database handle is automatically opened and closed around the action's
    * {@link Action#run(ReviewDb)} method. OrmExceptions are caught and passed
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 5a0d328..ec96f0b 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
@@ -104,7 +104,6 @@
       //
       if (!call.requireXsrfValid() || !session.get().isSignedIn()) {
         call.onFailure(new NotSignedInException());
-        return;
       }
     }
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
index 18cce28..f8d43a8 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
@@ -129,7 +129,7 @@
       VisibilityControl visibilityControl)
       throws OrmException {
     if (!suggestAccounts) {
-      return Collections.<AccountInfo> emptyList();
+      return Collections.emptyList();
     }
 
     final String a = query;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
index 0a9ed28..90357ae 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
@@ -69,15 +69,10 @@
   }
 
   public void myAccount(final AsyncCallback<Account> callback) {
-    callback.onSuccess(currentUser.get().getAccount());
-  }
-
-  @Override
-  public void myDiffPreferences(AsyncCallback<AccountDiffPreference> callback) {
-    run(callback, new Action<AccountDiffPreference>() {
+    run(callback, new Action<Account>() {
       @Override
-      public AccountDiffPreference run(ReviewDb db) throws OrmException {
-        return currentUser.get().getAccountDiffPreference();
+      public Account run(ReviewDb db) throws OrmException {
+        return db.accounts().get(currentUser.get().getAccountId());
       }
     });
   }
@@ -153,9 +148,7 @@
 
         if (filter != null) {
           try {
-            ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
-            builder.setAllowFileRegex(true);
-            builder.parse(filter);
+            queryBuilder.create(currentUser.get()).parse(filter);
           } catch (QueryParseException badFilter) {
             throw new InvalidQueryException(badFilter.getMessage(), filter);
           }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
deleted file mode 100644
index cf8b4db..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ /dev/null
@@ -1,380 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.changedetail;
-
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.ChangeInfo;
-import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.common.errors.NoSuchEntityException;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.httpd.rpc.Handler;
-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.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetAncestor;
-import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.AnonymousUser;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.ProjectUtil;
-import com.google.gerrit.server.account.AccountInfoCacheFactory;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.change.Mergeable;
-import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.changedetail.RebaseChange;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.GitRepositoryManager;
-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.project.NoSuchProjectException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/** Creates a {@link ChangeDetail} from a {@link Change}. */
-public class ChangeDetailFactory extends Handler<ChangeDetail> {
-  private static final Logger log = LoggerFactory
-      .getLogger(ChangeDetailFactory.class);
-
-  public interface Factory {
-    ChangeDetailFactory create(Change.Id id);
-  }
-
-  private final ChangeControl.Factory changeControlFactory;
-  private final PatchSetDetailFactory.Factory patchSetDetail;
-  private final AccountInfoCacheFactory aic;
-  private final AnonymousUser anonymousUser;
-  private final ReviewDb db;
-  private final GitRepositoryManager repoManager;
-
-  private final Change.Id changeId;
-
-  private ChangeDetail detail;
-  private ChangeControl control;
-  private Map<PatchSet.Id, PatchSet> patchsetsById;
-
-  private final Mergeable mergeable;
-  private boolean testMerge;
-
-  private List<PatchSetAncestor> currentPatchSetAncestors;
-  private List<PatchSet> currentDepPatchSets;
-  private List<Change> currentDepChanges;
-
-  @Inject
-  ChangeDetailFactory(
-      final PatchSetDetailFactory.Factory patchSetDetail, final ReviewDb db,
-      final GitRepositoryManager repoManager,
-      final ChangeControl.Factory changeControlFactory,
-      final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
-      final AnonymousUser anonymousUser,
-      final Mergeable mergeable,
-      @GerritServerConfig final Config cfg,
-      @Assisted final Change.Id id) {
-    this.patchSetDetail = patchSetDetail;
-    this.db = db;
-    this.repoManager = repoManager;
-    this.changeControlFactory = changeControlFactory;
-    this.anonymousUser = anonymousUser;
-    this.aic = accountInfoCacheFactory.create();
-
-    this.mergeable = mergeable;
-    this.testMerge = cfg.getBoolean("changeMerge", "test", false);
-
-    this.changeId = id;
-  }
-
-  @Override
-  public ChangeDetail call() throws OrmException, NoSuchEntityException,
-      PatchSetInfoNotAvailableException, NoSuchChangeException,
-      RepositoryNotFoundException, IOException, NoSuchProjectException {
-    control = changeControlFactory.validateFor(changeId);
-    final Change change = control.getChange();
-    final PatchSet patch = db.patchSets().get(change.currentPatchSetId());
-    if (patch == null) {
-      throw new NoSuchEntityException();
-    }
-
-    aic.want(change.getOwner());
-
-    detail = new ChangeDetail();
-    detail.setChange(change);
-    detail.setAllowsAnonymous(control.forUser(anonymousUser).isVisible(db));
-
-    detail.setCanAbandon(change.getStatus() != Change.Status.DRAFT && change.getStatus().isOpen() && control.canAbandon());
-    detail.setCanPublish(control.canPublish(db));
-    detail.setCanRestore(change.getStatus() == Change.Status.ABANDONED
-        && control.canRestore()
-        && ProjectUtil.branchExists(repoManager, change.getDest()));
-    detail.setCanDeleteDraft(control.canDeleteDraft(db));
-    detail.setStarred(control.getCurrentUser().getStarredChanges().contains(
-        changeId));
-
-    detail.setCanRevert(change.getStatus() == Change.Status.MERGED && control.canAddPatchSet());
-    detail.setCanCherryPick(control.getProjectControl().canUpload());
-    detail.setCanEdit(control.getRefControl().canWrite());
-    detail.setCanEditCommitMessage(change.getStatus().isOpen() && control.canAddPatchSet());
-    detail.setCanEditTopicName(control.canEditTopicName());
-
-    List<SubmitRecord> submitRecords = control.getSubmitRecords(db, patch);
-    for (SubmitRecord rec : submitRecords) {
-      if (rec.labels != null) {
-        for (SubmitRecord.Label lbl : rec.labels) {
-          aic.want(lbl.appliedBy);
-        }
-      }
-      if (detail.getChange().getStatus().isOpen()
-          && rec.status == SubmitRecord.Status.OK
-          && control.getRefControl().canSubmit()
-          && ProjectUtil.branchExists(repoManager, change.getDest())) {
-        detail.setCanSubmit(true);
-      }
-    }
-    detail.setSubmitRecords(submitRecords);
-
-    detail.setSubmitTypeRecord(control.getSubmitTypeRecord(db, patch));
-
-    patchsetsById = new HashMap<PatchSet.Id, PatchSet>();
-    loadPatchSets();
-    loadMessages();
-    if (change.currentPatchSetId() != null) {
-      loadCurrentPatchSet();
-    }
-    load();
-
-    detail.setCanRebase(detail.getChange().getStatus().isOpen() &&
-        control.canRebase() &&
-        RebaseChange.canDoRebase(db, change, repoManager,
-            currentPatchSetAncestors, currentDepPatchSets, currentDepChanges));
-    detail.setAccounts(aic.create());
-    return detail;
-  }
-
-  private void loadPatchSets() throws OrmException {
-    ResultSet<PatchSet> source = db.patchSets().byChange(changeId);
-    List<PatchSet> patches = new ArrayList<PatchSet>();
-    Set<PatchSet.Id> patchesWithDraftComments = new HashSet<PatchSet.Id>();
-    final CurrentUser user = control.getCurrentUser();
-    final Account.Id me =
-        user.isIdentifiedUser() ? ((IdentifiedUser) user).getAccountId()
-            : null;
-    for (PatchSet ps : source) {
-      final PatchSet.Id psId = ps.getId();
-      if (control.isPatchVisible(ps, db)) {
-        patches.add(ps);
-        if (me != null
-            && db.patchComments().draftByPatchSetAuthor(psId, me)
-                .iterator().hasNext()) {
-          patchesWithDraftComments.add(psId);
-        }
-      }
-      patchsetsById.put(psId, ps);
-    }
-    detail.setPatchSets(patches);
-    detail.setPatchSetsWithDraftComments(patchesWithDraftComments);
-  }
-
-  private void loadMessages() throws OrmException {
-    ResultSet<ChangeMessage> source = db.changeMessages().byChange(changeId);
-    List<ChangeMessage> msgList = new ArrayList<ChangeMessage>();
-    for (ChangeMessage msg : source) {
-      PatchSet.Id id = msg.getPatchSetId();
-      if (id != null) {
-        PatchSet ps = patchsetsById.get(msg.getPatchSetId());
-        if (ps != null && control.isPatchVisible(ps, db)) {
-          msgList.add(msg);
-        }
-      } else {
-        // Not guaranteed to have a non-null patchset id, so just display it.
-        msgList.add(msg);
-      }
-    }
-    detail.setMessages(msgList);
-    for (final ChangeMessage m : detail.getMessages()) {
-      aic.want(m.getAuthor());
-    }
-  }
-
-  private void load() throws OrmException, NoSuchChangeException,
-      NoSuchProjectException {
-    final Change.Status status = detail.getChange().getStatus();
-    if ((status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) &&
-        testMerge) {
-      try {
-        detail.getChange().setMergeable(mergeable.apply(new RevisionResource(
-            new ChangeResource(control),
-            detail.getCurrentPatchSet())).mergeable);
-      } catch (RepositoryNotFoundException e) {
-        log.warn("Cannot check mergeable", e);
-      } catch (ResourceConflictException e) {
-        log.warn("Cannot check mergeable", e);
-      } catch (BadRequestException e) {
-        log.warn("Cannot check mergeable", e);
-      } catch (AuthException e) {
-        log.warn("Cannot check mergeable", e);
-      } catch (IOException e) {
-        log.warn("Cannot check mergeable", e);
-      }
-    }
-  }
-
-  private boolean isReviewer(Change change) {
-    // Return true if the currently logged in user is a reviewer of the change.
-    try {
-      return control.isReviewer(db, new ChangeData(change));
-    } catch (OrmException e) {
-        return false;
-    }
-  }
-
-  private void loadCurrentPatchSet() throws OrmException,
-      NoSuchEntityException, PatchSetInfoNotAvailableException,
-      NoSuchChangeException {
-    currentDepPatchSets = new ArrayList<PatchSet>();
-    currentDepChanges = new ArrayList<Change>();
-    final PatchSet currentPatch = findCurrentOrLatestPatchSet();
-    final PatchSet.Id psId = currentPatch.getId();
-    final PatchSetDetailFactory loader = patchSetDetail.create(null, psId, null);
-    loader.patchSet = currentPatch;
-    loader.control = control;
-    detail.setCurrentPatchSetDetail(loader.call());
-    detail.setCurrentPatchSetId(psId);
-
-    final HashSet<Change.Id> changesToGet = new HashSet<Change.Id>();
-    final HashMap<Change.Id,PatchSet.Id> ancestorPatchIds =
-        new HashMap<Change.Id,PatchSet.Id>();
-    final List<Change.Id> ancestorOrder = new ArrayList<Change.Id>();
-    currentPatchSetAncestors = db.patchSetAncestors().ancestorsOf(psId).toList();
-    for (PatchSetAncestor a : currentPatchSetAncestors) {
-      for (PatchSet p : db.patchSets().byRevision(a.getAncestorRevision())) {
-        currentDepPatchSets.add(p);
-        final Change.Id ck = p.getId().getParentKey();
-        if (changesToGet.add(ck)) {
-          ancestorPatchIds.put(ck, p.getId());
-          ancestorOrder.add(ck);
-        }
-      }
-    }
-
-    final Set<PatchSet.Id> descendants = new HashSet<PatchSet.Id>();
-    RevId cprev;
-    for (PatchSet p : detail.getPatchSets()) {
-      cprev = p.getRevision();
-      if (cprev != null) {
-        for (PatchSetAncestor a : db.patchSetAncestors().descendantsOf(cprev)) {
-          if (descendants.add(a.getPatchSet())) {
-            changesToGet.add(a.getPatchSet().getParentKey());
-          }
-        }
-      }
-    }
-    final Map<Change.Id, Change> m =
-        db.changes().toMap(db.changes().get(changesToGet));
-
-    final CurrentUser currentUser = control.getCurrentUser();
-    Account.Id currentUserId = null;
-    if (currentUser.isIdentifiedUser()) {
-        currentUserId = ((IdentifiedUser) currentUser).getAccountId();
-    }
-
-    final ArrayList<ChangeInfo> dependsOn = new ArrayList<ChangeInfo>();
-    for (final Change.Id a : ancestorOrder) {
-      final Change ac = m.get(a);
-      if (ac != null && ac.getProject().equals(detail.getChange().getProject())) {
-        currentDepChanges.add(ac);
-        if (ac.getStatus().getCode() != Change.STATUS_DRAFT
-            || ac.getOwner().equals(currentUserId)
-            || isReviewer(ac)) {
-          dependsOn.add(newChangeInfo(ac, ancestorPatchIds));
-        }
-      }
-    }
-
-    final ArrayList<ChangeInfo> neededBy = new ArrayList<ChangeInfo>();
-    for (final PatchSet.Id a : descendants) {
-      final Change ac = m.get(a.getParentKey());
-      if (ac != null && ac.currentPatchSetId().equals(a)) {
-        if (ac.getStatus().getCode() != Change.STATUS_DRAFT
-            || ac.getOwner().equals(currentUserId)
-            || isReviewer(ac)) {
-          neededBy.add(newChangeInfo(ac, null));
-        }
-      }
-    }
-
-    Collections.sort(neededBy, new Comparator<ChangeInfo>() {
-      public int compare(final ChangeInfo o1, final ChangeInfo o2) {
-        return o1.getId().get() - o2.getId().get();
-      }
-    });
-
-    detail.setDependsOn(dependsOn);
-    detail.setNeededBy(neededBy);
-  }
-
-  private PatchSet findCurrentOrLatestPatchSet() {
-    PatchSet currentPatch = detail.getCurrentPatchSet();
-    // If the current patch set is a draft and user can't see it, set the
-    // current patch set to whatever the latest one is
-    if (currentPatch == null) {
-      List<PatchSet> patchSets = detail.getPatchSets();
-      if (!detail.getPatchSets().isEmpty()) {
-        currentPatch = patchSets.get(patchSets.size() - 1);
-      } else {
-        // Shouldn't happen, change shouldn't be visible if all the patchsets
-        // are drafts
-      }
-    }
-    return currentPatch;
-  }
-
-  private ChangeInfo newChangeInfo(final Change ac,
-      Map<Change.Id,PatchSet.Id> ancestorPatchIds) {
-    aic.want(ac.getOwner());
-    ChangeInfo ci;
-    if (ancestorPatchIds == null) {
-      ci = new ChangeInfo(ac);
-    } else {
-      ci = new ChangeInfo(ac, ancestorPatchIds.get(ac.getId()));
-    }
-    ci.setStarred(isStarred(ac));
-    return ci;
-  }
-
-  private boolean isStarred(final Change ac) {
-    return control.getCurrentUser().getStarredChanges().contains(ac.getId());
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailServiceImpl.java
index 8090451..538275f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailServiceImpl.java
@@ -14,42 +14,20 @@
 
 package com.google.gerrit.httpd.rpc.changedetail;
 
-import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.ChangeDetailService;
-import com.google.gerrit.common.data.IncludedInDetail;
 import com.google.gerrit.common.data.PatchSetDetail;
-import com.google.gerrit.common.data.PatchSetPublishDetail;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwtjsonrpc.common.AsyncCallback;
 import com.google.inject.Inject;
 
 class ChangeDetailServiceImpl implements ChangeDetailService {
-  private final ChangeDetailFactory.Factory changeDetail;
-  private final IncludedInDetailFactory.Factory includedInDetail;
   private final PatchSetDetailFactory.Factory patchSetDetail;
-  private final PatchSetPublishDetailFactory.Factory patchSetPublishDetail;
 
   @Inject
-  ChangeDetailServiceImpl(final ChangeDetailFactory.Factory changeDetail,
-      final IncludedInDetailFactory.Factory includedInDetail,
-      final PatchSetDetailFactory.Factory patchSetDetail,
-      final PatchSetPublishDetailFactory.Factory patchSetPublishDetail) {
-    this.changeDetail = changeDetail;
-    this.includedInDetail = includedInDetail;
+  ChangeDetailServiceImpl(
+      final PatchSetDetailFactory.Factory patchSetDetail) {
     this.patchSetDetail = patchSetDetail;
-    this.patchSetPublishDetail = patchSetPublishDetail;
-  }
-
-  public void changeDetail(final Change.Id id,
-      final AsyncCallback<ChangeDetail> callback) {
-    changeDetail.create(id).to(callback);
-  }
-
-  public void includedInDetail(final Change.Id id,
-      final AsyncCallback<IncludedInDetail> callback) {
-    includedInDetail.create(id).to(callback);
   }
 
   public void patchSetDetail(PatchSet.Id id,
@@ -61,9 +39,4 @@
       AccountDiffPreference diffPrefs, AsyncCallback<PatchSetDetail> callback) {
     patchSetDetail.create(baseId, id, diffPrefs).to(callback);
   }
-
-  public void patchSetPublishDetail(final PatchSet.Id id,
-      final AsyncCallback<PatchSetPublishDetail> callback) {
-    patchSetPublishDetail.create(id).to(callback);
-  }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java
deleted file mode 100644
index 53e0d9d..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.changedetail;
-
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.ChangeManageService;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSet.Id;
-import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.inject.Inject;
-
-class ChangeManageServiceImpl implements ChangeManageService {
-  private final RebaseChangeHandler.Factory rebaseChangeFactory;
-  private final PublishAction.Factory publishAction;
-  private final DeleteDraftChange.Factory deleteDraftChangeFactory;
-  private final EditCommitMessageHandler.Factory editCommitMessageHandlerFactory;
-
-  @Inject
-  ChangeManageServiceImpl(
-      final RebaseChangeHandler.Factory rebaseChangeFactory,
-      final PublishAction.Factory publishAction,
-      final DeleteDraftChange.Factory deleteDraftChangeFactory,
-      final EditCommitMessageHandler.Factory editCommitMessageHandler) {
-    this.rebaseChangeFactory = rebaseChangeFactory;
-    this.publishAction = publishAction;
-    this.deleteDraftChangeFactory = deleteDraftChangeFactory;
-    this.editCommitMessageHandlerFactory = editCommitMessageHandler;
-  }
-
-  public void rebaseChange(final PatchSet.Id patchSetId,
-      final AsyncCallback<ChangeDetail> callback) {
-    rebaseChangeFactory.create(patchSetId).to(callback);
-  }
-
-  public void publish(final PatchSet.Id patchSetId,
-      final AsyncCallback<ChangeDetail> callback) {
-    publishAction.create(patchSetId).to(callback);
-  }
-
-  public void deleteDraftChange(final PatchSet.Id patchSetId,
-      final AsyncCallback<VoidResult> callback) {
-    deleteDraftChangeFactory.create(patchSetId).to(callback);
-  }
-
-  public void createNewPatchSet(Id patchSetId, String message,
-      AsyncCallback<ChangeDetail> callback) {
-    editCommitMessageHandlerFactory.create(patchSetId, message).to(callback);
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java
index 48c13c2..840137a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java
@@ -28,17 +28,9 @@
     install(new FactoryModule() {
       @Override
       protected void configure() {
-        factory(EditCommitMessageHandler.Factory.class);
-        factory(RebaseChangeHandler.Factory.class);
-        factory(ChangeDetailFactory.Factory.class);
-        factory(IncludedInDetailFactory.Factory.class);
         factory(PatchSetDetailFactory.Factory.class);
-        factory(PatchSetPublishDetailFactory.Factory.class);
-        factory(PublishAction.Factory.class);
-        factory(DeleteDraftChange.Factory.class);
       }
     });
     rpc(ChangeDetailServiceImpl.class);
-    rpc(ChangeManageServiceImpl.class);
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java
deleted file mode 100644
index 0f088c9..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.changedetail;
-
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.io.IOException;
-
-class DeleteDraftChange extends Handler<VoidResult> {
-  interface Factory {
-    DeleteDraftChange create(PatchSet.Id patchSetId);
-  }
-
-  private final ChangeControl.Factory changeControlFactory;
-  private final ReviewDb db;
-  private final GitRepositoryManager gitManager;
-  private final GitReferenceUpdated gitRefUpdated;
-  private final ChangeIndexer indexer;
-
-  private final PatchSet.Id patchSetId;
-
-  @Inject
-  DeleteDraftChange(final ReviewDb db,
-      final ChangeControl.Factory changeControlFactory,
-      final GitRepositoryManager gitManager,
-      final GitReferenceUpdated gitRefUpdated,
-      final ChangeIndexer indexer,
-      @Assisted final PatchSet.Id patchSetId) {
-    this.changeControlFactory = changeControlFactory;
-    this.db = db;
-    this.gitManager = gitManager;
-    this.gitRefUpdated = gitRefUpdated;
-    this.indexer = indexer;
-
-    this.patchSetId = patchSetId;
-  }
-
-  @Override
-  public VoidResult call() throws NoSuchChangeException, OrmException, IOException {
-
-    final Change.Id changeId = patchSetId.getParentKey();
-    final ChangeControl control = changeControlFactory.validateFor(changeId);
-    if (!control.canDeleteDraft(db)) {
-      throw new NoSuchChangeException(changeId);
-    }
-
-    ChangeUtil.deleteDraftChange(patchSetId, gitManager, gitRefUpdated, db,
-        indexer);
-    return VoidResult.INSTANCE;
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java
deleted file mode 100644
index 564c136..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/EditCommitMessageHandler.java
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.changedetail;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.common.errors.NoSuchEntityException;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.mail.CommitMessageEditedSender;
-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;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gwtorm.server.OrmException;
-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.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-
-import java.io.IOException;
-
-class EditCommitMessageHandler extends Handler<ChangeDetail> {
-  interface Factory {
-    EditCommitMessageHandler create(PatchSet.Id patchSetId, String message);
-  }
-
-  private final ChangeControl.Factory changeControlFactory;
-  private final ReviewDb db;
-  private final IdentifiedUser currentUser;
-  private final ChangeDetailFactory.Factory changeDetailFactory;
-  private final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory;
-  private final PatchSet.Id patchSetId;
-  @Nullable
-  private final String message;
-  private final GitRepositoryManager gitManager;
-  private final PersonIdent myIdent;
-  private final PatchSetInserter.Factory patchSetInserterFactory;
-
-  @Inject
-  EditCommitMessageHandler(final ChangeControl.Factory changeControlFactory,
-      final ReviewDb db, final IdentifiedUser currentUser,
-      final ChangeDetailFactory.Factory changeDetailFactory,
-      final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory,
-      @Assisted final PatchSet.Id patchSetId,
-      @Assisted @Nullable final String message,
-      final GitRepositoryManager gitManager,
-      @GerritPersonIdent final PersonIdent myIdent,
-      final PatchSetInserter.Factory patchSetInserterFactory) {
-    this.changeControlFactory = changeControlFactory;
-    this.db = db;
-    this.currentUser = currentUser;
-    this.changeDetailFactory = changeDetailFactory;
-    this.commitMessageEditedSenderFactory = commitMessageEditedSenderFactory;
-    this.patchSetId = patchSetId;
-    this.message = message;
-    this.gitManager = gitManager;
-    this.myIdent = myIdent;
-    this.patchSetInserterFactory = patchSetInserterFactory;
-  }
-
-  @Override
-  public ChangeDetail call() throws NoSuchChangeException, OrmException,
-      EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException,
-      MissingObjectException, IncorrectObjectTypeException, IOException,
-      InvalidChangeOperationException, NoSuchProjectException {
-
-    final Change.Id changeId = patchSetId.getParentKey();
-    final ChangeControl control = changeControlFactory.validateFor(changeId);
-    if (!control.canAddPatchSet()) {
-      throw new InvalidChangeOperationException(
-          "Not allowed to add new Patch Sets to: " + changeId.toString());
-    }
-
-    final Repository git;
-    try {
-      git = gitManager.openRepository(db.changes().get(changeId).getProject());
-    } catch (RepositoryNotFoundException e) {
-      throw new NoSuchChangeException(changeId, e);
-    }
-    try {
-      ChangeUtil.editCommitMessage(patchSetId, control.getRefControl(),
-          currentUser, message, db, commitMessageEditedSenderFactory, git,
-          myIdent, patchSetInserterFactory);
-      return changeDetailFactory.create(changeId).call();
-    } finally {
-      git.close();
-    }
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
deleted file mode 100644
index 0c7df3e..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/IncludedInDetailFactory.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (C) 2010 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.changedetail;
-
-import com.google.gerrit.common.data.IncludedInDetail;
-import com.google.gerrit.common.errors.InvalidRevisionException;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.IncludedInResolver;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtorm.server.OrmException;
-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.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-import java.io.IOException;
-
-/** Creates a {@link IncludedInDetail} of a {@link Change}. */
-class IncludedInDetailFactory extends Handler<IncludedInDetail> {
-
-  interface Factory {
-    IncludedInDetailFactory create(Change.Id id);
-  }
-
-  private final ReviewDb db;
-  private final ChangeControl.Factory changeControlFactory;
-  private final GitRepositoryManager repoManager;
-  private final Change.Id changeId;
-
-  private ChangeControl control;
-
-  @Inject
-  IncludedInDetailFactory(final ReviewDb db,
-      final ChangeControl.Factory changeControlFactory,
-      final GitRepositoryManager repoManager, @Assisted final Change.Id changeId) {
-    this.changeControlFactory = changeControlFactory;
-    this.repoManager = repoManager;
-    this.changeId = changeId;
-    this.db = db;
-  }
-
-  @Override
-  public IncludedInDetail call() throws OrmException, NoSuchChangeException,
-      IOException, InvalidRevisionException {
-    control = changeControlFactory.validateFor(changeId);
-
-    final PatchSet patch =
-        db.patchSets().get(control.getChange().currentPatchSetId());
-    final Repository repo =
-        repoManager.openRepository(control.getProject().getNameKey());
-    try {
-      final RevWalk rw = new RevWalk(repo);
-      try {
-        rw.setRetainBody(false);
-
-        final RevCommit rev;
-        try {
-          rev = rw.parseCommit(ObjectId.fromString(patch.getRevision().get()));
-        } catch (IncorrectObjectTypeException err) {
-          throw new InvalidRevisionException();
-        } catch (MissingObjectException err) {
-          throw new InvalidRevisionException();
-        }
-
-        return IncludedInResolver.resolve(repo, rw, rev);
-      } finally {
-        rw.release();
-      }
-    } finally {
-      repo.close();
-    }
-  }
-}
\ No newline at end of file
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
index 788c6f6..fce8637 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
@@ -34,7 +34,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.ChangesCollection;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.change.Revisions;
 import com.google.gerrit.server.extensions.webui.UiActions;
@@ -76,6 +76,7 @@
   private final ReviewDb db;
   private final PatchListCache patchListCache;
   private final ChangeControl.Factory changeControlFactory;
+  private final ChangesCollection changes;
   private final Revisions revisions;
 
   private Project.NameKey projectKey;
@@ -93,6 +94,7 @@
   PatchSetDetailFactory(final PatchSetInfoFactory psif, final ReviewDb db,
       final PatchListCache patchListCache,
       final ChangeControl.Factory changeControlFactory,
+      final ChangesCollection changes,
       final Revisions revisions,
       @Assisted("psIdBase") @Nullable final PatchSet.Id psIdBase,
       @Assisted("psIdNew") final PatchSet.Id psIdNew,
@@ -101,6 +103,7 @@
     this.db = db;
     this.patchListCache = patchListCache;
     this.changeControlFactory = changeControlFactory;
+    this.changes = changes;
     this.revisions = revisions;
 
     this.psIdBase = psIdBase;
@@ -179,7 +182,7 @@
     detail.setCommands(Lists.newArrayList(Iterables.transform(
         UiActions.sorted(UiActions.plugins(UiActions.from(
           revisions,
-          new RevisionResource(new ChangeResource(control), patchSet),
+          new RevisionResource(changes.parse(control), patchSet),
           Providers.of(user)))),
         new Function<UiAction.Description, UiCommandDetail>() {
           @Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
deleted file mode 100644
index d0101a6..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.changedetail;
-
-import com.google.gerrit.common.data.LabelType;
-import com.google.gerrit.common.data.PatchSetPublishDetail;
-import com.google.gerrit.common.data.PermissionRange;
-import com.google.gerrit.common.data.SubmitRecord;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountInfoCacheFactory;
-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.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail> {
-  interface Factory {
-    PatchSetPublishDetailFactory create(PatchSet.Id patchSetId);
-  }
-
-  private final PatchSetInfoFactory infoFactory;
-  private final ReviewDb db;
-  private final ChangeControl.Factory changeControlFactory;
-  private final AccountInfoCacheFactory aic;
-  private final IdentifiedUser user;
-
-  private final PatchSet.Id patchSetId;
-
-  private PatchSetInfo patchSetInfo;
-  private Change change;
-  private List<PatchLineComment> drafts;
-
-  @Inject
-  PatchSetPublishDetailFactory(final PatchSetInfoFactory infoFactory,
-      final ReviewDb db,
-      final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
-      final ChangeControl.Factory changeControlFactory,
-      final IdentifiedUser user, @Assisted final PatchSet.Id patchSetId) {
-    this.infoFactory = infoFactory;
-    this.db = db;
-    this.changeControlFactory = changeControlFactory;
-    this.aic = accountInfoCacheFactory.create();
-    this.user = user;
-
-    this.patchSetId = patchSetId;
-  }
-
-  @Override
-  public PatchSetPublishDetail call() throws OrmException,
-      PatchSetInfoNotAvailableException, NoSuchChangeException {
-    final Change.Id changeId = patchSetId.getParentKey();
-    final ChangeControl control = changeControlFactory.validateFor(changeId);
-    change = control.getChange();
-    PatchSet patchSet = db.patchSets().get(patchSetId);
-    patchSetInfo = infoFactory.get(change, patchSet);
-    drafts = db.patchComments().draftByPatchSetAuthor(patchSetId, user.getAccountId()).toList();
-
-    aic.want(change.getOwner());
-
-    PatchSetPublishDetail detail = new PatchSetPublishDetail();
-    detail.setPatchSetInfo(patchSetInfo);
-    detail.setChange(change);
-    detail.setDrafts(drafts);
-
-    if (change.getStatus().isOpen()
-        && patchSetId.equals(change.currentPatchSetId())) {
-      // TODO Push this selection of labels down into the Prolog interpreter.
-      // Ideally we discover the labels the user can apply here based on doing
-      // a findall() over the space of labels they can apply combined against
-      // the submit rule, thereby skipping any mutually exclusive cases. However
-      // those are not common, so it might just be reasonable to take this
-      // simple approach.
-
-      Map<String, PermissionRange> rangeByName =
-          new HashMap<String, PermissionRange>();
-      for (PermissionRange r : control.getLabelRanges()) {
-        if (r.isLabel()) {
-          rangeByName.put(r.getLabel(), r);
-        }
-      }
-
-      boolean couldSubmit = false;
-      List<SubmitRecord> submitRecords = control.canSubmit(db, patchSet);
-      for (SubmitRecord rec : submitRecords) {
-        if (rec.status == SubmitRecord.Status.OK) {
-          couldSubmit = true;
-        }
-
-        if (rec.labels != null) {
-          int ok = 0;
-
-          for (SubmitRecord.Label lbl : rec.labels) {
-            aic.want(lbl.appliedBy);
-
-            boolean canMakeOk = false;
-            PermissionRange range = rangeByName.get(lbl.label);
-            if (range != null) {
-              LabelType lt = control.getLabelTypes().byLabel(lbl.label);
-              if (lt != null && lt.getMax().getValue() == range.getMax()) {
-                canMakeOk = true;
-              }
-            }
-
-            switch (lbl.status) {
-              case OK:
-              case MAY:
-                ok++;
-                break;
-
-              case NEED:
-                if (canMakeOk) {
-                  ok++;
-                }
-                break;
-
-              case IMPOSSIBLE:
-              case REJECT:
-                break;
-            }
-          }
-
-          if (rec.status == SubmitRecord.Status.NOT_READY
-              && ok == rec.labels.size()) {
-            couldSubmit = true;
-          }
-        }
-      }
-
-      if (couldSubmit && control.getRefControl().canSubmit()) {
-        detail.setCanSubmit(true);
-      }
-    }
-
-    detail.setSubmitTypeRecord(control.getSubmitTypeRecord(db, patchSet));
-    detail.setAccounts(aic.create());
-
-    return detail;
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PublishAction.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PublishAction.java
deleted file mode 100644
index 38f8fc6..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PublishAction.java
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.changedetail;
-
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.data.ReviewResult;
-import com.google.gerrit.common.errors.NoSuchEntityException;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.changedetail.PublishDraft;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-
-import java.io.IOException;
-
-class PublishAction extends Handler<ChangeDetail> {
-  interface Factory {
-    PublishAction create(PatchSet.Id patchSetId);
-  }
-
-  private final PublishDraft.Factory publishFactory;
-  private final ChangeDetailFactory.Factory changeDetailFactory;
-
-  private final PatchSet.Id patchSetId;
-
-  @Inject
-  PublishAction(final PublishDraft.Factory publishFactory,
-      final ChangeDetailFactory.Factory changeDetailFactory,
-      @Assisted final PatchSet.Id patchSetId) {
-    this.publishFactory = publishFactory;
-    this.changeDetailFactory = changeDetailFactory;
-
-    this.patchSetId = patchSetId;
-  }
-
-  @Override
-  public ChangeDetail call() throws OrmException, NoSuchEntityException,
-      IllegalStateException, PatchSetInfoNotAvailableException,
-      NoSuchChangeException, RepositoryNotFoundException, IOException,
-      NoSuchProjectException {
-    final ReviewResult result = publishFactory.create(patchSetId).call();
-    if (result.getErrors().size() > 0) {
-      throw new IllegalStateException("Cannot publish patchset");
-    }
-    return changeDetailFactory.create(result.getChangeId()).call();
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
deleted file mode 100644
index 8558fe8..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChangeHandler.java
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.changedetail;
-
-import com.google.gerrit.common.data.ChangeDetail;
-import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.common.errors.NoSuchEntityException;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.changedetail.RebaseChange;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-
-import java.io.IOException;
-
-class RebaseChangeHandler extends Handler<ChangeDetail> {
-  interface Factory {
-    RebaseChangeHandler create(PatchSet.Id patchSetId);
-  }
-
-  private final RebaseChange rebaseChange;
-  private final IdentifiedUser currentUser;
-  private final ChangeDetailFactory.Factory changeDetailFactory;
-
-  private final PatchSet.Id patchSetId;
-
-  @Inject
-  RebaseChangeHandler(final RebaseChange rebaseChange,
-      final IdentifiedUser currentUser,
-      final ChangeDetailFactory.Factory changeDetailFactory,
-      @Assisted final PatchSet.Id patchSetId) {
-    this.rebaseChange = rebaseChange;
-    this.currentUser = currentUser;
-    this.changeDetailFactory = changeDetailFactory;
-
-    this.patchSetId = patchSetId;
-  }
-
-  @Override
-  public ChangeDetail call() throws NoSuchChangeException, OrmException,
-      EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException,
-      MissingObjectException, IncorrectObjectTypeException, IOException,
-      InvalidChangeOperationException, NoSuchProjectException {
-    rebaseChange.rebase(patchSetId, currentUser);
-    return changeDetailFactory.create(patchSetId.getParentKey()).call();
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/doc/QueryDocumentationFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/doc/QueryDocumentationFilter.java
new file mode 100644
index 0000000..002b520
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/doc/QueryDocumentationFilter.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd.rpc.doc;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.documentation.QueryDocumentationExecutor;
+import com.google.gerrit.server.documentation.QueryDocumentationExecutor.DocQueryException;
+import com.google.gerrit.server.documentation.QueryDocumentationExecutor.DocResult;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Singleton
+public class QueryDocumentationFilter implements Filter {
+  private final QueryDocumentationExecutor searcher;
+
+  @Inject
+  QueryDocumentationFilter(QueryDocumentationExecutor searcher) {
+    this.searcher = searcher;
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) {
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+      throws IOException, ServletException {
+    HttpServletRequest req = (HttpServletRequest) request;
+    if ("GET".equals(req.getMethod())
+        && !Strings.isNullOrEmpty(req.getParameter("q"))) {
+      HttpServletResponse rsp = (HttpServletResponse) response;
+      try {
+        List<DocResult> result = searcher.doQuery(request.getParameter("q"));
+        Multimap<String, String> config = LinkedHashMultimap.create();
+        RestApiServlet.replyJson(req, rsp, config, result);
+      } catch (DocQueryException e) {
+        rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+      }
+    } else {
+      chain.doFilter(request, response);
+    }
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
index 35a2b3d..64d5838 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
@@ -14,60 +14,36 @@
 
 package com.google.gerrit.httpd.rpc.patch;
 
-import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.PatchDetailService;
 import com.google.gerrit.common.data.PatchScript;
-import com.google.gerrit.common.data.ReviewResult;
 import com.google.gerrit.common.errors.NoSuchEntityException;
 import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
 import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.httpd.rpc.changedetail.ChangeDetailFactory;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
 import com.google.gerrit.server.patch.PatchScriptFactory;
-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.project.NoSuchProjectException;
 import com.google.gwtjsonrpc.common.AsyncCallback;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-
-import java.io.IOException;
-import java.util.Collections;
-
 class PatchDetailServiceImpl extends BaseServiceImplementation implements
     PatchDetailService {
-  private final DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory;
   private final PatchScriptFactory.Factory patchScriptFactoryFactory;
-  private final SaveDraft.Factory saveDraftFactory;
-  private final ChangeDetailFactory.Factory changeDetailFactory;
   private final ChangeControl.Factory changeControlFactory;
 
   @Inject
   PatchDetailServiceImpl(final Provider<ReviewDb> schema,
       final Provider<CurrentUser> currentUser,
-      final DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory,
       final PatchScriptFactory.Factory patchScriptFactoryFactory,
-      final SaveDraft.Factory saveDraftFactory,
-      final ChangeDetailFactory.Factory changeDetailFactory,
       final ChangeControl.Factory changeControlFactory) {
     super(schema, currentUser);
 
-    this.deleteDraftPatchSetFactory = deleteDraftPatchSetFactory;
     this.patchScriptFactoryFactory = patchScriptFactoryFactory;
-    this.saveDraftFactory = saveDraftFactory;
-    this.changeDetailFactory = changeDetailFactory;
     this.changeControlFactory = changeControlFactory;
   }
 
@@ -89,69 +65,4 @@
       }
     }.to(callback);
   }
-
-  public void saveDraft(final PatchLineComment comment,
-      final AsyncCallback<PatchLineComment> callback) {
-    saveDraftFactory.create(comment).to(callback);
-  }
-
-  public void deleteDraft(final PatchLineComment.Key commentKey,
-      final AsyncCallback<VoidResult> callback) {
-    run(callback, new Action<VoidResult>() {
-      public VoidResult run(ReviewDb db) throws OrmException, Failure {
-        Change.Id id = commentKey.getParentKey().getParentKey().getParentKey();
-        db.changes().beginTransaction(id);
-        try {
-          final PatchLineComment comment = db.patchComments().get(commentKey);
-          if (comment == null) {
-            throw new Failure(new NoSuchEntityException());
-          }
-          if (!getAccountId().equals(comment.getAuthor())) {
-            throw new Failure(new NoSuchEntityException());
-          }
-          if (comment.getStatus() != PatchLineComment.Status.DRAFT) {
-            throw new Failure(new IllegalStateException("Comment published"));
-          }
-          db.patchComments().delete(Collections.singleton(comment));
-          db.commit();
-          return VoidResult.INSTANCE;
-        } finally {
-          db.rollback();
-        }
-      }
-    });
-  }
-
-  public void deleteDraftPatchSet(final PatchSet.Id psid,
-      final AsyncCallback<ChangeDetail> callback) {
-    run(callback, new Action<ChangeDetail>() {
-      public ChangeDetail run(ReviewDb db) throws OrmException, Failure {
-        ReviewResult result = null;
-        try {
-          result = deleteDraftPatchSetFactory.create(psid).call();
-          if (result.getErrors().size() > 0) {
-            throw new Failure(new NoSuchEntityException());
-          }
-          if (result.getChangeId() == null) {
-            // the change was deleted because the draft patch set that was
-            // deleted was the only patch set in the change
-            return null;
-          }
-          return changeDetailFactory.create(result.getChangeId()).call();
-        } catch (NoSuchChangeException e) {
-          throw new Failure(new NoSuchChangeException(psid.getParentKey()));
-        } catch (NoSuchProjectException e) {
-          throw new Failure(e);
-        } catch (NoSuchEntityException e) {
-          throw new Failure(e);
-        } catch (PatchSetInfoNotAvailableException e) {
-          throw new Failure(e);
-        } catch (RepositoryNotFoundException e) {
-          throw new Failure(e);
-        } catch (IOException e) {
-          throw new Failure(e);
-        }
-      }
-    });
-  }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
index 4e69b14..83bfcdc 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.httpd.rpc.RpcServletModule;
 import com.google.gerrit.httpd.rpc.UiRpcModule;
-import com.google.gerrit.server.config.FactoryModule;
 
 public class PatchModule extends RpcServletModule {
   public PatchModule() {
@@ -25,12 +24,6 @@
 
   @Override
   protected void configureServlets() {
-    install(new FactoryModule() {
-      @Override
-      protected void configure() {
-        factory(SaveDraft.Factory.class);
-      }
-    });
     rpc(PatchDetailServiceImpl.class);
   }
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java
deleted file mode 100644
index a9b908d..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/SaveDraft.java
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd.rpc.patch;
-
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.util.TimeUtil;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.util.Collections;
-
-class SaveDraft extends Handler<PatchLineComment> {
-  interface Factory {
-    SaveDraft create(PatchLineComment comment);
-  }
-
-  private final ChangeControl.Factory changeControlFactory;
-  private final ReviewDb db;
-  private final IdentifiedUser currentUser;
-
-  private final PatchLineComment comment;
-
-  @Inject
-  SaveDraft(final ChangeControl.Factory changeControlFactory,
-      final ReviewDb db, final IdentifiedUser currentUser,
-      @Assisted final PatchLineComment comment) {
-    this.changeControlFactory = changeControlFactory;
-    this.db = db;
-    this.currentUser = currentUser;
-    this.comment = comment;
-  }
-
-  @Override
-  public PatchLineComment call() throws NoSuchChangeException, OrmException {
-    if (comment.getStatus() != PatchLineComment.Status.DRAFT) {
-      throw new IllegalStateException("Comment published");
-    }
-
-    final Patch.Key patchKey = comment.getKey().getParentKey();
-    final PatchSet.Id patchSetId = patchKey.getParentKey();
-    final Change.Id changeId = patchKey.getParentKey().getParentKey();
-
-    db.changes().beginTransaction(changeId);
-    try {
-      changeControlFactory.validateFor(changeId);
-      if (db.patchSets().get(patchSetId) == null) {
-        throw new NoSuchChangeException(changeId);
-      }
-
-      final Account.Id me = currentUser.getAccountId();
-      if (comment.getKey().get() == null) {
-        if (comment.getLine() < 0) {
-          throw new IllegalStateException("Comment line must be >= 0, not "
-              + comment.getLine());
-        }
-
-        if (comment.getParentUuid() != null) {
-          final PatchLineComment parent =
-              db.patchComments().get(
-                  new PatchLineComment.Key(patchKey, comment.getParentUuid()));
-          if (parent == null || parent.getSide() != comment.getSide()) {
-            throw new IllegalStateException("Parent comment must be on same side");
-          }
-        }
-        if (comment.getRange() != null
-            && comment.getLine() != comment.getRange().getEndLine()) {
-            throw new IllegalStateException(
-              "Range endLine must be on the same line as the comment");
-        }
-
-        final PatchLineComment nc =
-            new PatchLineComment(new PatchLineComment.Key(patchKey,
-                ChangeUtil.messageUUID(db)), comment.getLine(), me,
-                comment.getParentUuid(), TimeUtil.nowTs());
-        nc.setSide(comment.getSide());
-        nc.setMessage(comment.getMessage());
-        nc.setRange(comment.getRange());
-        db.patchComments().insert(Collections.singleton(nc));
-        db.commit();
-        return nc;
-
-      } else {
-        if (!me.equals(comment.getAuthor())) {
-          throw new NoSuchChangeException(changeId);
-        }
-        comment.updated(TimeUtil.nowTs());
-        db.patchComments().update(Collections.singleton(comment));
-        db.commit();
-        return comment;
-      }
-    } finally {
-      db.rollback();
-    }
-  }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
index ececdcc..d18c16a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
@@ -20,15 +20,18 @@
 import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.SetParent;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -40,9 +43,11 @@
 
 class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> {
   interface Factory {
-    ChangeProjectAccess create(@Assisted Project.NameKey projectName,
+    ChangeProjectAccess create(
+        @Assisted("projectName") Project.NameKey projectName,
         @Nullable @Assisted ObjectId base,
         @Assisted List<AccessSection> sectionList,
+        @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
         @Nullable @Assisted String message);
   }
 
@@ -52,18 +57,21 @@
   private final ProjectCache projectCache;
 
   @Inject
-  ChangeProjectAccess(final ProjectAccessFactory.Factory projectAccessFactory,
-      final ProjectControl.Factory projectControlFactory,
-      final ProjectCache projectCache, final GroupBackend groupBackend,
-      final MetaDataUpdate.User metaDataUpdateFactory,
+  ChangeProjectAccess(ProjectAccessFactory.Factory projectAccessFactory,
+      ProjectControl.Factory projectControlFactory,
+      ProjectCache projectCache, GroupBackend groupBackend,
+      MetaDataUpdate.User metaDataUpdateFactory,
+      AllProjectsNameProvider allProjects,
+      Provider<SetParent> setParent,
       ChangeHooks hooks, IdentifiedUser user,
-
-      @Assisted final Project.NameKey projectName,
-      @Nullable @Assisted final ObjectId base,
+      @Assisted("projectName") Project.NameKey projectName,
+      @Nullable @Assisted ObjectId base,
       @Assisted List<AccessSection> sectionList,
+      @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
       @Nullable @Assisted String message) {
     super(projectControlFactory, groupBackend, metaDataUpdateFactory,
-        projectName, base, sectionList, message, true);
+        allProjects, setParent, projectName, base, sectionList,
+        parentProjectName, message, true);
     this.projectAccessFactory = projectAccessFactory;
     this.projectCache = projectCache;
     this.hooks = hooks;
@@ -72,11 +80,12 @@
 
   @Override
   protected ProjectAccess updateProjectConfig(ProjectConfig config,
-      MetaDataUpdate md) throws IOException, NoSuchProjectException, ConfigInvalidException {
+      MetaDataUpdate md, boolean parentProjectUpdate) throws IOException,
+      NoSuchProjectException, ConfigInvalidException {
     RevCommit commit = config.commit(md);
 
     hooks.doRefUpdatedHook(
-      new Branch.NameKey(config.getProject().getNameKey(), GitRepositoryManager.REF_CONFIG),
+      new Branch.NameKey(config.getProject().getNameKey(), RefNames.REFS_CONFIG),
       base, commit.getId(), user.getAccount());
 
     projectCache.evict(config.getProject());
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 2c5681b..ee4439f 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
@@ -14,7 +14,11 @@
 
 package com.google.gerrit.httpd.rpc.project;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.common.data.GroupInfo;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.ProjectAccess;
@@ -23,10 +27,10 @@
 import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.NoSuchProjectException;
@@ -109,11 +113,10 @@
       md.close();
     }
 
-    final RefControl metaConfigControl = pc.controlForRef(GitRepositoryManager.REF_CONFIG);
+    final RefControl metaConfigControl = pc.controlForRef(RefNames.REFS_CONFIG);
     List<AccessSection> local = new ArrayList<AccessSection>();
     Set<String> ownerOf = new HashSet<String>();
-    Map<AccountGroup.UUID, Boolean> visibleGroups =
-        new HashMap<AccountGroup.UUID, Boolean>();
+    Map<AccountGroup.UUID, Boolean> visibleGroups = new HashMap<>();
 
     for (AccessSection section : config.getAccessSections()) {
       String name = section.getName();
@@ -203,11 +206,39 @@
     detail.setOwnerOf(ownerOf);
     detail.setCanUpload(pc.isOwner()
         || (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
+    detail.setCanChangeParent(pc.getCurrentUser().getCapabilities()
+        .canAdministrateServer());
     detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible());
+    detail.setGroupInfo(buildGroupInfo(local));
     detail.setLabelTypes(pc.getLabelTypes());
     return detail;
   }
 
+  private Map<AccountGroup.UUID, GroupInfo> buildGroupInfo(List<AccessSection> local) {
+    Map<AccountGroup.UUID, GroupInfo> infos = new HashMap<>();
+    for (AccessSection section : local) {
+      for (Permission permission : section.getPermissions()) {
+        for (PermissionRule rule : permission.getRules()) {
+          if (rule.getGroup() != null) {
+            AccountGroup.UUID uuid = rule.getGroup().getUUID();
+            if (uuid != null && !infos.containsKey(uuid)) {
+              GroupDescription.Basic group = groupBackend.get(uuid);
+              infos.put(uuid, group != null ? new GroupInfo(group) : null);
+            }
+          }
+        }
+      }
+    }
+    return Maps.filterEntries(
+      infos,
+      new Predicate<Map.Entry<AccountGroup.UUID, GroupInfo>>() {
+        @Override
+        public boolean apply(Map.Entry<AccountGroup.UUID, GroupInfo> in) {
+          return in.getValue() != null;
+        }
+      });
+  }
+
   private ProjectControl open() throws NoSuchProjectException {
     return projectControlFactory.validateFor( //
         projectName, //
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
index 0a50a38..b2b8bb3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -16,22 +16,30 @@
 
 import static com.google.gerrit.common.ProjectAccessUtil.mergeSections;
 
+import com.google.common.base.Objects;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.common.errors.UpdateParentFailedException;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupBackends;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.project.SetParent;
 import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -47,27 +55,32 @@
   private final ProjectControl.Factory projectControlFactory;
   protected final GroupBackend groupBackend;
   private final MetaDataUpdate.User metaDataUpdateFactory;
+  private final AllProjectsNameProvider allProjects;
+  private final Provider<SetParent> setParent;
 
   protected final Project.NameKey projectName;
   protected final ObjectId base;
   private List<AccessSection> sectionList;
+  private final Project.NameKey parentProjectName;
   protected String message;
   private boolean checkIfOwner;
 
-  protected ProjectAccessHandler(
-      final ProjectControl.Factory projectControlFactory,
-      final GroupBackend groupBackend,
-      final MetaDataUpdate.User metaDataUpdateFactory,
-      final Project.NameKey projectName, final ObjectId base,
-      final List<AccessSection> sectionList, final String message,
-      final boolean checkIfOwner) {
+  protected ProjectAccessHandler(ProjectControl.Factory projectControlFactory,
+      GroupBackend groupBackend, MetaDataUpdate.User metaDataUpdateFactory,
+      AllProjectsNameProvider allProjects, Provider<SetParent> setParent,
+      Project.NameKey projectName, ObjectId base,
+      List<AccessSection> sectionList, Project.NameKey parentProjectName,
+      String message, boolean checkIfOwner) {
     this.projectControlFactory = projectControlFactory;
     this.groupBackend = groupBackend;
     this.metaDataUpdateFactory = metaDataUpdateFactory;
+    this.allProjects = allProjects;
+    this.setParent = setParent;
 
     this.projectName = projectName;
     this.base = base;
     this.sectionList = sectionList;
+    this.parentProjectName = parentProjectName;
     this.message = message;
     this.checkIfOwner = checkIfOwner;
   }
@@ -75,7 +88,7 @@
   @Override
   public final T call() throws NoSuchProjectException, IOException,
       ConfigInvalidException, InvalidNameException, NoSuchGroupException,
-      OrmException {
+      OrmException, UpdateParentFailedException {
     final ProjectControl projectControl =
         projectControlFactory.controlFor(projectName);
 
@@ -120,6 +133,27 @@
         }
       }
 
+      boolean parentProjectUpdate = false;
+      if (!config.getProject().getNameKey().equals(allProjects.get()) &&
+          !config.getProject().getParent(allProjects.get()).equals(parentProjectName)) {
+        parentProjectUpdate = true;
+        try {
+          setParent.get().validateParentUpdate(projectControl,
+              Objects.firstNonNull(parentProjectName, allProjects.get()).get(),
+              checkIfOwner);
+        } catch (AuthException e) {
+          throw new UpdateParentFailedException(
+              "You are not allowed to change the parent project since you are "
+              + "not an administrator. You may save the modifications for review "
+              + "so that an administrator can approve them.", e);
+        } catch (ResourceConflictException e) {
+          throw new UpdateParentFailedException(e.getMessage(), e);
+        } catch (UnprocessableEntityException e) {
+          throw new UpdateParentFailedException(e.getMessage(), e);
+        }
+        config.getProject().setParentName(parentProjectName);
+      }
+
       if (message != null && !message.isEmpty()) {
         if (!message.endsWith("\n")) {
           message += "\n";
@@ -129,15 +163,15 @@
         md.setMessage("Modify access rules\n");
       }
 
-      return updateProjectConfig(config, md);
+      return updateProjectConfig(config, md, parentProjectUpdate);
     } finally {
       md.close();
     }
   }
 
   protected abstract T updateProjectConfig(ProjectConfig config,
-      MetaDataUpdate md) throws IOException, NoSuchProjectException,
-      ConfigInvalidException, OrmException;
+      MetaDataUpdate md, boolean parentProjectUpdate) throws IOException,
+      NoSuchProjectException, ConfigInvalidException, OrmException;
 
   private void replace(ProjectConfig config, Set<String> toDelete,
       AccessSection section) throws NoSuchGroupException {
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 66ba75c..c378701 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
@@ -56,14 +56,16 @@
   @Override
   public void changeProjectAccess(Project.NameKey projectName,
       String baseRevision, String msg, List<AccessSection> sections,
-      AsyncCallback<ProjectAccess> cb) {
-    changeProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb);
+      Project.NameKey parentProjectName, AsyncCallback<ProjectAccess> cb) {
+    changeProjectAccessFactory.create(projectName, getBase(baseRevision),
+        sections, parentProjectName, msg).to(cb);
   }
 
   @Override
   public void reviewProjectAccess(Project.NameKey projectName,
       String baseRevision, String msg, List<AccessSection> sections,
-      AsyncCallback<Change.Id> cb) {
-    reviewProjectAccessFactory.create(projectName, getBase(baseRevision), sections, msg).to(cb);
+      Project.NameKey parentProjectName, AsyncCallback<Change.Id> cb) {
+    reviewProjectAccessFactory.create(projectName, getBase(baseRevision),
+        sections, parentProjectName, msg).to(cb);
   }
 }
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 27a02d9..6537a6b 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
@@ -17,28 +17,35 @@
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 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.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.ChangesCollection;
+import com.google.gerrit.server.change.MergeabilityChecker;
 import com.google.gerrit.server.change.PostReviewers;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.mail.CreateChangeSender;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.SetParent;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -60,9 +67,11 @@
       LoggerFactory.getLogger(ReviewProjectAccess.class);
 
   interface Factory {
-    ReviewProjectAccess create(@Assisted Project.NameKey projectName,
+    ReviewProjectAccess create(
+        @Assisted("projectName") Project.NameKey projectName,
         @Nullable @Assisted ObjectId base,
         @Assisted List<AccessSection> sectionList,
+        @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
         @Nullable @Assisted String message);
   }
 
@@ -70,10 +79,11 @@
   private final IdentifiedUser user;
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final Provider<PostReviewers> reviewersProvider;
-  private final ChangeControl.GenericFactory changeFactory;
-  private final ChangeIndexer indexer;
+  private final MergeabilityChecker mergeabilityChecker;
   private final ChangeHooks hooks;
   private final CreateChangeSender.Factory createChangeSenderFactory;
+  private final ProjectCache projectCache;
+  private final ChangesCollection changes;
 
   @Inject
   ReviewProjectAccess(final ProjectControl.Factory projectControlFactory,
@@ -81,29 +91,36 @@
       MetaDataUpdate.User metaDataUpdateFactory, ReviewDb db,
       IdentifiedUser user, PatchSetInfoFactory patchSetInfoFactory,
       Provider<PostReviewers> reviewersProvider,
-      ChangeControl.GenericFactory changeFactory,
-      ChangeIndexer indexer, ChangeHooks hooks,
+      MergeabilityChecker mergeabilityChecker, ChangeHooks hooks,
       CreateChangeSender.Factory createChangeSenderFactory,
+      ProjectCache projectCache,
+      AllProjectsNameProvider allProjects,
+      ChangesCollection changes,
+      Provider<SetParent> setParent,
 
-      @Assisted Project.NameKey projectName,
+      @Assisted("projectName") Project.NameKey projectName,
       @Nullable @Assisted ObjectId base,
       @Assisted List<AccessSection> sectionList,
+      @Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
       @Nullable @Assisted String message) {
     super(projectControlFactory, groupBackend, metaDataUpdateFactory,
-        projectName, base, sectionList, message, false);
+        allProjects, setParent, projectName, base, sectionList,
+        parentProjectName, message, false);
     this.db = db;
     this.user = user;
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.reviewersProvider = reviewersProvider;
-    this.changeFactory = changeFactory;
-    this.indexer = indexer;
+    this.mergeabilityChecker = mergeabilityChecker;
     this.hooks = hooks;
     this.createChangeSenderFactory = createChangeSenderFactory;
+    this.projectCache = projectCache;
+    this.changes = changes;
   }
 
   @Override
-  protected Change.Id updateProjectConfig(ProjectConfig config, MetaDataUpdate md)
-      throws IOException, OrmException {
+  protected Change.Id updateProjectConfig(ProjectConfig config,
+      MetaDataUpdate md, boolean parentProjectUpdate) throws IOException,
+      OrmException {
     Change.Id changeId = new Change.Id(db.nextChangeId());
     PatchSet ps =
         new PatchSet(new PatchSet.Id(changeId, Change.INITIAL_PATCH_SET_ID));
@@ -118,7 +135,7 @@
         user.getAccountId(),
         new Branch.NameKey(
             config.getProject().getNameKey(),
-            GitRepositoryManager.REF_CONFIG),
+            RefNames.REFS_CONFIG),
         TimeUtil.nowTs());
 
     ps.setCreatedOn(change.getCreatedOn());
@@ -138,7 +155,7 @@
     } finally {
       db.rollback();
     }
-    indexer.index(change);
+    mergeabilityChecker.newCheck().addChange(change).reindex().run();
     hooks.doPatchsetCreatedHook(change, ps, db);
     try {
       CreateChangeSender cm =
@@ -149,7 +166,16 @@
     } catch (Exception err) {
       log.error("Cannot send email for new change " + change.getId(), err);
     }
-    addProjectOwnersAsReviewers(change);
+    ChangeResource rsrc;
+    try {
+      rsrc = changes.parse(changeId);
+    } catch (ResourceNotFoundException e) {
+      throw new IOException(e);
+    }
+    addProjectOwnersAsReviewers(rsrc);
+    if (parentProjectUpdate) {
+      addAdministratorsAsReviewers(rsrc);
+    }
     return changeId;
   }
 
@@ -167,13 +193,11 @@
     db.patchSetAncestors().insert(toInsert);
   }
 
-  private void addProjectOwnersAsReviewers(final Change change) {
+  private void addProjectOwnersAsReviewers(ChangeResource rsrc) {
     final String projectOwners =
-        groupBackend.get(AccountGroup.PROJECT_OWNERS).getName();
+        groupBackend.get(SystemGroupBackend.PROJECT_OWNERS).getName();
     try {
-      ChangeResource rsrc =
-          new ChangeResource(changeFactory.controlFor(change, user));
-      PostReviewers.Input input = new PostReviewers.Input();
+      AddReviewerInput input = new AddReviewerInput();
       input.reviewer = projectOwners;
       reviewersProvider.get().apply(rsrc, input);
     } catch (Exception e) {
@@ -181,4 +205,20 @@
       // can't be added as reviewer
     }
   }
+
+  private void addAdministratorsAsReviewers(ChangeResource rsrc) {
+    List<PermissionRule> adminRules =
+        projectCache.getAllProjects().getConfig()
+            .getAccessSection(AccessSection.GLOBAL_CAPABILITIES)
+            .getPermission(GlobalCapability.ADMINISTRATE_SERVER).getRules();
+    for (PermissionRule r : adminRules) {
+      try {
+        AddReviewerInput input = new AddReviewerInput();
+        input.reviewer = r.getGroup().getUUID().get();
+        reviewersProvider.get().apply(rsrc, input);
+      } catch (Exception e) {
+        // ignore
+      }
+    }
+  }
 }
diff --git a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/auth/container/LoginRedirect.html b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/auth/container/LoginRedirect.html
index 433be20..d88af9a 100644
--- a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/auth/container/LoginRedirect.html
+++ b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/auth/container/LoginRedirect.html
@@ -2,7 +2,7 @@
 <html>
   <head>
     <title>Gerrit Code Review</title>
-    <script type="text/javascript" language="javascript">
+    <script type="text/javascript">
       var href = window.location.href;
       var p = href.indexOf('#');
       var token;
diff --git a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/LegacyGerrit.html b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/LegacyGerrit.html
index 6639a5b..6046004 100644
--- a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/LegacyGerrit.html
+++ b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/LegacyGerrit.html
@@ -2,7 +2,7 @@
 <html>
   <head>
     <title>Gerrit Code Review</title>
-    <script type="text/javascript" language="javascript">
+    <script type="text/javascript">
       var href = window.location.href;
       var p = href.indexOf('#');
       var token = '';
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java
index c834b94..26cdd8a 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/GitWebConfigTest.java
@@ -14,19 +14,24 @@
 
 package com.google.gerrit.httpd;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
-public class GitWebConfigTest extends TestCase {
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class GitWebConfigTest {
 
   private static final String VALID_CHARACTERS = "*()";
   private static final String SOME_INVALID_CHARACTERS = "09AZaz$-_.+!',";
 
+  @Test
   public void testValidPathSeparator() {
     for(char c : VALID_CHARACTERS.toCharArray()) {
       assertTrue("valid character rejected: " + c, GitWebConfig.isValidPathSeparator(c));
     }
   }
 
+  @Test
   public void testInalidPathSeparator() {
     for(char c : SOME_INVALID_CHARACTERS.toCharArray()) {
       assertFalse("invalid character accepted: " + c, GitWebConfig.isValidPathSeparator(c));
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java
index ffbc7f3..8533a9c 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/restapi/ParameterParserTest.java
@@ -21,9 +21,11 @@
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
 
-import junit.framework.TestCase;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
 
-public class ParameterParserTest extends TestCase {
+public class ParameterParserTest {
+  @Test
   public void testConvertFormToJson() throws BadRequestException {
     JsonObject obj = ParameterParser.formToJson(
         ImmutableMap.of(
diff --git a/gerrit-launcher/.gitignore b/gerrit-launcher/.gitignore
deleted file mode 100644
index 980a6b1..0000000
--- a/gerrit-launcher/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-launcher.iml
\ No newline at end of file
diff --git a/gerrit-launcher/.settings/org.eclipse.core.resources.prefs b/gerrit-launcher/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index e9441bb..0000000
--- a/gerrit-launcher/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-launcher/.settings/org.eclipse.core.runtime.prefs b/gerrit-launcher/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-launcher/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-launcher/.settings/org.eclipse.jdt.core.prefs b/gerrit-launcher/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 470942d..0000000
--- a/gerrit-launcher/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-launcher/.settings/org.eclipse.jdt.ui.prefs b/gerrit-launcher/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-launcher/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-launcher/BUCK b/gerrit-launcher/BUCK
index e5ff3d0..6281a1c 100644
--- a/gerrit-launcher/BUCK
+++ b/gerrit-launcher/BUCK
@@ -1,6 +1,8 @@
+# NOTE: GerritLauncher must be a single, self-contained class. Do not add any
+# additional srcs or deps to this rule.
 java_library(
   name = 'launcher',
-  srcs = glob(['src/main/java/**/*.java']),
+  srcs = ['src/main/java/com/google/gerrit/launcher/GerritLauncher.java'],
   visibility = [
     '//gerrit-acceptance-tests/...',
     '//gerrit-main:main_lib',
diff --git a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
index 3593386..33a3534 100644
--- a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -22,11 +22,11 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.net.JarURLConnection;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.security.CodeSource;
@@ -153,10 +153,7 @@
     final Method main;
     try {
       main = clazz.getMethod("main", argv.getClass());
-    } catch (SecurityException e) {
-      System.err.println("fatal: unknown command " + name);
-      return 1;
-    } catch (NoSuchMethodException e) {
+    } catch (SecurityException | NoSuchMethodException e) {
       System.err.println("fatal: unknown command " + name);
       return 1;
     }
@@ -189,11 +186,8 @@
     try {
       path = getDistributionArchive();
     } catch (FileNotFoundException e) {
-      if (NOT_ARCHIVED == e.getMessage()) {
-        // Assume the CLASSPATH was made complete by the calling process,
-        // as we are likely being run from within a developer's IDE.
-        //
-        return GerritLauncher.class.getClassLoader();
+      if (NOT_ARCHIVED.equals(e.getMessage())) {
+        return useDevClasspath();
       }
       throw e;
     }
@@ -288,10 +282,10 @@
     // match the name it was in the archive.
     //
     String name = ze.getName();
-    if (0 <= name.lastIndexOf('/')) {
+    if (name.contains("/")) {
       name = name.substring(name.lastIndexOf('/') + 1);
     }
-    if (0 <= name.lastIndexOf('.')) {
+    if (name.contains(".")) {
       name = name.substring(0, name.lastIndexOf('.'));
     }
     if (name.isEmpty()) {
@@ -301,6 +295,7 @@
   }
 
   private volatile static File myArchive;
+  private volatile static File myHome;
 
   /**
    * Locate the JAR/WAR file we were launched from.
@@ -328,11 +323,8 @@
     // ZipFile may have the path of our JAR hiding within itself.
     //
     try {
-      Field nameField = ZipFile.class.getDeclaredField("name");
-      nameField.setAccessible(true);
-
       JarFile jar = ((JarURLConnection) myClazz.openConnection()).getJarFile();
-      File path = new File((String) nameField.get(jar));
+      File path = new File(jar.getName());
       if (path.isFile()) {
         return path;
       }
@@ -458,42 +450,26 @@
     return tmp;
   }
 
+  /**
+   * Provide path to a working directory
+   *
+   * @return local path of the working directory or null if cannot be determined
+   */
+  public static File getHomeDirectory() {
+    if (myHome == null) {
+      myHome = locateHomeDirectory();
+    }
+    return myHome;
+  }
+
+
   private static File tmproot() {
     File tmp;
     String gerritTemp = System.getenv("GERRIT_TMP");
     if (gerritTemp != null && gerritTemp.length() > 0) {
       tmp = new File(gerritTemp);
     } else {
-      // Try to find the user's home directory. If we can't find it
-      // return null so the JVM's default temporary directory is used
-      // instead. This is probably /tmp or /var/tmp.
-      //
-      String userHome = System.getProperty("user.home");
-      if (userHome == null || "".equals(userHome)) {
-        userHome = System.getenv("HOME");
-        if (userHome == null || "".equals(userHome)) {
-          System.err.println("warning: cannot determine home directory");
-          System.err.println("warning: using system temporary directory instead");
-          return null;
-        }
-      }
-
-      // Ensure the home directory exists. If it doesn't, try to make it.
-      //
-      final File home = new File(userHome);
-      if (!home.exists()) {
-        if (home.mkdirs()) {
-          System.err.println("warning: created " + home.getAbsolutePath());
-        } else {
-          System.err.println("warning: " + home.getAbsolutePath() + " not found");
-          System.err.println("warning: using system temporary directory instead");
-          return null;
-        }
-      }
-
-      // Use $HOME/.gerritcodereview/tmp for our temporary file area.
-      //
-      tmp = new File(new File(home, ".gerritcodereview"), "tmp");
+      tmp = new File(getHomeDirectory(), "tmp");
     }
     if (!tmp.exists() && !tmp.mkdirs()) {
       System.err.println("warning: cannot create " + tmp.getAbsolutePath());
@@ -526,6 +502,116 @@
     }
   }
 
+  private static File locateHomeDirectory() {
+    // Try to find the user's home directory. If we can't find it
+    // return null so the JVM's default temporary directory is used
+    // instead. This is probably /tmp or /var/tmp.
+    //
+    String userHome = System.getProperty("user.home");
+    if (userHome == null || "".equals(userHome)) {
+      userHome = System.getenv("HOME");
+      if (userHome == null || "".equals(userHome)) {
+        System.err.println("warning: cannot determine home directory");
+        System.err.println("warning: using system temporary directory instead");
+        return null;
+      }
+    }
+
+    // Ensure the home directory exists. If it doesn't, try to make it.
+    //
+    final File home = new File(userHome);
+    if (!home.exists()) {
+      if (home.mkdirs()) {
+        System.err.println("warning: created " + home.getAbsolutePath());
+      } else {
+        System.err.println("warning: " + home.getAbsolutePath() + " not found");
+        System.err.println("warning: using system temporary directory instead");
+        return null;
+      }
+    }
+
+    // Use $HOME/.gerritcodereview/tmp for our temporary file area.
+    //
+    final File gerrithome = new File(home, ".gerritcodereview");
+    if (!gerrithome.exists() && !gerrithome.mkdirs()) {
+      System.err.println("warning: cannot create " + gerrithome.getAbsolutePath());
+      System.err.println("warning: using system temporary directory instead");
+      return null;
+    }
+    try {
+      return gerrithome.getCanonicalFile();
+    } catch (IOException e) {
+      return gerrithome;
+    }
+  }
+
+  /**
+   * Locate the path of the {@code buck-out} directory in a source tree.
+   *
+   * @throws FileNotFoundException if the directory cannot be found.
+   */
+  public static File getDeveloperBuckOut() throws FileNotFoundException {
+    // Find ourselves in the CLASSPATH, we should be a loose class file.
+    Class<GerritLauncher> self = GerritLauncher.class;
+    URL u = self.getResource(self.getSimpleName() + ".class");
+    if (u == null) {
+      throw new FileNotFoundException("Cannot find class " + self.getName());
+    } else if (!"file".equals(u.getProtocol())) {
+      throw new FileNotFoundException("Cannot find extract path from " + u);
+    }
+
+    // Pop up to the top level classes folder that contains us.
+    File dir = new File(u.getPath());
+    String myName = self.getName();
+    for (;;) {
+      int dot = myName.lastIndexOf('.');
+      if (dot < 0) {
+        dir = dir.getParentFile();
+        break;
+      }
+      myName = myName.substring(0, dot);
+      dir = dir.getParentFile();
+    }
+
+    dir = popdir(u, dir, "classes");
+    dir = popdir(u, dir, "eclipse");
+    if ("buck-out".equals(dir.getName())) {
+      return dir;
+    }
+    throw new FileNotFoundException("Cannot find buck-out from " + u);
+  }
+
+  private static File popdir(URL u, File dir, String name)
+      throws FileNotFoundException {
+    if (dir.getName().equals(name)) {
+      return dir.getParentFile();
+    }
+    throw new FileNotFoundException("Cannot find buck-out from " + u);
+  }
+
+  private static ClassLoader useDevClasspath()
+      throws MalformedURLException, FileNotFoundException {
+    File out = getDeveloperBuckOut();
+    List<URL> dirs = new ArrayList<URL>();
+    dirs.add(new File(new File(out, "eclipse"), "classes").toURI().toURL());
+    ClassLoader cl = GerritLauncher.class.getClassLoader();
+    for (URL u : ((URLClassLoader) cl).getURLs()) {
+      if (includeJar(u)) {
+        dirs.add(u);
+      }
+    }
+    return new URLClassLoader(
+        dirs.toArray(new URL[dirs.size()]),
+        ClassLoader.getSystemClassLoader().getParent());
+  }
+
+  private static boolean includeJar(URL u) {
+    String path = u.getPath();
+    return path.endsWith(".jar")
+        && !path.endsWith("-src.jar")
+        && !path.contains("/buck-out/gen/lib/gwt/");
+  }
+
   private GerritLauncher() {
   }
 }
diff --git a/gerrit-lucene/BUCK b/gerrit-lucene/BUCK
index 5a907eb..2b45d2b 100644
--- a/gerrit-lucene/BUCK
+++ b/gerrit-lucene/BUCK
@@ -22,6 +22,7 @@
   deps = [
     ':query_builder',
     '//gerrit-antlr:query_exception',
+    '//gerrit-common:annotations',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
     '//gerrit-reviewdb:server',
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index a434ac9..294aa35 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -30,7 +30,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
@@ -51,11 +51,14 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.SortKeyPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 
+import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.analysis.util.CharArraySet;
 import org.apache.lucene.document.Document;
@@ -120,17 +123,33 @@
         ImmutableMap.builder();
     @SuppressWarnings("deprecation")
     Version lucene43 = Version.LUCENE_43;
+    @SuppressWarnings("deprecation")
+    Version lucene44 = Version.LUCENE_44;
     for (Map.Entry<Integer, Schema<ChangeData>> e
         : ChangeSchemas.ALL.entrySet()) {
       if (e.getKey() <= 3) {
         versions.put(e.getValue(), lucene43);
+      } else if (e.getKey() <= 5) {
+        versions.put(e.getValue(), lucene44);
       } else {
-        versions.put(e.getValue(), Version.LUCENE_44);
+        versions.put(e.getValue(), Version.LUCENE_46);
       }
     }
     LUCENE_VERSIONS = versions.build();
   }
 
+  public static void setReady(SitePaths sitePaths, int version, boolean ready)
+      throws IOException {
+    try {
+      FileBasedConfig cfg =
+          LuceneVersionManager.loadGerritIndexConfig(sitePaths);
+      LuceneVersionManager.setReady(cfg, version, ready);
+      cfg.save();
+    } catch (ConfigInvalidException e) {
+      throw new IOException(e);
+    }
+  }
+
   static interface Factory {
     LuceneChangeIndex create(Schema<ChangeData> schema, String base);
   }
@@ -171,8 +190,11 @@
   private final SitePaths sitePaths;
   private final FillArgs fillArgs;
   private final ListeningExecutorService executor;
+  private final Provider<ReviewDb> db;
+  private final ChangeData.Factory changeDataFactory;
   private final File dir;
   private final Schema<ChangeData> schema;
+  private final QueryBuilder queryBuilder;
   private final SubIndex openIndex;
   private final SubIndex closedIndex;
 
@@ -181,12 +203,16 @@
       @GerritServerConfig Config cfg,
       SitePaths sitePaths,
       @IndexExecutor ListeningExecutorService executor,
+      Provider<ReviewDb> db,
+      ChangeData.Factory changeDataFactory,
       FillArgs fillArgs,
       @Assisted Schema<ChangeData> schema,
       @Assisted @Nullable String base) throws IOException {
     this.sitePaths = sitePaths;
     this.fillArgs = fillArgs;
     this.executor = executor;
+    this.db = db;
+    this.changeDataFactory = changeDataFactory;
     this.schema = schema;
 
     if (base == null) {
@@ -198,10 +224,15 @@
         LUCENE_VERSIONS.get(schema),
         "unknown Lucene version for index schema: %s", schema);
 
+    Analyzer analyzer =
+        new StandardAnalyzer(luceneVersion, CharArraySet.EMPTY_SET);
+    queryBuilder = new QueryBuilder(schema, analyzer);
+
     GerritIndexWriterConfig openConfig =
         new GerritIndexWriterConfig(luceneVersion, cfg, "changes_open");
     GerritIndexWriterConfig closedConfig =
         new GerritIndexWriterConfig(luceneVersion, cfg, "changes_closed");
+
     if (cfg.getBoolean("index", "lucene", "testInmemory", false)) {
       openIndex = new SubIndex(new RAMDirectory(), "ramOpen", openConfig);
       closedIndex = new SubIndex(new RAMDirectory(), "ramClosed", closedConfig);
@@ -240,7 +271,7 @@
     Term id = QueryBuilder.idTerm(cd);
     Document doc = toDocument(cd);
     try {
-      if (cd.getChange().getStatus().isOpen()) {
+      if (cd.change().getStatus().isOpen()) {
         Futures.allAsList(
             closedIndex.delete(id),
             openIndex.insert(doc)).get();
@@ -249,9 +280,7 @@
             openIndex.delete(id),
             closedIndex.insert(doc)).get();
       }
-    } catch (ExecutionException e) {
-      throw new IOException(e);
-    } catch (InterruptedException e) {
+    } catch (OrmException | ExecutionException | InterruptedException e) {
       throw new IOException(e);
     }
   }
@@ -262,7 +291,7 @@
     Term id = QueryBuilder.idTerm(cd);
     Document doc = toDocument(cd);
     try {
-      if (cd.getChange().getStatus().isOpen()) {
+      if (cd.change().getStatus().isOpen()) {
         Futures.allAsList(
             closedIndex.delete(id),
             openIndex.replace(id, doc)).get();
@@ -271,9 +300,7 @@
             openIndex.delete(id),
             closedIndex.replace(id, doc)).get();
       }
-    } catch (ExecutionException e) {
-      throw new IOException(e);
-    } catch (InterruptedException e) {
+    } catch (OrmException | ExecutionException | InterruptedException e) {
       throw new IOException(e);
     }
   }
@@ -286,9 +313,7 @@
       Futures.allAsList(
           openIndex.delete(id),
           closedIndex.delete(id)).get();
-    } catch (ExecutionException e) {
-      throw new IOException(e);
-    } catch (InterruptedException e) {
+    } catch (ExecutionException | InterruptedException e) {
       throw new IOException(e);
     }
   }
@@ -300,8 +325,8 @@
   }
 
   @Override
-  public ChangeDataSource getSource(Predicate<ChangeData> p, int limit)
-      throws QueryParseException {
+  public ChangeDataSource getSource(Predicate<ChangeData> p, int start,
+      int limit) throws QueryParseException {
     Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p);
     List<SubIndex> indexes = Lists.newArrayListWithCapacity(2);
     if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
@@ -310,36 +335,50 @@
     if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
       indexes.add(closedIndex);
     }
-    return new QuerySource(indexes, QueryBuilder.toQuery(schema, p), limit,
-        ChangeQueryBuilder.hasNonTrivialSortKeyAfter(schema, p));
+    return new QuerySource(indexes, queryBuilder.toQuery(p), start, limit,
+        getSort(schema, p));
   }
 
   @Override
   public void markReady(boolean ready) throws IOException {
-    try {
-      FileBasedConfig cfg = LuceneVersionManager.loadGerritIndexConfig(sitePaths);
-      LuceneVersionManager.setReady(cfg, schema.getVersion(), ready);
-      cfg.save();
-    } catch (ConfigInvalidException e) {
-      throw new IOException(e);
+    setReady(sitePaths, schema.getVersion(), ready);
+  }
+
+  private static final ImmutableSet<String> FIELDS =
+      ImmutableSet.of(ID_FIELD, CHANGE_FIELD, APPROVAL_FIELD);
+
+  @SuppressWarnings("deprecation")
+  private static Sort getSort(Schema<ChangeData> schema,
+      Predicate<ChangeData> p) {
+    // Standard order is descending by sort key, unless reversed due to a
+    // sortkey_before predicate.
+    if (SortKeyPredicate.hasSortKeyField(schema)) {
+      boolean reverse = ChangeQueryBuilder.hasNonTrivialSortKeyAfter(schema, p);
+      return new Sort(new SortField(
+          ChangeField.SORTKEY.getName(), SortField.Type.LONG, !reverse));
+    } else {
+      return new Sort(
+          new SortField(
+            ChangeField.UPDATED.getName(), SortField.Type.LONG, true),
+          new SortField(
+            ChangeField.LEGACY_ID.getName(), SortField.Type.INT, true));
     }
   }
 
-  private static class QuerySource implements ChangeDataSource {
-    private static final ImmutableSet<String> FIELDS =
-        ImmutableSet.of(ID_FIELD, CHANGE_FIELD, APPROVAL_FIELD);
-
+  private class QuerySource implements ChangeDataSource {
     private final List<SubIndex> indexes;
     private final Query query;
+    private final int start;
     private final int limit;
-    private final boolean reverse;
+    private final Sort sort;
 
-    private QuerySource(List<SubIndex> indexes, Query query, int limit,
-        boolean reverse) {
+    private QuerySource(List<SubIndex> indexes, Query query, int start,
+        int limit, Sort sort) {
       this.indexes = indexes;
       this.query = query;
+      this.start = start;
       this.limit = limit;
-      this.reverse = reverse;
+      this.sort = sort;
     }
 
     @Override
@@ -360,24 +399,19 @@
     @Override
     public ResultSet<ChangeData> read() throws OrmException {
       IndexSearcher[] searchers = new IndexSearcher[indexes.size()];
-      Sort sort = new Sort(
-          new SortField(
-              ChangeField.SORTKEY.getName(),
-              SortField.Type.LONG,
-              // Standard order is descending by sort key, unless reversed due
-              // to a sortkey_before predicate.
-              !reverse));
       try {
+        int realLimit = start + limit;
         TopDocs[] hits = new TopDocs[indexes.size()];
         for (int i = 0; i < indexes.size(); i++) {
           searchers[i] = indexes.get(i).acquire();
-          hits[i] = searchers[i].search(query, limit, sort);
+          hits[i] = searchers[i].search(query, realLimit, sort);
         }
-        TopDocs docs = TopDocs.merge(sort, limit, hits);
+        TopDocs docs = TopDocs.merge(sort, realLimit, hits);
 
         List<ChangeData> result =
             Lists.newArrayListWithCapacity(docs.scoreDocs.length);
-        for (ScoreDoc sd : docs.scoreDocs) {
+        for (int i = start; i < docs.scoreDocs.length; i++) {
+          ScoreDoc sd = docs.scoreDocs[i];
           Document doc = searchers[sd.shardIndex].doc(sd.doc, FIELDS);
           result.add(toChangeData(doc));
         }
@@ -415,16 +449,16 @@
     }
   }
 
-  private static ChangeData toChangeData(Document doc) {
+  private ChangeData toChangeData(Document doc) {
     BytesRef cb = doc.getBinaryValue(CHANGE_FIELD);
     if (cb == null) {
       int id = doc.getField(ID_FIELD).numericValue().intValue();
-      return new ChangeData(new Change.Id(id));
+      return changeDataFactory.create(db.get(), new Change.Id(id));
     }
 
     Change change = ChangeProtoField.CODEC.decode(
         cb.bytes, cb.offset, cb.length);
-    ChangeData cd = new ChangeData(change);
+    ChangeData cd = changeDataFactory.create(db.get(), change);
 
     BytesRef[] approvalsBytes = doc.getBinaryValues(APPROVAL_FIELD);
     if (approvalsBytes != null) {
@@ -468,9 +502,17 @@
         doc.add(new LongField(name, (Long) value, store));
       }
     } else if (type == FieldType.TIMESTAMP) {
-      for (Object value : values.getValues()) {
-        int t = QueryBuilder.toIndexTime((Timestamp) value);
-        doc.add(new IntField(name, t, store));
+      @SuppressWarnings("deprecation")
+      boolean legacy = values.getField() == ChangeField.LEGACY_UPDATED;
+      if (legacy) {
+        for (Object value : values.getValues()) {
+          int t = queryBuilder.toIndexTimeInMinutes((Timestamp) value);
+          doc.add(new IntField(name, (int) t, store));
+        }
+      } else {
+        for (Object value : values.getValues()) {
+          doc.add(new LongField(name, ((Timestamp) value).getTime(), store));
+        }
       }
     } else if (type == FieldType.EXACT
         || type == FieldType.PREFIX) {
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index ef96374..583e54f 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.ChangeSchemas;
 import com.google.gerrit.server.index.IndexCollection;
@@ -45,12 +44,7 @@
 
   @Override
   protected void configure() {
-    install(new FactoryModule() {
-      @Override
-      public void configure() {
-        factory(LuceneChangeIndex.Factory.class);
-      }
-    });
+    factory(LuceneChangeIndex.Factory.class);
     install(new IndexModule(threads));
     if (singleVersion == null && base == null) {
       install(new MultiVersionModule());
@@ -62,12 +56,7 @@
   private class MultiVersionModule extends LifecycleModule {
     @Override
     public void configure() {
-      install(new FactoryModule() {
-        @Override
-        public void configure() {
-          factory(OnlineReindexer.Factory.class);
-        }
-      });
+      factory(OnlineReindexer.Factory.class);
       listener().to(LuceneVersionManager.class);
     }
   }
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
index f99cce8..3221b8a 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -33,9 +33,9 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.SortKeyPredicate;
 
+import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.FuzzyQuery;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.NumericRangeQuery;
 import org.apache.lucene.search.PrefixQuery;
@@ -45,7 +45,7 @@
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.NumericUtils;
 
-import java.sql.Timestamp;
+import java.util.Date;
 import java.util.List;
 
 public class QueryBuilder {
@@ -55,27 +55,34 @@
     return intTerm(ID_FIELD, cd.getId().get());
   }
 
-  public static Query toQuery(Schema<ChangeData> schema, Predicate<ChangeData> p)
-      throws QueryParseException {
+  private final Schema<ChangeData> schema;
+  private final org.apache.lucene.util.QueryBuilder queryBuilder;
+
+  public QueryBuilder(Schema<ChangeData> schema, Analyzer analyzer) {
+    this.schema = schema;
+    queryBuilder = new org.apache.lucene.util.QueryBuilder(analyzer);
+  }
+
+  public Query toQuery(Predicate<ChangeData> p) throws QueryParseException {
     if (p instanceof AndPredicate) {
-      return and(schema, p);
+      return and(p);
     } else if (p instanceof OrPredicate) {
-      return or(schema, p);
+      return or(p);
     } else if (p instanceof NotPredicate) {
-      return not(schema, p);
+      return not(p);
     } else if (p instanceof IndexPredicate) {
-      return fieldQuery(schema, (IndexPredicate<ChangeData>) p);
+      return fieldQuery((IndexPredicate<ChangeData>) p);
     } else {
       throw new QueryParseException("cannot create query for index: " + p);
     }
   }
 
-  private static Query or(Schema<ChangeData> schema, Predicate<ChangeData> p)
+  private Query or(Predicate<ChangeData> p)
       throws QueryParseException {
     try {
       BooleanQuery q = new BooleanQuery();
       for (int i = 0; i < p.getChildCount(); i++) {
-        q.add(toQuery(schema, p.getChild(i)), SHOULD);
+        q.add(toQuery(p.getChild(i)), SHOULD);
       }
       return q;
     } catch (BooleanQuery.TooManyClauses e) {
@@ -83,7 +90,7 @@
     }
   }
 
-  private static Query and(Schema<ChangeData> schema, Predicate<ChangeData> p)
+  private Query and(Predicate<ChangeData> p)
       throws QueryParseException {
     try {
       BooleanQuery b = new BooleanQuery();
@@ -95,10 +102,10 @@
           if (n instanceof TimestampRangePredicate) {
             b.add(notTimestamp((TimestampRangePredicate<ChangeData>) n), MUST);
           } else {
-            not.add(toQuery(schema, n));
+            not.add(toQuery(n));
           }
         } else {
-          b.add(toQuery(schema, c), MUST);
+          b.add(toQuery(c), MUST);
         }
       }
       for (Query q : not) {
@@ -110,7 +117,7 @@
     }
   }
 
-  private static Query not(Schema<ChangeData> schema, Predicate<ChangeData> p)
+  private Query not(Predicate<ChangeData> p)
       throws QueryParseException {
     Predicate<ChangeData> n = p.getChild(0);
     if (n instanceof TimestampRangePredicate) {
@@ -120,12 +127,12 @@
     // Lucene does not support negation, start with all and subtract.
     BooleanQuery q = new BooleanQuery();
     q.add(new MatchAllDocsQuery(), MUST);
-    q.add(toQuery(schema, n), MUST_NOT);
+    q.add(toQuery(n), MUST_NOT);
     return q;
   }
 
-  private static Query fieldQuery(Schema<ChangeData> schema,
-      IndexPredicate<ChangeData> p) throws QueryParseException {
+  private Query fieldQuery(IndexPredicate<ChangeData> p)
+      throws QueryParseException {
     if (p.getType() == FieldType.INTEGER) {
       return intQuery(p);
     } else if (p.getType() == FieldType.TIMESTAMP) {
@@ -137,7 +144,7 @@
     } else if (p.getType() == FieldType.FULL_TEXT) {
       return fullTextQuery(p);
     } else if (p instanceof SortKeyPredicate) {
-      return sortKeyQuery(schema, (SortKeyPredicate) p);
+      return sortKeyQuery((SortKeyPredicate) p);
     } else {
       throw badFieldType(p.getType());
     }
@@ -149,7 +156,7 @@
     return new Term(name, bytes);
   }
 
-  private static Query intQuery(IndexPredicate<ChangeData> p)
+  private Query intQuery(IndexPredicate<ChangeData> p)
       throws QueryParseException {
     int value;
     try {
@@ -162,7 +169,7 @@
     return new TermQuery(intTerm(p.getField().getName(), value));
   }
 
-  private static Query sortKeyQuery(Schema<ChangeData> schema, SortKeyPredicate p) {
+  private Query sortKeyQuery(SortKeyPredicate p) {
     long min = p.getMinValue(schema);
     long max = p.getMaxValue(schema);
     return NumericRangeQuery.newLongRange(
@@ -172,33 +179,51 @@
         false, false);
   }
 
-  private static Query timestampQuery(IndexPredicate<ChangeData> p)
+  @SuppressWarnings("deprecation")
+  private Query timestampQuery(IndexPredicate<ChangeData> p)
       throws QueryParseException {
     if (p instanceof TimestampRangePredicate) {
       TimestampRangePredicate<ChangeData> r =
           (TimestampRangePredicate<ChangeData>) p;
-      return NumericRangeQuery.newIntRange(
-          r.getField().getName(),
-          toIndexTime(r.getMinTimestamp()),
-          toIndexTime(r.getMaxTimestamp()),
-          true, true);
+      if (r.getField() == ChangeField.LEGACY_UPDATED) {
+        return NumericRangeQuery.newIntRange(
+            r.getField().getName(),
+            toIndexTimeInMinutes(r.getMinTimestamp()),
+            toIndexTimeInMinutes(r.getMaxTimestamp()),
+            true, true);
+      } else {
+        return NumericRangeQuery.newLongRange(
+            r.getField().getName(),
+            r.getMinTimestamp().getTime(),
+            r.getMaxTimestamp().getTime(),
+            true, true);
+      }
     }
     throw new QueryParseException("not a timestamp: " + p);
   }
 
-  private static Query notTimestamp(TimestampRangePredicate<ChangeData> r)
+  @SuppressWarnings("deprecation")
+  private Query notTimestamp(TimestampRangePredicate<ChangeData> r)
       throws QueryParseException {
     if (r.getMinTimestamp().getTime() == 0) {
-      return NumericRangeQuery.newIntRange(
-          r.getField().getName(),
-          toIndexTime(r.getMaxTimestamp()),
-          null,
-          true, true);
+      if (r.getField() == ChangeField.LEGACY_UPDATED) {
+        return NumericRangeQuery.newIntRange(
+            r.getField().getName(),
+            toIndexTimeInMinutes(r.getMaxTimestamp()),
+            null,
+            true, true);
+      } else {
+        return NumericRangeQuery.newLongRange(
+            r.getField().getName(),
+            r.getMaxTimestamp().getTime(),
+            null,
+            true, true);
+      }
     }
     throw new QueryParseException("cannot negate: " + r);
   }
 
-  private static Query exactQuery(IndexPredicate<ChangeData> p) {
+  private Query exactQuery(IndexPredicate<ChangeData> p) {
     if (p instanceof RegexPredicate<?>) {
       return regexQuery(p);
     } else {
@@ -206,7 +231,7 @@
     }
   }
 
-  private static Query regexQuery(IndexPredicate<ChangeData> p) {
+  private Query regexQuery(IndexPredicate<ChangeData> p) {
     String re = p.getValue();
     if (re.startsWith("^")) {
       re = re.substring(1);
@@ -217,22 +242,19 @@
     return new RegexpQuery(new Term(p.getField().getName(), re));
   }
 
-  private static Query prefixQuery(IndexPredicate<ChangeData> p) {
+  private Query prefixQuery(IndexPredicate<ChangeData> p) {
     return new PrefixQuery(new Term(p.getField().getName(), p.getValue()));
   }
 
-  private static Query fullTextQuery(IndexPredicate<ChangeData> p) {
-    return new FuzzyQuery(new Term(p.getField().getName(), p.getValue()));
+  private Query fullTextQuery(IndexPredicate<ChangeData> p) {
+    return queryBuilder.createPhraseQuery(p.getField().getName(), p.getValue());
   }
 
-  public static int toIndexTime(Timestamp ts) {
+  public int toIndexTimeInMinutes(Date ts) {
     return (int) (ts.getTime() / 60000);
   }
 
   public static IllegalArgumentException badFieldType(FieldType<?> t) {
     return new IllegalArgumentException("unknown index field type " + t);
   }
-
-  private QueryBuilder() {
-  }
 }
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
index 2abf3ef..4ee3bf4 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/SubIndex.java
@@ -31,6 +31,7 @@
 import org.apache.lucene.search.ReferenceManager.RefreshListener;
 import org.apache.lucene.search.SearcherFactory;
 import org.apache.lucene.search.SearcherManager;
+import org.apache.lucene.store.AlreadyClosedException;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.FSDirectory;
 import org.slf4j.Logger;
@@ -157,7 +158,11 @@
 
     try {
       writer.getIndexWriter().commit();
-      writer.getIndexWriter().close(true);
+      try {
+        writer.getIndexWriter().close(true);
+      } catch (AlreadyClosedException e) {
+        // Ignore.
+      }
     } catch (IOException e) {
       log.warn("error closing Lucene writer", e);
     }
diff --git a/gerrit-main/.gitignore b/gerrit-main/.gitignore
deleted file mode 100644
index c847710..0000000
--- a/gerrit-main/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-main.iml
\ No newline at end of file
diff --git a/gerrit-main/.settings/org.eclipse.core.resources.prefs b/gerrit-main/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index e9441bb..0000000
--- a/gerrit-main/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-main/.settings/org.eclipse.core.runtime.prefs b/gerrit-main/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-main/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-main/.settings/org.eclipse.jdt.core.prefs b/gerrit-main/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index a7bc058..0000000
--- a/gerrit-main/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.2
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.2
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-main/.settings/org.eclipse.jdt.ui.prefs b/gerrit-main/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-main/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-main/BUCK b/gerrit-main/BUCK
index da39eec..388126e 100644
--- a/gerrit-main/BUCK
+++ b/gerrit-main/BUCK
@@ -9,5 +9,7 @@
   name = 'main_lib',
   srcs = ['src/main/java/Main.java'],
   deps = ['//gerrit-launcher:launcher'],
+  source = '1.2',
+  target = '1.2',
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-main/src/main/java/Main.java b/gerrit-main/src/main/java/Main.java
index 30a55fb..d3c9fdd 100644
--- a/gerrit-main/src/main/java/Main.java
+++ b/gerrit-main/src/main/java/Main.java
@@ -31,11 +31,11 @@
 
   private static boolean onSupportedJavaVersion() {
     final String version = System.getProperty("java.specification.version");
-    if (1.6 <= parse(version)) {
+    if (1.7 <= parse(version)) {
       return true;
 
     } else {
-      System.err.println("fatal: Gerrit Code Review requires Java 6 or later");
+      System.err.println("fatal: Gerrit Code Review requires Java 7 or later");
       System.err.println("       (trying to run on Java " + version + ")");
       return false;
     }
diff --git a/gerrit-openid/.gitignore b/gerrit-openid/.gitignore
deleted file mode 100644
index 158faf1..0000000
--- a/gerrit-openid/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-openid.iml
\ No newline at end of file
diff --git a/gerrit-openid/.settings/org.eclipse.core.resources.prefs b/gerrit-openid/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index 839d647..0000000
--- a/gerrit-openid/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,5 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/main/resources=UTF-8
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-openid/.settings/org.eclipse.core.runtime.prefs b/gerrit-openid/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-openid/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-openid/.settings/org.eclipse.jdt.core.prefs b/gerrit-openid/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 470942d..0000000
--- a/gerrit-openid/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-openid/.settings/org.eclipse.jdt.ui.prefs b/gerrit-openid/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-openid/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-openid/BUCK b/gerrit-openid/BUCK
index 8da83c3..90d0d48 100644
--- a/gerrit-openid/BUCK
+++ b/gerrit-openid/BUCK
@@ -3,6 +3,7 @@
   srcs = glob(['src/main/java/**/*.java']),
   resources = glob(['src/main/resources/**/*']),
   deps = [
+    '//gerrit-common:annotations',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
     '//gerrit-gwtexpui:server',
@@ -17,6 +18,6 @@
     '//lib/log:api',
     '//lib/openid:consumer',
   ],
-  compile_deps = ['//lib:servlet-api-3_0'],
+  compile_deps = ['//lib:servlet-api-3_1'],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/DiscoveryResult.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/DiscoveryResult.java
index 711f29a..37051da 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/DiscoveryResult.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/DiscoveryResult.java
@@ -25,7 +25,7 @@
     NO_PROVIDER,
 
     /** The provider was discovered, but something else failed. */
-    ERROR;
+    ERROR
   }
 
   Status status;
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdLoginServlet.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdLoginServlet.java
index 4cbb22f..51342f7 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdLoginServlet.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdLoginServlet.java
@@ -24,7 +24,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-/** Handles the <code>/OpenID</code> URL for web based single-sign-on. */
+/** Handles the {@code /OpenID} URL for web based single-sign-on. */
 @SuppressWarnings("serial")
 @Singleton
 class OpenIdLoginServlet extends HttpServlet {
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/SignInMode.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/SignInMode.java
index b6a9857..9aae6c5 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/SignInMode.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/SignInMode.java
@@ -15,5 +15,5 @@
 package com.google.gerrit.httpd.auth.openid;
 
 enum SignInMode {
-  SIGN_IN, LINK_IDENTIY, REGISTER;
+  SIGN_IN, LINK_IDENTIY, REGISTER
 }
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/XrdsServlet.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/XrdsServlet.java
index 98ad11d..4719a84 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/XrdsServlet.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/XrdsServlet.java
@@ -43,7 +43,7 @@
   protected void doGet(HttpServletRequest req, HttpServletResponse rsp)
       throws IOException {
     final StringBuilder r = new StringBuilder();
-    r.append("<?xml version=\"1.0\" encoding=\"" + ENC + "\"?>");
+    r.append("<?xml version=\"1.0\" encoding=\"").append(ENC).append("\"?>");
     r.append("<xrds:XRDS");
     r.append(" xmlns:xrds=\"xri://$xrds\"");
     r.append(" xmlns:openid=\"http://openid.net/xmlns/1.0\"");
@@ -51,7 +51,8 @@
     r.append("<XRD>");
     r.append("<Service priority=\"1\">");
     r.append("<Type>http://specs.openid.net/auth/2.0/return_to</Type>");
-    r.append("<URI>" + url.get() + OpenIdServiceImpl.RETURN_URL + "</URI>");
+    r.append("<URI>").append(url.get()).append(OpenIdServiceImpl.RETURN_URL)
+     .append("</URI>");
     r.append("</Service>");
     r.append("</XRD>");
     r.append("</xrds:XRDS>");
diff --git a/gerrit-patch-commonsnet/.gitignore b/gerrit-patch-commonsnet/.gitignore
deleted file mode 100644
index 121f8e90..0000000
--- a/gerrit-patch-commonsnet/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-patch-commonsnet.iml
\ No newline at end of file
diff --git a/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs b/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index e9441bb..0000000
--- a/gerrit-patch-commonsnet/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-patch-commonsnet/.settings/org.eclipse.jdt.core.prefs b/gerrit-patch-commonsnet/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 5f73a7f..0000000
--- a/gerrit-patch-commonsnet/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:35 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java b/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
index 1f08a81..bb8296e 100644
--- a/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
+++ b/gerrit-patch-commonsnet/src/main/java/org/apache/commons/net/smtp/AuthSMTPClient.java
@@ -176,9 +176,9 @@
 
   private String toHex(final byte[] b) {
     final StringBuilder sec = new StringBuilder();
-    for (int i = 0; i < b.length; i++) {
-      final int u = (b[i] >> 4) & 0xf;
-      final int l = b[i] & 0xf;
+    for (byte c : b) {
+      final int u = (c >> 4) & 0xf;
+      final int l = c & 0xf;
       sec.append(hexchar[u]);
       sec.append(hexchar[l]);
     }
diff --git a/gerrit-patch-jgit/.gitignore b/gerrit-patch-jgit/.gitignore
deleted file mode 100644
index 7c4c433..0000000
--- a/gerrit-patch-jgit/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-patch-jgit.iml
\ No newline at end of file
diff --git a/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs b/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index e9441bb..0000000
--- a/gerrit-patch-jgit/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-patch-jgit/.settings/org.eclipse.core.runtime.prefs b/gerrit-patch-jgit/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-patch-jgit/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-patch-jgit/.settings/org.eclipse.jdt.core.prefs b/gerrit-patch-jgit/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 5f73a7f..0000000
--- a/gerrit-patch-jgit/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:35 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-patch-jgit/.settings/org.eclipse.jdt.ui.prefs b/gerrit-patch-jgit/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-patch-jgit/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-patch-jgit/BUCK b/gerrit-patch-jgit/BUCK
index 18890ac..00b8228 100644
--- a/gerrit-patch-jgit/BUCK
+++ b/gerrit-patch-jgit/BUCK
@@ -7,7 +7,7 @@
     SRC + 'diff/ReplaceEdit.java',
   ],
   gwtxml = SRC + 'JGit.gwt.xml',
-  deps = [
+  compile_deps = [
     '//lib:gwtjsonrpc',
     '//lib/gwt:user',
     '//lib/jgit:jgit',
@@ -30,3 +30,15 @@
   ],
   visibility = ['PUBLIC'],
 )
+
+java_test(
+  name = 'jgit_patch_tests',
+  srcs = glob(['src/test/java/**/*.java']),
+  deps = [
+    ':server',
+    '//lib/jgit:jgit',
+    '//lib:junit',
+  ],
+  source_under_test = [':server'],
+  visibility = ['//tools/eclipse:classpath'],
+)
diff --git a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
index 9a55e0f..5a9b935 100644
--- a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
+++ b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
@@ -49,7 +49,7 @@
       return new Edit(get(o, 0), get(o, 1), get(o, 2), get(o, 3));
     }
 
-    List<Edit> l = new ArrayList<Edit>((cnt / 4) - 1);
+    List<Edit> l = new ArrayList<>((cnt / 4) - 1);
     for (int i = 4; i < cnt;) {
       int as = get(o, i++);
       int ae = get(o, i++);
diff --git a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/Edit_JsonSerializer.java b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/Edit_JsonSerializer.java
index 7870002..1a3714b 100644
--- a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/Edit_JsonSerializer.java
+++ b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/Edit_JsonSerializer.java
@@ -35,7 +35,7 @@
       return new Edit(get(o, 0), get(o, 1), get(o, 2), get(o, 3));
     }
 
-    List<Edit> l = new ArrayList<Edit>((cnt / 4) - 1);
+    List<Edit> l = new ArrayList<>((cnt / 4) - 1);
     for (int i = 4; i < cnt;) {
       int as = get(o, i++);
       int ae = get(o, i++);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java
similarity index 70%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
rename to gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java
index c48f968..f0ad62a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-patch-jgit/src/test/java/org/eclipse/jgit/diff/EditDeserializerTest.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package org.eclipse.jgit.diff;
 
-public class GroupInfo {
-  public String id;
-  public String name;
-  public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+import org.junit.Test;
+import static org.junit.Assert.assertNotNull;
+
+public class EditDeserializerTest {
+  @Test
+  public void testDiffDeserializer() {
+    assertNotNull("edit deserializer", new EditDeserializer());
+  }
 }
diff --git a/gerrit-pgm/.gitignore b/gerrit-pgm/.gitignore
deleted file mode 100644
index dafe355..0000000
--- a/gerrit-pgm/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-pgm.iml
\ No newline at end of file
diff --git a/gerrit-pgm/.settings/org.eclipse.core.resources.prefs b/gerrit-pgm/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index 839d647..0000000
--- a/gerrit-pgm/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,5 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/main/resources=UTF-8
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-pgm/.settings/org.eclipse.core.runtime.prefs b/gerrit-pgm/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-pgm/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-pgm/.settings/org.eclipse.jdt.core.prefs b/gerrit-pgm/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 470942d..0000000
--- a/gerrit-pgm/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-pgm/.settings/org.eclipse.jdt.ui.prefs b/gerrit-pgm/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-pgm/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 8915353..b7162ed 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -1,21 +1,26 @@
 SRCS = 'src/main/java/com/google/gerrit/pgm/'
 
 INIT_API_SRCS = [SRCS + n for n in [
+  'init/AllProjectsConfig.java',
+  'init/AllProjectsNameOnInitProvider.java',
+  'util/ConsoleUI.java',
+  'util/Die.java',
   'init/InitFlags.java',
   'init/InitStep.java',
   'init/InitStep.java',
   'init/InstallPlugins.java',
   'init/Section.java',
-  'util/ConsoleUI.java',
-  'util/Die.java',
 ]]
 
 java_library(
   name = 'init-api',
   srcs = INIT_API_SRCS,
   deps = [
+    '//gerrit-common:annotations',
     '//gerrit-common:server',
+    '//gerrit-reviewdb:server',
     '//gerrit-server:server',
+    '//lib:guava',
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
     '//lib/jgit:jgit',
@@ -54,6 +59,7 @@
     ':init-api',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
+    '//gerrit-lucene:lucene',
     '//gerrit-reviewdb:server',
     '//gerrit-server:server',
     '//gerrit-util-cli:cli',
@@ -66,6 +72,7 @@
     '//lib:guava',
     '//lib:gwtjsonrpc',
     '//lib:gwtorm',
+    '//lib/log:api',
   ],
   compile_deps = ['//gerrit-launcher:launcher'],
   visibility = [
@@ -89,6 +96,7 @@
     '//gerrit-cache-h2:cache-h2',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
+    '//gerrit-gwtexpui:linker_server',
     '//gerrit-gwtexpui:server',
     '//gerrit-httpd:httpd',
     '//gerrit-lucene:lucene',
@@ -103,7 +111,7 @@
     '//lib:guava',
     '//lib:gwtorm',
     '//lib:h2',
-    '//lib:servlet-api-3_0',
+    '//lib:servlet-api-3_1',
     '//lib/guice:guice',
     '//lib/guice:guice-servlet',
     '//lib/jetty:server',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
index bd22146..76adadc 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.pgm.init.InitFlags;
 import com.google.gerrit.pgm.init.InitModule;
 import com.google.gerrit.pgm.init.InstallPlugins;
+import com.google.gerrit.pgm.init.PluginsDistribution;
 import com.google.gerrit.pgm.init.SitePathInitializer;
 import com.google.gerrit.pgm.util.ConsoleUI;
 import com.google.gerrit.pgm.util.Die;
@@ -47,29 +48,49 @@
 import com.google.inject.TypeLiteral;
 import com.google.inject.spi.Message;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 
 import javax.sql.DataSource;
 
 /** Initialize a new Gerrit installation. */
 public class BaseInit extends SiteProgram {
+  private static final Logger log =
+      LoggerFactory.getLogger(BaseInit.class);
 
   private final boolean standalone;
+  private final boolean initDb;
+  protected final PluginsDistribution pluginsDistribution;
+  private final List<String> pluginsToInstall;
 
-  public BaseInit() {
+  protected BaseInit(PluginsDistribution pluginsDistribution,
+      List<String> pluginsToInstall) {
     this.standalone = true;
+    this.initDb = true;
+    this.pluginsDistribution = pluginsDistribution;
+    this.pluginsToInstall = pluginsToInstall;
   }
 
-  public BaseInit(File sitePath, boolean standalone) {
-    this(sitePath, null, standalone);
+  public BaseInit(File sitePath, boolean standalone, boolean initDb,
+      PluginsDistribution pluginsDistribution, List<String> pluginsToInstall) {
+    this(sitePath, null, standalone, initDb, pluginsDistribution, pluginsToInstall);
   }
 
   public BaseInit(File sitePath, final Provider<DataSource> dsProvider,
-      boolean standalone) {
+      boolean standalone, boolean initDb,
+      PluginsDistribution pluginsDistribution, List<String> pluginsToInstall) {
     super(sitePath, dsProvider);
     this.standalone = standalone;
+    this.initDb = initDb;
+    this.pluginsDistribution = pluginsDistribution;
+    this.pluginsToInstall = pluginsToInstall;
   }
 
   @Override
@@ -89,6 +110,8 @@
 
       run = createSiteRun(init);
       run.upgradeSchema();
+
+      init.initializer.postRun();
     } catch (Exception failure) {
       if (init.flags.deleteOnFailure) {
         recursiveDelete(getSitePath());
@@ -118,7 +141,25 @@
   }
 
   protected List<String> getInstallPlugins() {
-    return null;
+    try {
+      if (pluginsToInstall != null && pluginsToInstall.isEmpty()) {
+        return Collections.emptyList();
+      }
+      List<String> names = pluginsDistribution.listPluginNames();
+      if (pluginsToInstall != null) {
+        for (Iterator<String> i = names.iterator(); i.hasNext();) {
+          String n = i.next();
+          if (!pluginsToInstall.contains(n)) {
+            i.remove();
+          }
+        }
+      }
+      return names;
+    } catch (FileNotFoundException e) {
+      log.warn("Couldn't find distribution archive location."
+          + " No plugin will be installed");
+      return null;
+    }
   }
 
   protected boolean getAutoStart() {
@@ -146,7 +187,7 @@
     final File sitePath = getSitePath();
     final List<Module> m = new ArrayList<Module>();
 
-    m.add(new InitModule(standalone));
+    m.add(new InitModule(standalone, initDb));
     m.add(new AbstractModule() {
       @Override
       protected void configure() {
@@ -156,6 +197,7 @@
             Objects.firstNonNull(getInstallPlugins(), Lists.<String> newArrayList());
         bind(new TypeLiteral<List<String>>() {}).annotatedWith(
             InstallPlugins.class).toInstance(plugins);
+        bind(PluginsDistribution.class).toInstance(pluginsDistribution);
       }
     });
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 6adaf32..9d10e66 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.pgm.http.jetty.JettyEnv;
 import com.google.gerrit.pgm.http.jetty.JettyModule;
 import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter;
+import com.google.gerrit.pgm.shell.JythonShell;
 import com.google.gerrit.pgm.util.ErrorLogFile;
 import com.google.gerrit.pgm.util.GarbageCollectionLogFile;
 import com.google.gerrit.pgm.util.LogFileCompressor;
@@ -43,38 +44,43 @@
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.server.account.InternalAccountDirectory;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.change.MergeabilityChecksExecutorModule;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.AuthConfigModule;
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
 import com.google.gerrit.server.config.CanonicalWebUrlProvider;
 import com.google.gerrit.server.config.GerritGlobalModule;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.MasterNodeStartup;
 import com.google.gerrit.server.contact.HttpContactStoreConnection;
 import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.index.NoIndexModule;
+import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.patch.IntraLineWorkerPool;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
 import com.google.gerrit.server.plugins.PluginRestApiModule;
+import com.google.gerrit.server.schema.DataSourceProvider;
 import com.google.gerrit.server.schema.SchemaVersionCheck;
 import com.google.gerrit.server.ssh.NoSshKeyCache;
 import com.google.gerrit.server.ssh.NoSshModule;
+import com.google.gerrit.server.ssh.SshAddressesModule;
 import com.google.gerrit.solr.SolrIndexModule;
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
-import com.google.gerrit.sshd.commands.MasterCommandModule;
-import com.google.gerrit.sshd.commands.SlaveCommandModule;
+import com.google.gerrit.sshd.commands.DefaultCommandModule;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+import com.google.inject.Key;
 import com.google.inject.Module;
 import com.google.inject.Provider;
 import com.google.inject.Stage;
 
+import org.eclipse.jgit.lib.Config;
 import org.kohsuke.args4j.Option;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -114,6 +120,9 @@
   @Option(name = "--console-log", usage = "Log to console (not $site_path/logs)")
   private boolean consoleLog;
 
+  @Option(name = "-s", usage = "Start interactive shell")
+  private boolean inspector;
+
   @Option(name = "--run-id", usage = "Cookie to store in $site_path/logs/gerrit.run")
   private String runId;
 
@@ -133,6 +142,7 @@
   private Injector httpdInjector;
   private File runFile;
   private boolean test;
+  private AbstractModule luceneModule;
 
   private Runnable serverStarted;
 
@@ -144,6 +154,10 @@
     this.serverStarted = serverStarted;
   }
 
+  public void setEnableHttpd(boolean enable) {
+    httpd = enable;
+  }
+
   @Override
   public int run() throws Exception {
     if (doInit) {
@@ -216,7 +230,16 @@
         serverStarted.run();
       }
 
-      RuntimeShutdown.waitFor();
+      if (inspector) {
+        JythonShell shell = new JythonShell();
+        shell.set("m", manager);
+        shell.set("ds", dbInjector.getInstance(DataSourceProvider.class));
+        shell.set("schk", dbInjector.getInstance(SchemaVersionCheck.class));
+        shell.set("d", this);
+        shell.run();
+      } else {
+        RuntimeShutdown.waitFor();
+      }
       return 0;
     } catch (Throwable err) {
       log.error("Unable to start daemon", err);
@@ -237,6 +260,12 @@
   }
 
   @VisibleForTesting
+  public void setLuceneModule(LuceneIndexModule m) {
+    luceneModule = m;
+    test = true;
+  }
+
+  @VisibleForTesting
   public void start() {
     if (dbInjector == null) {
       dbInjector = createDbInjector(MULTI_USER);
@@ -247,6 +276,7 @@
       .setCfgInjector(cfgInjector);
     manager.add(dbInjector, cfgInjector, sysInjector);
 
+    sshd &= !sshdOff();
     if (sshd) {
       initSshd();
     }
@@ -263,6 +293,11 @@
     manager.stop();
   }
 
+  private boolean sshdOff() {
+    Config cfg = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
+    return new SshAddressesModule().getListenAddresses(cfg).isEmpty();
+  }
+
   private String myVersion() {
     return com.google.gerrit.common.Version.getVersion();
   }
@@ -280,6 +315,7 @@
     modules.add(new WorkQueue.Module());
     modules.add(new ChangeHookRunner.Module());
     modules.add(new ReceiveCommitsExecutorModule());
+    modules.add(new MergeabilityChecksExecutorModule());
     modules.add(new IntraLineWorkerPool.Module());
     modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
     modules.add(new InternalAccountDirectory.Module());
@@ -287,18 +323,7 @@
     modules.add(new SmtpEmailSender.Module());
     modules.add(new SignedTokenEmailTokenVerifier.Module());
     modules.add(new PluginRestApiModule());
-    AbstractModule changeIndexModule;
-    switch (IndexModule.getIndexType(cfgInjector)) {
-      case LUCENE:
-        changeIndexModule = new LuceneIndexModule();
-        break;
-      case SOLR:
-        changeIndexModule = new SolrIndexModule();
-        break;
-      default:
-        changeIndexModule = new NoIndexModule();
-    }
-    modules.add(changeIndexModule);
+    modules.add(createIndexModule());
     if (Objects.firstNonNull(httpd, true)) {
       modules.add(new CanonicalWebUrlModule() {
         @Override
@@ -331,6 +356,18 @@
     return cfgInjector.createChildInjector(modules);
   }
 
+  private AbstractModule createIndexModule() {
+    IndexType indexType = IndexModule.getIndexType(cfgInjector);
+    switch (indexType) {
+      case LUCENE:
+        return luceneModule != null ? luceneModule : new LuceneIndexModule();
+      case SOLR:
+        return new SolrIndexModule();
+      default:
+        throw new IllegalStateException("unsupported index.type = " + indexType);
+    }
+  }
+
   private void initSshd() {
     sshInjector = createSshInjector();
     sysInjector.getInstance(PluginGuiceEnvironment.class)
@@ -340,19 +377,12 @@
 
   private Injector createSshInjector() {
     final List<Module> modules = new ArrayList<Module>();
-    if (sshd) {
-      modules.add(sysInjector.getInstance(SshModule.class));
-      if (!test) {
-        modules.add(new SshHostKeyModule());
-      }
-      if (slave) {
-        modules.add(new SlaveCommandModule());
-      } else {
-        modules.add(new MasterCommandModule());
-      }
-    } else {
-      modules.add(new NoSshModule());
+    modules.add(sysInjector.getInstance(SshModule.class));
+    if (!test) {
+      modules.add(new SshHostKeyModule());
     }
+    modules.add(new DefaultCommandModule(slave));
+
     return sysInjector.createChildInjector(modules);
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index 68e0f6a..fa77732 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -31,7 +31,6 @@
 import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.Module;
-import com.google.inject.Provider;
 
 import org.kohsuke.args4j.Option;
 
@@ -40,8 +39,6 @@
 import java.util.List;
 import java.util.ArrayList;
 
-import javax.sql.DataSource;
-
 /** Initialize a new Gerrit installation. */
 public class Init extends BaseInit {
   @Option(name = "--batch", usage = "Batch mode; skip interactive prompting")
@@ -56,21 +53,18 @@
   @Option(name = "--list-plugins", usage = "List available plugins")
   private boolean listPlugins;
 
-  @Option(name = "--install-plugin", usage = "Install given plugin without asking", multiValued = true)
+  @Option(name = "--install-plugin", usage = "Install given plugin without asking")
   private List<String> installPlugins;
 
   @Inject
   Browser browser;
 
   public Init() {
+    super(new WarDistribution(), null);
   }
 
   public Init(File sitePath) {
-    this(sitePath, null);
-  }
-
-  public Init(File sitePath, final Provider<DataSource> dsProvider) {
-    super(sitePath, dsProvider, true);
+    super(sitePath, true, true, new WarDistribution(), null);
     batchMode = true;
     noAutoStart = true;
   }
@@ -81,7 +75,7 @@
 
     if (!skipPlugins) {
       final List<PluginData> plugins =
-          InitPlugins.listPluginsAndRemoveTempFiles(init.site);
+          InitPlugins.listPluginsAndRemoveTempFiles(init.site, pluginsDistribution);
       ConsoleUI ui = ConsoleUI.getInstance(false);
       verifyInstallPluginList(ui, plugins);
       if (listPlugins) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java
index 803b702..4c66f0b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/PrologShell.java
@@ -32,7 +32,7 @@
 import java.util.List;
 
 public class PrologShell extends AbstractProgram {
-  @Option(name = "-s", multiValued = true, metaVar = "FILE.pl", usage = "file to load")
+  @Option(name = "-s", metaVar = "FILE.pl", usage = "file to load")
   private List<String> fileName = new ArrayList<String>();
 
   @Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
index 4512078..12e1e99 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
@@ -26,6 +26,7 @@
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
@@ -42,9 +43,9 @@
     }
     try {
       JavaSchemaModel jsm = new JavaSchemaModel(ReviewDb.class);
-      PrintWriter out = new PrintWriter(new BufferedWriter(
-          new OutputStreamWriter(lock.getOutputStream(), "UTF-8")));
-      try {
+      try (OutputStream o = lock.getOutputStream();
+          PrintWriter out = new PrintWriter(
+              new BufferedWriter(new OutputStreamWriter(o, "UTF-8")))) {
         String header;
         InputStream in = getClass().getResourceAsStream("ProtoGenHeader.txt");
         try {
@@ -60,8 +61,6 @@
         out.write(header.replace("@@VERSION@@", version));
         jsm.generateProto(out);
         out.flush();
-      } finally {
-        out.close();
       }
       if (!lock.commit()) {
         throw die("Could not write to " + file);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index b67ac87..e712caa 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -15,9 +15,13 @@
 package com.google.gerrit.pgm;
 
 import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+import static com.google.inject.Scopes.SINGLETON;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.DisabledChangeHooks;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleManager;
@@ -27,17 +31,47 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.rules.PrologModule;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountByEmailCacheImpl;
+import com.google.gerrit.server.account.AccountCacheImpl;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupCacheImpl;
+import com.google.gerrit.server.account.GroupIncludeCacheImpl;
 import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.change.ChangeKindCache;
+import com.google.gerrit.server.change.MergeabilityChecker;
+import com.google.gerrit.server.change.MergeabilityChecksExecutor;
+import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
+import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.CanonicalWebUrlProvider;
+import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitModule;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.group.GroupModule;
 import com.google.gerrit.server.index.ChangeBatchIndexer;
 import com.google.gerrit.server.index.ChangeIndex;
 import com.google.gerrit.server.index.ChangeSchemas;
 import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.index.NoIndexModule;
+import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.notedb.NoteDbModule;
 import com.google.gerrit.server.patch.PatchListCacheImpl;
+import com.google.gerrit.server.project.AccessControlModule;
+import com.google.gerrit.server.project.CommentLinkInfo;
+import com.google.gerrit.server.project.CommentLinkProvider;
+import com.google.gerrit.server.project.ProjectCacheImpl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.SectionSortCache;
+import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.schema.DataSourceProvider;
 import com.google.gerrit.server.schema.DataSourceType;
 import com.google.gerrit.solr.SolrIndexModule;
@@ -48,8 +82,11 @@
 import com.google.inject.Key;
 import com.google.inject.Module;
 import com.google.inject.Provider;
+import com.google.inject.Provides;
 import com.google.inject.ProvisionException;
+import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
+import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ProgressMonitor;
@@ -77,6 +114,9 @@
   @Option(name = "--output", usage = "Prefix for output; path for local disk index, or prefix for remote index")
   private String outputBase;
 
+  @Option(name = "--recheck-mergeable", usage = "Recheck mergeable flag on all changes")
+  private boolean recheckMergeable;
+
   @Option(name = "--verbose", usage = "Output debug information for each change")
   private boolean verbose;
 
@@ -91,9 +131,6 @@
   public int run() throws Exception {
     mustHaveValidSite();
     dbInjector = createDbInjector(MULTI_USER);
-    if (IndexModule.getIndexType(dbInjector) == IndexType.SQL) {
-      throw die("index.type must be configured (or not SQL)");
-    }
     limitThreads();
     disableLuceneAutomaticCommit();
     if (version == null) {
@@ -109,11 +146,15 @@
     sysManager.start();
 
     index = sysInjector.getInstance(IndexCollection.class).getSearchIndex();
-    index.markReady(false);
-    index.deleteAll();
-    int result = indexAll();
-    index.markReady(true);
-
+    int result = 0;
+    try {
+      index.markReady(false);
+      index.deleteAll();
+      result = indexAll();
+      index.markReady(true);
+    } catch (Exception e) {
+      throw die(e.getMessage());
+    }
     sysManager.stop();
     dbManager.stop();
     return result;
@@ -145,11 +186,11 @@
         changeIndexModule = new SolrIndexModule(false, threads, outputBase);
         break;
       default:
-        changeIndexModule = new NoIndexModule();
+        throw new IllegalStateException("unsupported index.type");
     }
     modules.add(changeIndexModule);
     modules.add(new ReviewDbModule());
-    modules.add(new AbstractModule() {
+    modules.add(new FactoryModule() {
       @SuppressWarnings("rawtypes")
       @Override
       protected void configure() {
@@ -157,9 +198,36 @@
         // once, so don't worry about cache removal.
         bind(new TypeLiteral<DynamicSet<CacheRemovalListener>>() {})
             .toInstance(DynamicSet.<CacheRemovalListener> emptySet());
+        bind(new TypeLiteral<List<CommentLinkInfo>>() {})
+            .toProvider(CommentLinkProvider.class).in(SINGLETON);
+        bind(String.class).annotatedWith(CanonicalWebUrl.class)
+            .toProvider(CanonicalWebUrlProvider.class);
+        bind(IdentifiedUser.class)
+          .toProvider(Providers. <IdentifiedUser>of(null));
+        bind(CurrentUser.class).to(IdentifiedUser.class);
+        install(new AccessControlModule());
         install(new DefaultCacheFactory.Module());
+        install(new GroupModule());
+        install(new PrologModule());
+        install(AccountByEmailCacheImpl.module());
+        install(AccountCacheImpl.module());
+        install(GroupCacheImpl.module());
+        install(GroupIncludeCacheImpl.module());
+        install(ProjectCacheImpl.module());
+        install(SectionSortCache.module());
+        factory(CapabilityControl.Factory.class);
+        factory(ChangeData.Factory.class);
+        factory(ProjectState.Factory.class);
+
+        if (recheckMergeable) {
+          install(new MergeabilityModule());
+        } else {
+          bind(MergeabilityChecker.class)
+              .toProvider(Providers.<MergeabilityChecker> of(null));
+        }
       }
     });
+
     return dbInjector.createChildInjector(modules);
   }
 
@@ -213,6 +281,43 @@
     }
   }
 
+  private static class MergeabilityModule extends FactoryModule {
+    @Override
+    public void configure() {
+      factory(PatchSetInserter.Factory.class);
+      bind(ChangeHooks.class).to(DisabledChangeHooks.class);
+      bind(ReplacePatchSetSender.Factory.class).toProvider(
+          Providers.<ReplacePatchSetSender.Factory>of(null));
+
+      factory(MergeUtil.Factory.class);
+      DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
+      DynamicSet.setOf(binder(), CommitValidationListener.class);
+      factory(CommitValidators.Factory.class);
+
+      install(ChangeKindCache.module());
+
+      install(new GitModule());
+      install(new NoteDbModule());
+    }
+
+    @Provides
+    @Singleton
+    @MergeabilityChecksExecutor(Priority.BACKGROUND)
+    public WorkQueue.Executor createMergeabilityChecksExecutor(
+        WorkQueue queues) {
+      return queues.createQueue(1, "MergeabilityChecks");
+    }
+
+    @Provides
+    @Singleton
+    @MergeabilityChecksExecutor(Priority.INTERACTIVE)
+    public WorkQueue.Executor createInteractiveMergeabilityChecksExecutor(
+        @MergeabilityChecksExecutor(Priority.BACKGROUND)
+          WorkQueue.Executor bg) {
+      return bg;
+    }
+  }
+
   private int indexAll() throws Exception {
     ReviewDb db = sysInjector.getInstance(ReviewDb.class);
     ProgressMonitor pm = new TextProgressMonitor();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java
deleted file mode 100644
index 795ba5b..0000000
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ScanTrackingIds.java
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (C) 2010 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.pgm;
-
-import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
-
-import com.google.gerrit.lifecycle.LifecycleManager;
-import com.google.gerrit.pgm.util.SiteProgram;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.config.TrackingFooters;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.schema.SchemaVersionCheck;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.TextProgressMonitor;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.kohsuke.args4j.Option;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/** Scan changes and update the trackingid information for them. */
-public class ScanTrackingIds extends SiteProgram {
-  @Option(name = "--threads", usage = "Number of concurrent threads to run")
-  private int threads = 2 * Runtime.getRuntime().availableProcessors();
-
-  private final LifecycleManager manager = new LifecycleManager();
-  private final TextProgressMonitor monitor = new TextProgressMonitor();
-  private List<Change> todo;
-
-  private Injector dbInjector;
-
-  @Inject
-  private TrackingFooters footers;
-
-  @Inject
-  private GitRepositoryManager gitManager;
-
-  @Inject
-  private SchemaFactory<ReviewDb> database;
-
-  @Override
-  public int run() throws Exception {
-    if (threads <= 0) {
-      threads = 1;
-    }
-
-    dbInjector = createDbInjector(MULTI_USER);
-    manager.add(
-        dbInjector,
-        dbInjector.createChildInjector(SchemaVersionCheck.module()));
-    manager.start();
-    dbInjector.injectMembers(this);
-
-    final ReviewDb db = database.open();
-    try {
-      todo = db.changes().all().toList();
-      synchronized (monitor) {
-        monitor.beginTask("Scanning changes", todo.size());
-      }
-    } finally {
-      db.close();
-    }
-
-    final List<Worker> workers = new ArrayList<Worker>(threads);
-    for (int tid = 0; tid < threads; tid++) {
-      Worker t = new Worker();
-      t.start();
-      workers.add(t);
-    }
-    for (Worker t : workers) {
-      t.join();
-    }
-    synchronized (monitor) {
-      monitor.endTask();
-    }
-    manager.stop();
-    return 0;
-  }
-
-  private void scan(ReviewDb db, Change change) {
-    final Project.NameKey project = change.getDest().getParentKey();
-    final Repository git;
-    try {
-      git = gitManager.openRepository(project);
-    } catch (IOException e) {
-      return;
-    }
-    try {
-      PatchSet ps = db.patchSets().get(change.currentPatchSetId());
-      if (ps == null || ps.getRevision() == null
-          || ps.getRevision().get() == null) {
-        return;
-      }
-      ChangeUtil.updateTrackingIds(db, change, footers, parse(git, ps)
-          .getFooterLines());
-    } catch (OrmException error) {
-      System.err.println("ERR " + error.getMessage());
-    } catch (IOException error) {
-      System.err.println("ERR Cannot scan " + change.getId() + ": "
-          + error.getMessage());
-    } finally {
-      git.close();
-    }
-  }
-
-  private RevCommit parse(final Repository git, PatchSet ps)
-      throws MissingObjectException, IncorrectObjectTypeException, IOException {
-    RevWalk rw = new RevWalk(git);
-    try {
-      return rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
-    } finally {
-      rw.release();
-    }
-  }
-
-  private Change next() {
-    synchronized (todo) {
-      if (todo.isEmpty()) {
-        return null;
-      }
-      return todo.remove(todo.size() - 1);
-    }
-  }
-
-  private class Worker extends Thread {
-    @Override
-    public void run() {
-      ReviewDb db;
-      try {
-        db = database.open();
-      } catch (OrmException e) {
-        e.printStackTrace();
-        return;
-      }
-      try {
-        for (;;) {
-          Change change = next();
-          if (change == null) {
-            break;
-          }
-          scan(db, change);
-          synchronized (monitor) {
-            monitor.update(1);
-          }
-        }
-      } finally {
-        db.close();
-      }
-    }
-  }
-}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java
new file mode 100644
index 0000000..2c34711
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm;
+
+import static com.google.gerrit.pgm.init.InitPlugins.JAR;
+import static com.google.gerrit.pgm.init.InitPlugins.PLUGIN_DIR;
+
+import com.google.gerrit.launcher.GerritLauncher;
+import com.google.gerrit.pgm.init.PluginsDistribution;
+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.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+@Singleton
+public class WarDistribution implements PluginsDistribution {
+
+  @Override
+  public void foreach(Processor processor) throws FileNotFoundException, IOException {
+    File myWar = GerritLauncher.getDistributionArchive();
+    if (myWar.isFile()) {
+      try (ZipFile zf = new ZipFile(myWar)) {
+        Enumeration<? extends ZipEntry> e = zf.entries();
+        while (e.hasMoreElements()) {
+          ZipEntry ze = e.nextElement();
+          if (ze.isDirectory()) {
+            continue;
+          }
+
+          if (ze.getName().startsWith(PLUGIN_DIR) && ze.getName().endsWith(JAR)) {
+            String pluginJarName = new File(ze.getName()).getName();
+            String pluginName = pluginJarName.substring(0,
+                pluginJarName.length() - JAR.length());
+            final InputStream in = zf.getInputStream(ze);
+            processor.process(pluginName, in);
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public List<String> listPluginNames() throws FileNotFoundException {
+    // not yet used
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
index e086e6a..5ca53df 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
@@ -18,9 +18,9 @@
 import com.google.common.base.Strings;
 import com.google.gwtexpui.server.CacheHeaders;
 
-import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.HttpConnection;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.ErrorHandler;
 import org.slf4j.Logger;
@@ -37,8 +37,8 @@
 
   public void handle(String target, Request baseRequest,
       HttpServletRequest req, HttpServletResponse res) throws IOException {
-    AbstractHttpConnection conn = AbstractHttpConnection.getCurrentConnection();
-    conn.getRequest().setHandled(true);
+    HttpConnection conn = HttpConnection.getCurrentConnection();
+    baseRequest.setHandled(true);
     try {
       log(req);
     } finally {
@@ -46,10 +46,11 @@
     }
   }
 
-  private void reply(AbstractHttpConnection conn, HttpServletResponse res)
+  private void reply(HttpConnection conn, HttpServletResponse res)
       throws IOException {
     byte[] msg = message(conn);
-    res.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain; charset=ISO-8859-1");
+    res.setHeader(HttpHeader.CONTENT_TYPE.asString(),
+        "text/plain; charset=ISO-8859-1");
     res.setContentLength(msg.length);
     try {
       CacheHeaders.setNotCacheable(res);
@@ -63,10 +64,12 @@
     }
   }
 
-  private static byte[] message(AbstractHttpConnection conn) {
-    String msg = conn.getResponse().getReason();
-    if (msg == null)
-      msg = HttpStatus.getMessage(conn.getResponse().getStatus());
+  private static byte[] message(HttpConnection conn) {
+    String msg = conn.getHttpChannel().getResponse().getReason();
+    if (msg == null) {
+      msg = HttpStatus.getMessage(conn.getHttpChannel()
+          .getResponse().getStatus());
+    }
     return msg.getBytes(Charsets.ISO_8859_1);
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
index 6aef509..03b5a55 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
@@ -17,12 +17,12 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.util.LogUtil;
 import com.google.gerrit.server.util.TimeUtil;
 
 import org.apache.log4j.Appender;
 import org.apache.log4j.AsyncAppender;
 import org.apache.log4j.DailyRollingFileAppender;
-import org.apache.log4j.Layout;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 import org.apache.log4j.spi.ErrorHandler;
@@ -35,45 +35,52 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.TimeZone;
 
 /** Writes the {@code httpd_log} file with per-request data. */
 class HttpLog extends AbstractLifeCycle implements RequestLog {
   private static final Logger log = Logger.getLogger(HttpLog.class);
   private static final String LOG_NAME = "httpd_log";
 
-  private static final String P_HOST = "Host";
-  private static final String P_USER = "User";
-  private static final String P_METHOD = "Method";
-  private static final String P_RESOURCE = "Resource";
-  private static final String P_PROTOCOL = "Version";
-  private static final String P_STATUS = "Status";
-  private static final String P_CONTENT_LENGTH = "Content-Length";
-  private static final String P_REFERER = "Referer";
-  private static final String P_USER_AGENT = "User-Agent";
+  protected static final String P_HOST = "Host";
+  protected static final String P_USER = "User";
+  protected static final String P_METHOD = "Method";
+  protected static final String P_RESOURCE = "Resource";
+  protected static final String P_PROTOCOL = "Version";
+  protected static final String P_STATUS = "Status";
+  protected static final String P_CONTENT_LENGTH = "Content-Length";
+  protected static final String P_REFERER = "Referer";
+  protected static final String P_USER_AGENT = "User-Agent";
 
   private final AsyncAppender async;
 
   HttpLog(final SitePaths site, final Config config) {
-    final DailyRollingFileAppender dst = new DailyRollingFileAppender();
-    dst.setName(LOG_NAME);
-    dst.setLayout(new MyLayout());
-    dst.setEncoding("UTF-8");
-    dst.setFile(new File(resolve(site.logs_dir), LOG_NAME).getPath());
-    dst.setImmediateFlush(true);
-    dst.setAppend(true);
-    dst.setThreshold(Level.INFO);
-    dst.setErrorHandler(new DieErrorHandler());
-    dst.activateOptions();
-    dst.setErrorHandler(new LogLogHandler());
-
     async = new AsyncAppender();
     async.setBlocking(true);
     async.setBufferSize(config.getInt("core", "asyncLoggingBufferSize", 64));
     async.setLocationInfo(false);
-    async.addAppender(dst);
+    if (LogUtil.shouldConfigureLogSystem()) {
+      final DailyRollingFileAppender dst = new DailyRollingFileAppender();
+      dst.setName(LOG_NAME);
+      dst.setLayout(new HttpLogLayout());
+      dst.setEncoding("UTF-8");
+      dst.setFile(new File(resolve(site.logs_dir), LOG_NAME).getPath());
+      dst.setImmediateFlush(true);
+      dst.setAppend(true);
+      dst.setThreshold(Level.INFO);
+      dst.setErrorHandler(new DieErrorHandler());
+      dst.activateOptions();
+      dst.setErrorHandler(new LogLogHandler());
+      async.addAppender(dst);
+    } else {
+      Appender appender = log.getAppender(LOG_NAME);
+      if (appender != null) {
+        async.addAppender(appender);
+      } else {
+        log.warn("No appender with the name: "
+            + LOG_NAME
+            + " was found. HTTPD logging is disabled");
+      }
+    }
     async.activateOptions();
   }
 
@@ -153,105 +160,6 @@
     }
   }
 
-  private static final class MyLayout extends Layout {
-    private final SimpleDateFormat dateFormat;
-    private long lastTimeMillis;
-    private String lastTimeString;
-
-    MyLayout() {
-      final TimeZone tz = TimeZone.getDefault();
-      dateFormat = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z");
-      dateFormat.setTimeZone(tz);
-
-      lastTimeMillis = TimeUtil.nowMs();
-      lastTimeString = dateFormat.format(new Date(lastTimeMillis));
-    }
-
-    @Override
-    public String format(LoggingEvent event) {
-      final StringBuilder buf = new StringBuilder(128);
-
-      opt(buf, event, P_HOST);
-
-      buf.append(' ');
-      buf.append('-'); // identd on client system (never requested)
-
-      buf.append(' ');
-      opt(buf, event, P_USER);
-
-      buf.append(' ');
-      buf.append('[');
-      formatDate(event.getTimeStamp(), buf);
-      buf.append(']');
-
-      buf.append(' ');
-      buf.append('"');
-      buf.append(event.getMDC(P_METHOD));
-      buf.append(' ');
-      buf.append(event.getMDC(P_RESOURCE));
-      buf.append(' ');
-      buf.append(event.getMDC(P_PROTOCOL));
-      buf.append('"');
-
-      buf.append(' ');
-      buf.append(event.getMDC(P_STATUS));
-
-      buf.append(' ');
-      opt(buf, event, P_CONTENT_LENGTH);
-
-      buf.append(' ');
-      dq_opt(buf, event, P_REFERER);
-
-      buf.append(' ');
-      dq_opt(buf, event, P_USER_AGENT);
-
-      buf.append('\n');
-      return buf.toString();
-    }
-
-    private void opt(StringBuilder buf, LoggingEvent event, String key) {
-      String val = (String) event.getMDC(key);
-      if (val == null) {
-        buf.append('-');
-      } else {
-        buf.append(val);
-      }
-    }
-
-    private void dq_opt(StringBuilder buf, LoggingEvent event, String key) {
-      String val = (String) event.getMDC(key);
-      if (val == null) {
-        buf.append('-');
-      } else {
-        buf.append('"');
-        buf.append(val);
-        buf.append('"');
-      }
-    }
-
-    private void formatDate(final long now, final StringBuilder sbuf) {
-      final long rounded = now - (int) (now % 1000);
-      if (rounded != lastTimeMillis) {
-        synchronized (dateFormat) {
-          lastTimeMillis = rounded;
-          lastTimeString = dateFormat.format(new Date(lastTimeMillis));
-          sbuf.append(lastTimeString);
-        }
-      } else {
-        sbuf.append(lastTimeString);
-      }
-    }
-
-    @Override
-    public boolean ignoresThrowable() {
-      return true;
-    }
-
-    @Override
-    public void activateOptions() {
-    }
-  }
-
   private static final class DieErrorHandler implements ErrorHandler {
     @Override
     public void error(String message, Exception e, int errorCode,
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java
new file mode 100644
index 0000000..bab4de7
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HttpLogLayout.java
@@ -0,0 +1,122 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.http.jetty;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+public final class HttpLogLayout extends Layout {
+  private final SimpleDateFormat dateFormat;
+  private long lastTimeMillis;
+  private String lastTimeString;
+
+  public HttpLogLayout() {
+    final TimeZone tz = TimeZone.getDefault();
+    dateFormat = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z");
+    dateFormat.setTimeZone(tz);
+
+    lastTimeMillis = System.currentTimeMillis();
+    lastTimeString = dateFormat.format(new Date(lastTimeMillis));
+  }
+
+  @Override
+  public String format(LoggingEvent event) {
+    final StringBuilder buf = new StringBuilder(128);
+
+    opt(buf, event, HttpLog.P_HOST);
+
+    buf.append(' ');
+    buf.append('-'); // identd on client system (never requested)
+
+    buf.append(' ');
+    opt(buf, event, HttpLog.P_USER);
+
+    buf.append(' ');
+    buf.append('[');
+    formatDate(event.getTimeStamp(), buf);
+    buf.append(']');
+
+    buf.append(' ');
+    buf.append('"');
+    buf.append(event.getMDC(HttpLog.P_METHOD));
+    buf.append(' ');
+    buf.append(event.getMDC(HttpLog.P_RESOURCE));
+    buf.append(' ');
+    buf.append(event.getMDC(HttpLog.P_PROTOCOL));
+    buf.append('"');
+
+    buf.append(' ');
+    buf.append(event.getMDC(HttpLog.P_STATUS));
+
+    buf.append(' ');
+    opt(buf, event, HttpLog.P_CONTENT_LENGTH);
+
+    buf.append(' ');
+    dq_opt(buf, event, HttpLog.P_REFERER);
+
+    buf.append(' ');
+    dq_opt(buf, event, HttpLog.P_USER_AGENT);
+
+    buf.append('\n');
+    return buf.toString();
+  }
+
+  private void opt(StringBuilder buf, LoggingEvent event, String key) {
+    String val = (String) event.getMDC(key);
+    if (val == null) {
+      buf.append('-');
+    } else {
+      buf.append(val);
+    }
+  }
+
+  private void dq_opt(StringBuilder buf, LoggingEvent event, String key) {
+    String val = (String) event.getMDC(key);
+    if (val == null) {
+      buf.append('-');
+    } else {
+      buf.append('"');
+      buf.append(val);
+      buf.append('"');
+    }
+  }
+
+  private void formatDate(final long now, final StringBuilder sbuf) {
+    final long rounded = now - (int) (now % 1000);
+    if (rounded != lastTimeMillis) {
+      synchronized (dateFormat) {
+        lastTimeMillis = rounded;
+        lastTimeString = dateFormat.format(new Date(lastTimeMillis));
+        sbuf.append(lastTimeString);
+      }
+    } else {
+      sbuf.append(lastTimeString);
+    }
+  }
+
+  @Override
+  public boolean ignoresThrowable() {
+    return true;
+  }
+
+  @Override
+  public void activateOptions() {
+  }
+}
+
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyModule.java
index 1ae9355..b563349 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyModule.java
@@ -15,9 +15,8 @@
 package com.google.gerrit.pgm.http.jetty;
 
 import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.inject.AbstractModule;
 
-public class JettyModule extends AbstractModule {
+public class JettyModule extends LifecycleModule {
   private final JettyEnv env;
 
   public JettyModule(final JettyEnv env) {
@@ -28,11 +27,6 @@
   protected void configure() {
     bind(JettyEnv.class).toInstance(env);
     bind(JettyServer.class);
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().to(JettyServer.Lifecycle.class);
-      }
-    });
+    listener().to(JettyServer.Lifecycle.class);
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index e1d1281b..6ba54c9 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -17,7 +17,10 @@
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
+import com.google.common.base.Charsets;
 import com.google.common.base.Objects;
+import com.google.common.escape.Escaper;
+import com.google.common.html.HtmlEscapers;
 import com.google.common.io.ByteStreams;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.launcher.GerritLauncher;
@@ -26,32 +29,40 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.util.TimeUtil;
+import com.google.gwtexpui.linker.server.UserAgentRule;
+import com.google.gwtexpui.server.CacheHeaders;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Singleton;
 import com.google.inject.servlet.GuiceFilter;
 import com.google.inject.servlet.GuiceServletContextListener;
 
-import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.http.HttpScheme;
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.ForwardedRequestCustomizer;
 import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.server.handler.RequestLogHandler;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
 import org.eclipse.jetty.server.session.SessionHandler;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
 import org.eclipse.jetty.servlet.DefaultServlet;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.BlockingArrayQueue;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.util.thread.ThreadPool;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.util.RawParseUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,10 +73,10 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InterruptedIOException;
+import java.io.PrintWriter;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.Enumeration;
@@ -84,6 +95,7 @@
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 @Singleton
 public class JettyServer {
@@ -131,9 +143,8 @@
       throws MalformedURLException, IOException {
     this.site = site;
 
-    httpd = new Server();
-    httpd.setConnectors(listen(cfg));
-    httpd.setThreadPool(threadPool(cfg));
+    httpd = new Server(threadPool(cfg));
+    httpd.setConnectors(listen(httpd, cfg));
 
     Handler app = makeContext(env, cfg);
     if (cfg.getBoolean("httpd", "requestLog", !reverseProxy)) {
@@ -142,15 +153,12 @@
       handler.setHandler(app);
       app = handler;
     }
-    httpd.setHandler(app);
 
+    httpd.setHandler(app);
     httpd.setStopAtShutdown(false);
-    httpd.setSendDateHeader(true);
-    httpd.setSendServerVersion(false);
-    httpd.setGracefulShutdown((int) MILLISECONDS.convert(1, SECONDS));
   }
 
-  private Connector[] listen(final Config cfg) {
+  private Connector[] listen(Server server, Config cfg) {
     // OpenID and certain web-based single-sign-on products can cause
     // some very long headers, especially in the Referer header. We
     // need to use a larger default header size to ensure we have
@@ -168,7 +176,8 @@
     for (int idx = 0; idx < listenUrls.length; idx++) {
       final URI u = listenUrls[idx];
       final int defaultPort;
-      final SelectChannelConnector c;
+      final ServerConnector c;
+      HttpConfiguration config = defaultConfig(requestHeaderSize);
 
       if (AuthType.CLIENT_SSL_CERT_LDAP.equals(authType) && ! "https".equals(u.getScheme())) {
         throw new IllegalArgumentException("Protocol '" + u.getScheme()
@@ -179,7 +188,8 @@
 
       if ("http".equals(u.getScheme())) {
         defaultPort = 80;
-        c = new SelectChannelConnector();
+        c = newServerConnector(server, acceptors, config);
+
       } else if ("https".equals(u.getScheme())) {
         SslContextFactory ssl = new SslContextFactory();
         final File keystore = getFile(cfg, "sslkeystore", "etc/keystore");
@@ -188,7 +198,7 @@
           password = "gerrit";
         }
         ssl.setKeyStorePath(keystore.getAbsolutePath());
-        ssl.setTrustStore(keystore.getAbsolutePath());
+        ssl.setTrustStorePath(keystore.getAbsolutePath());
         ssl.setKeyStorePassword(password);
         ssl.setTrustStorePassword(password);
 
@@ -203,24 +213,30 @@
         }
 
         defaultPort = 443;
-        c = new SslSelectChannelConnector(ssl);
+
+        config.addCustomizer(new SecureRequestCustomizer());
+        c = new ServerConnector(server,
+            null, null, null, 0, acceptors,
+            new SslConnectionFactory(ssl, "http/1.1"),
+            new HttpConnectionFactory(config));
 
       } else if ("proxy-http".equals(u.getScheme())) {
         defaultPort = 8080;
-        c = new SelectChannelConnector();
-        c.setForwarded(true);
+        config.addCustomizer(new ForwardedRequestCustomizer());
+        c = newServerConnector(server, acceptors, config);
 
       } else if ("proxy-https".equals(u.getScheme())) {
         defaultPort = 8080;
-        c = new SelectChannelConnector() {
+        config.addCustomizer(new ForwardedRequestCustomizer());
+        config.addCustomizer(new HttpConfiguration.Customizer() {
           @Override
-          public void customize(EndPoint endpoint, Request request)
-              throws IOException {
-            request.setScheme("https");
-            super.customize(endpoint, request);
+          public void customize(Connector connector,
+              HttpConfiguration channelConfig, Request request) {
+            request.setScheme(HttpScheme.HTTPS.asString());
+            request.setSecure(true);
           }
-        };
-        c.setForwarded(true);
+        });
+        c = newServerConnector(server, acceptors, config);
 
       } else {
         throw new IllegalArgumentException("Protocol '" + u.getScheme() + "' "
@@ -249,16 +265,26 @@
         throw new IllegalArgumentException("Invalid httpd.listenurl " + u, e);
       }
 
-      c.setRequestHeaderSize(requestHeaderSize);
-      c.setAcceptors(acceptors);
       c.setReuseAddress(reuseAddress);
-      c.setStatsOn(false);
-
       connectors[idx] = c;
     }
     return connectors;
   }
 
+  private static ServerConnector newServerConnector(Server server,
+      int acceptors, HttpConfiguration config) {
+    return new ServerConnector(server, null, null, null, 0, acceptors,
+        new HttpConnectionFactory(config));
+  }
+
+  private HttpConfiguration defaultConfig(int requestHeaderSize) {
+    HttpConfiguration config = new HttpConfiguration();
+    config.setRequestHeaderSize(requestHeaderSize);
+    config.setSendServerVersion(false);
+    config.setSendDateHeader(true);
+    return config;
+  }
+
   static boolean isReverseProxied(final URI[] listenUrls) {
     for (URI u : listenUrls) {
       if ("http".equals(u.getScheme()) || "https".equals(u.getScheme())) {
@@ -295,11 +321,23 @@
   }
 
   private ThreadPool threadPool(Config cfg) {
-    final QueuedThreadPool pool = new QueuedThreadPool();
+    int maxThreads = cfg.getInt("httpd", null, "maxthreads", 25);
+    int minThreads = cfg.getInt("httpd", null, "minthreads", 5);
+    int maxQueued = cfg.getInt("httpd", null, "maxqueued", 50);
+    int idleTimeout = (int)MILLISECONDS.convert(60, SECONDS);
+    int maxCapacity = maxQueued == 0
+        ? Integer.MAX_VALUE
+        : Math.max(minThreads, maxQueued);
+    QueuedThreadPool pool = new QueuedThreadPool(
+        maxThreads,
+        minThreads,
+        idleTimeout,
+        new BlockingArrayQueue<Runnable>(
+            minThreads, // capacity,
+            minThreads, // growBy,
+            maxCapacity // maxCapacity
+    ));
     pool.setName("HTTP");
-    pool.setMinThreads(cfg.getInt("httpd", null, "minthreads", 5));
-    pool.setMaxThreads(cfg.getInt("httpd", null, "maxthreads", 25));
-    pool.setMaxQueued(cfg.getInt("httpd", null, "maxqueued", 50));
     return pool;
   }
 
@@ -416,7 +454,7 @@
       try {
         baseResource = unpackWar(GerritLauncher.getDistributionArchive());
       } catch (FileNotFoundException err) {
-        if (err.getMessage() == GerritLauncher.NOT_ARCHIVED) {
+        if (GerritLauncher.NOT_ARCHIVED.equals(err.getMessage())) {
           baseResource = useDeveloperBuild(app);
         } else {
           throw err;
@@ -501,93 +539,78 @@
 
   private Resource useDeveloperBuild(ServletContextHandler app)
       throws IOException {
-    // Find ourselves in the CLASSPATH. We should be a loose class file.
-    //
-    URL u = getClass().getResource(getClass().getSimpleName() + ".class");
-    if (u == null) {
-      throw new FileNotFoundException("Cannot find web application root");
-    }
-    if (!"file".equals(u.getProtocol())) {
-      throw new FileNotFoundException("Cannot find web root from " + u);
-    }
+    final File dir = GerritLauncher.getDeveloperBuckOut();
+    final File gen = new File(dir, "gen");
+    final File root = dir.getParentFile();
+    final File dstwar = makeWarTempDir();
+    File ui = new File(dstwar, "gerrit_ui");
+    File p = new File(ui, "permutations");
+    mkdir(ui);
+    p.createNewFile();
+    p.deleteOnExit();
 
-    // Pop up to the top level classes folder that contains us.
-    //
-    File dir = new File(u.getPath());
-    String myName = getClass().getName();
-    for (;;) {
-      int dot = myName.lastIndexOf('.');
-      if (dot < 0) {
-        dir = dir.getParentFile();
-        break;
-      }
-      myName = myName.substring(0, dot);
-      dir = dir.getParentFile();
-    }
+    app.addFilter(new FilterHolder(new Filter() {
+      private final UserAgentRule rule = new UserAgentRule();
+      private String lastTarget;
+      private long lastTime;
 
-    if (!dir.getName().equals("classes")) {
-      throw new FileNotFoundException("Cannot find web root from " + u);
-    }
-    dir = dir.getParentFile(); // pop classes
+      @Override
+      public void doFilter(ServletRequest request, ServletResponse res,
+          FilterChain chain) throws IOException, ServletException {
+        String pkg = "gerrit-gwtui";
+        String target = "ui_" + rule.select((HttpServletRequest) request);
+        String rule = "//" + pkg + ":" + target;
+        File zip = new File(new File(gen, pkg), target + ".zip");
 
-    if ("buck-out".equals(dir.getName())) {
-      final File dstwar = makeWarTempDir();
-      String pkg = "gerrit-gwtui";
-      String target = targetForBrowser(System.getProperty("gerrit.browser"));
-      final File gen = new File(dir, "gen");
-      String out = new File(new File(gen, pkg), target).getAbsolutePath();
-      final File zip = new File(out + ".zip");
-      final File root = dir.getParentFile();
-      final String name = "//" + pkg + ":" + target;
+        synchronized (this) {
+          try {
+            build(root, gen, rule);
+          } catch (BuildFailureException e) {
+            displayFailure(rule, e.why, (HttpServletResponse) res);
+            return;
+          }
 
-      File ui = new File(dstwar, "gerrit_ui");
-      File p = new File(ui, "permutations");
-      mkdir(ui);
-      p.createNewFile();
-      p.deleteOnExit();
-
-      app.addFilter(new FilterHolder(new Filter() {
-        private long last;
-
-        @Override
-        public void doFilter(ServletRequest request, ServletResponse res,
-            FilterChain chain) throws IOException, ServletException {
-          HttpServletRequest req = (HttpServletRequest) request;
-          build(root, gen, name);
-          if (last != zip.lastModified()) {
-            last = zip.lastModified();
+          if (!target.equals(lastTarget) || lastTime != zip.lastModified()) {
+            lastTarget = target;
+            lastTime = zip.lastModified();
             unpack(zip, dstwar);
           }
-          chain.doFilter(req, res);
         }
 
-        @Override
-        public void init(FilterConfig config) {
-        }
-        @Override
-        public void destroy() {
-        }
-      }), "/", EnumSet.of(DispatcherType.REQUEST));
-      return Resource.newResource(dstwar.toURI());
-    } else if ("target".equals(dir.getName())) {
-      return useMavenDeveloperBuild(dir);
-    } else {
-      throw new FileNotFoundException("Cannot find web root from " + u);
-    }
-  }
+        chain.doFilter(request, res);
+      }
 
-  private static String targetForBrowser(String browser) {
-    if (browser == null || browser.isEmpty()) {
-      return "ui_dbg";
-    } else if (browser.startsWith("ui_")) {
-      return browser;
-    } else {
-      return "ui_" + browser;
-    }
+      private void displayFailure(String rule, byte[] why, HttpServletResponse res)
+          throws IOException {
+        res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        res.setContentType("text/html");
+        res.setCharacterEncoding(Charsets.UTF_8.name());
+        CacheHeaders.setNotCacheable(res);
+
+        Escaper html = HtmlEscapers.htmlEscaper();
+        PrintWriter w = res.getWriter();
+        w.write("<html><title>BUILD FAILED</title><body>");
+        w.format("<h1>%s FAILED</h1>", html.escape(rule));
+        w.write("<pre>");
+        w.write(html.escape(RawParseUtils.decode(why)));
+        w.write("</pre>");
+        w.write("</body></html>");
+        w.close();
+      }
+
+      @Override
+      public void init(FilterConfig config) {
+      }
+
+      @Override
+      public void destroy() {
+      }
+    }), "/", EnumSet.of(DispatcherType.REQUEST));
+    return Resource.newResource(dstwar.toURI());
   }
 
   private static void build(File root, File gen, String target)
-      throws IOException {
+      throws IOException, BuildFailureException {
     log.info("buck build " + target);
     Properties properties = loadBuckProperties(gen);
     String buck = Objects.firstNonNull(properties.getProperty("buck"), "buck");
@@ -615,9 +638,7 @@
       throw new InterruptedIOException("interrupted waiting for " + buck);
     }
     if (status != 0) {
-      System.err.write(out);
-      System.err.println();
-      System.exit(status);
+      throw new BuildFailureException(out);
     }
 
     long time = TimeUtil.nowMs() - start;
@@ -637,24 +658,12 @@
     return properties;
   }
 
-  private Resource useMavenDeveloperBuild(File dir) throws IOException {
-    dir = dir.getParentFile(); // pop target
-    dir = dir.getParentFile(); // pop the module we are in
+  @SuppressWarnings("serial")
+  private static class BuildFailureException extends Exception {
+    final byte[] why;
 
-    // Drop down into gerrit-gwtui to find the WAR assets we need.
-    //
-    dir = new File(new File(dir, "gerrit-gwtui"), "target");
-    final File[] entries = dir.listFiles();
-    if (entries == null) {
-      throw new FileNotFoundException("No " + dir);
+    BuildFailureException(byte[] why) {
+      this.why = why;
     }
-    for (File e : entries) {
-      if (e.isDirectory() /* must be a directory */
-          && e.getName().startsWith("gerrit-gwtui-")
-          && new File(e, "gerrit_ui/gerrit_ui.nocache.js").isFile()) {
-        return Resource.newResource(e.toURI());
-      }
-    }
-    throw new FileNotFoundException("No " + dir + "/gerrit-gwtui-*");
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java
new file mode 100644
index 0000000..cd4a0b8
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsConfig.java
@@ -0,0 +1,174 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.git.VersionedMetaData;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+import java.io.IOException;
+
+public class AllProjectsConfig extends VersionedMetaData {
+  private final String project;
+  private final SitePaths site;
+  private final InitFlags flags;
+
+  private Config cfg;
+  private ObjectId revision;
+
+  @Inject
+  AllProjectsConfig(AllProjectsNameOnInitProvider allProjects, SitePaths site,
+      InitFlags flags) {
+    this.project = allProjects.get();
+    this.site = site;
+    this.flags = flags;
+
+  }
+
+  @Override
+  protected String getRefName() {
+    return RefNames.REFS_CONFIG;
+  }
+
+  private File getPath() {
+    File basePath = site.resolve(flags.cfg.getString("gerrit", null, "basePath"));
+    if (basePath == null) {
+      throw new IllegalStateException("gerrit.basePath must be configured");
+    }
+    return FileKey.resolve(new File(basePath, project), FS.DETECTED);
+  }
+
+  public Config load() throws IOException, ConfigInvalidException {
+    File path = getPath();
+    if (path == null) {
+      return null;
+    }
+
+    Repository repo = new FileRepository(path);
+    try {
+      load(repo);
+    } finally {
+      repo.close();
+    }
+    return cfg;
+  }
+
+  @Override
+  protected void onLoad() throws IOException, ConfigInvalidException {
+    cfg = readConfig(ProjectConfig.PROJECT_CONFIG);
+    revision = getRevision();
+  }
+
+  @Override
+  protected boolean onSave(CommitBuilder commit) throws IOException,
+      ConfigInvalidException {
+    throw new UnsupportedOperationException();
+  }
+
+  void save(String message) throws IOException {
+    save(new PersonIdent("Gerrit Initialization", "init@gerrit"), message);
+  }
+
+  public void save(String pluginName, String message) throws IOException {
+    save(new PersonIdent(pluginName, pluginName + "@gerrit"),
+        "Update from plugin " + pluginName + ": " + message);
+  }
+
+  private void save(PersonIdent ident, String msg) throws IOException {
+    File path = getPath();
+    if (path == null) {
+      throw new IOException("All-Projects does not exist.");
+    }
+
+    Repository repo = new FileRepository(path);
+    try {
+      inserter = repo.newObjectInserter();
+      reader = repo.newObjectReader();
+      try {
+        RevWalk rw = new RevWalk(reader);
+        try {
+          RevTree srcTree = revision != null ? rw.parseTree(revision) : null;
+          newTree = readTree(srcTree);
+          saveConfig(ProjectConfig.PROJECT_CONFIG, cfg);
+          ObjectId res = newTree.writeTree(inserter);
+          if (res.equals(srcTree)) {
+            // If there are no changes to the content, don't create the commit.
+            return;
+          }
+
+          CommitBuilder commit = new CommitBuilder();
+          commit.setAuthor(ident);
+          commit.setCommitter(ident);
+          commit.setMessage(msg);
+          commit.setTreeId(res);
+          if (revision != null) {
+            commit.addParentId(revision);
+          }
+          ObjectId newRevision = inserter.insert(commit);
+          updateRef(repo, ident, newRevision, "commit: " + msg);
+          revision = newRevision;
+        } finally {
+          rw.release();
+        }
+      } finally {
+        if (inserter != null) {
+          inserter.release();
+          inserter = null;
+        }
+        if (reader != null) {
+          reader.release();
+          reader = null;
+        }
+      }
+    } finally {
+      repo.close();
+    }
+  }
+
+  private void updateRef(Repository repo, PersonIdent ident,
+      ObjectId newRevision, String refLogMsg) throws IOException {
+    RefUpdate ru = repo.updateRef(getRefName());
+    ru.setRefLogIdent(ident);
+    ru.setNewObjectId(newRevision);
+    ru.setExpectedOldObjectId(revision);
+    ru.setRefLogMessage(refLogMsg, false);
+    RefUpdate.Result r = ru.update();
+    switch(r) {
+      case FAST_FORWARD:
+      case NEW:
+      case NO_CHANGE:
+        break;
+      default:
+        throw new IOException("Failed to update " + getRefName() + " of "
+            + project + ": " + r.name());
+    }
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsNameOnInitProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsNameOnInitProvider.java
new file mode 100644
index 0000000..1c9415a
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AllProjectsNameOnInitProvider.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class AllProjectsNameOnInitProvider implements Provider<String> {
+  private final String name;
+
+  @Inject
+  AllProjectsNameOnInitProvider(Section.Factory sections) {
+    String n = sections.get("gerrit", null).get("allProjects");
+    name = Objects.firstNonNull(
+        Strings.emptyToNull(n), AllProjectsNameProvider.DEFAULT);
+  }
+
+  public String get() {
+    return name;
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
index 1524707..a2037b5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
@@ -107,4 +107,8 @@
       auth.setSecure("restTokenPrivateKey", SignedToken.generateRandomKey());
     }
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
index 2b5729f..7230c9d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitCache.java
@@ -54,4 +54,8 @@
       throw die("cannot create cache.directory " + loc.getAbsolutePath());
     }
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
index aa584e8..92cd46d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitContainer.java
@@ -127,4 +127,8 @@
   private static String javaHome() {
     return System.getProperty("java.home");
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
index 2120a73..0c64552 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
@@ -85,4 +85,8 @@
 
     dci.initConfig(database);
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
index b6f8519..dc8a440 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitGitManager.java
@@ -45,4 +45,8 @@
       throw die("Cannot create " + d);
     }
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
index 3e7901a..699daa8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitHttpd.java
@@ -189,4 +189,8 @@
       throw die("Cannot delete " + tmpdir);
     }
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
new file mode 100644
index 0000000..9966fda
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import com.google.gerrit.lucene.LuceneChangeIndex;
+import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.ChangeSchemas;
+import com.google.gerrit.server.index.IndexModule.IndexType;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+
+/** Initialize the {@code index} configuration section. */
+@Singleton
+class InitIndex implements InitStep {
+  private final ConsoleUI ui;
+  private final Section index;
+  private final SitePaths site;
+  private final InitFlags initFlags;
+
+  @Inject
+  InitIndex(ConsoleUI ui,
+      Section.Factory sections,
+      SitePaths site,
+      InitFlags initFlags) {
+    this.ui = ui;
+    this.index = sections.get("index", null);
+    this.site = site;
+    this.initFlags = initFlags;
+  }
+
+  public void run() throws IOException {
+    ui.header("Index");
+
+    IndexType type = index.select("Type", "type", IndexType.LUCENE);
+    if (type == IndexType.SOLR) {
+      index.string("Solr Index URL", "url", "localhost:9983");
+    }
+    if (site.isNew && type == IndexType.LUCENE) {
+      LuceneChangeIndex.setReady(
+          site, ChangeSchemas.getLatest().getVersion(), true);
+    } else {
+      final String message = String.format(
+        "\nThe index must be %sbuilt before starting Gerrit:\n"
+        + "  java -jar gerrit.war reindex -d site_path\n",
+        site.isNew ? "" : "re");
+      ui.message(message);
+      initFlags.autoStart = false;
+    }
+  }
+
+  @Override
+  public void postRun() throws Exception {
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
new file mode 100644
index 0000000..78cfa3b
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import com.google.gerrit.pgm.util.ConsoleUI;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.util.Arrays;
+
+@Singleton
+public class InitLabels implements InitStep {
+  private static final String KEY_LABEL = "label";
+  private static final String KEY_FUNCTION = "function";
+  private static final String KEY_VALUE = "value";
+  private static final String LABEL_VERIFIED = "Verified";
+
+  private final ConsoleUI ui;
+  private final AllProjectsConfig allProjectsConfig;
+
+  private boolean installVerified;
+
+  @Inject
+  InitLabels(ConsoleUI ui, AllProjectsConfig allProjectsConfig) {
+    this.ui = ui;
+    this.allProjectsConfig = allProjectsConfig;
+  }
+
+  @Override
+  public void run() throws Exception {
+    Config cfg = allProjectsConfig.load();
+    if (cfg == null || !cfg.getSubsections(KEY_LABEL).contains(LABEL_VERIFIED)) {
+      ui.header("Review Labels");
+      installVerified = ui.yesno(false, "Install Verified label");
+    }
+  }
+
+  @Override
+  public void postRun() throws Exception {
+    Config cfg = allProjectsConfig.load();
+    if (installVerified) {
+      cfg.setString(KEY_LABEL, LABEL_VERIFIED, KEY_FUNCTION, "MaxWithBlock");
+      cfg.setStringList(KEY_LABEL, LABEL_VERIFIED, KEY_VALUE,
+          Arrays.asList(new String[] {"-1 Fails", " 0 No score", "+1 Verified"}));
+      allProjectsConfig.save("Configure 'Verified' label");
+    }
+  }
+}
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 94185eb..4ce9a24 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
@@ -25,9 +25,11 @@
 public class InitModule extends FactoryModule {
 
   private final boolean standalone;
+  private final boolean initDb;
 
-  public InitModule(boolean standalone) {
+  public InitModule(boolean standalone, boolean initDb) {
     this.standalone = standalone;
+    this.initDb = initDb;
   }
 
   @Override
@@ -43,10 +45,12 @@
     step().to(UpgradeFrom2_0_x.class);
 
     step().to(InitGitManager.class);
-    if (standalone) {
+    if (initDb) {
       step().to(InitDatabase.class);
     }
+    step().to(InitIndex.class);
     step().to(InitAuth.class);
+    step().to(InitLabels.class);
     step().to(InitSendEmail.class);
     if (standalone) {
       step().to(InitContainer.class);
@@ -54,9 +58,7 @@
     step().to(InitSshd.class);
     step().to(InitHttpd.class);
     step().to(InitCache.class);
-    if (standalone) {
-      step().to(InitPlugins.class);
-    }
+    step().to(InitPlugins.class);
   }
 
   protected LinkedBindingBuilder<InitStep> step() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
index 6b4cedf..5584cf3 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPluginStepsLoader.java
@@ -65,26 +65,28 @@
     return pluginsInitSteps;
   }
 
+  @SuppressWarnings("resource")
   private InitStep loadInitStep(File jar) {
     try {
-      ClassLoader pluginLoader =
+      URLClassLoader pluginLoader =
           new URLClassLoader(new URL[] {jar.toURI().toURL()},
-              InitPluginStepsLoader.class.getClassLoader());
-      JarFile jarFile = new JarFile(jar);
-      Attributes jarFileAttributes = jarFile.getManifest().getMainAttributes();
-      String initClassName = jarFileAttributes.getValue("Gerrit-InitStep");
-      if (initClassName == null) {
+             InitPluginStepsLoader.class.getClassLoader());
+      try (JarFile jarFile = new JarFile(jar)) {
+        Attributes jarFileAttributes = jarFile.getManifest().getMainAttributes();
+        String initClassName = jarFileAttributes.getValue("Gerrit-InitStep");
+        if (initClassName == null) {
+          return null;
+        }
+        @SuppressWarnings("unchecked")
+        Class<? extends InitStep> initStepClass =
+            (Class<? extends InitStep>) pluginLoader.loadClass(initClassName);
+        return getPluginInjector(jar).getInstance(initStepClass);
+      } catch (ClassCastException e) {
+        ui.message(
+            "WARN: InitStep from plugin %s does not implement %s (Exception: %s)",
+            jar.getName(), InitStep.class.getName(), e.getMessage());
         return null;
       }
-      @SuppressWarnings("unchecked")
-      Class<? extends InitStep> initStepClass =
-          (Class<? extends InitStep>) pluginLoader.loadClass(initClassName);
-      return getPluginInjector(jar).getInstance(initStepClass);
-    } catch (ClassCastException e) {
-      ui.message(
-          "WARN: InitStep from plugin %s does not implement %s (Exception: %s)",
-          jar.getName(), InitStep.class.getName(), e.getMessage());
-      return null;
     } catch (Exception e) {
       ui.message(
           "WARN: Cannot load and get plugin init step for %s (Exception: %s)",
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
index 9c8305a..e5f6f56 100644
--- 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
@@ -15,7 +15,6 @@
 package com.google.gerrit.pgm.init;
 
 import com.google.common.collect.Lists;
-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;
@@ -25,18 +24,15 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Enumeration;
 import java.util.List;
 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 static final String PLUGIN_DIR = "WEB-INF/plugins/";
-  private static final String JAR = ".jar";
+  public static final String PLUGIN_DIR = "WEB-INF/plugins/";
+  public static final String JAR = ".jar";
 
   public static class PluginData {
     public final String name;
@@ -50,46 +46,31 @@
     }
   }
 
-  public static List<PluginData> listPlugins(SitePaths site) throws IOException {
-    return listPlugins(site, false);
+  public static List<PluginData> listPlugins(SitePaths site,
+      PluginsDistribution pluginsDistribution) throws IOException {
+    return listPlugins(site, false, pluginsDistribution);
   }
 
-  public static List<PluginData> listPluginsAndRemoveTempFiles(SitePaths site) throws IOException {
-    return listPlugins(site, true);
+  public static List<PluginData> listPluginsAndRemoveTempFiles(SitePaths site,
+      PluginsDistribution pluginsDistribution) throws IOException {
+    return listPlugins(site, true, pluginsDistribution);
   }
 
-  private static List<PluginData> listPlugins(SitePaths site, boolean deleteTempPluginFile) throws IOException {
-    final File myWar = GerritLauncher.getDistributionArchive();
+  private static List<PluginData> listPlugins(final SitePaths site,
+      final boolean deleteTempPluginFile, PluginsDistribution pluginsDistribution)
+          throws IOException {
     final List<PluginData> result = Lists.newArrayList();
-    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)) {
-            final String pluginJarName = new File(ze.getName()).getName();
-            final String pluginName = pluginJarName.substring(0,  pluginJarName.length() - JAR.length());
-            final InputStream in = zf.getInputStream(ze);
-            final File tmpPlugin = PluginLoader.storeInTemp(pluginName, in, site);
-            final String pluginVersion = getVersion(tmpPlugin);
-            if (deleteTempPluginFile) {
-              tmpPlugin.delete();
-            }
-
-            result.add(new PluginData(pluginName, pluginVersion, tmpPlugin));
-          }
+    pluginsDistribution.foreach(new PluginsDistribution.Processor() {
+      @Override
+      public void process(String pluginName, InputStream in) throws IOException {
+        File tmpPlugin = PluginLoader.storeInTemp(pluginName, in, site);
+        String pluginVersion = getVersion(tmpPlugin);
+        if (deleteTempPluginFile) {
+          tmpPlugin.delete();
         }
-      } finally {
-        zf.close();
+        result.add(new PluginData(pluginName, pluginVersion, tmpPlugin));
       }
-    } catch (IOException e) {
-      throw new IOException("Failure during plugin installation", e);
-    }
+    });
     return result;
   }
 
@@ -97,14 +78,17 @@
   private final SitePaths site;
   private final InitFlags initFlags;
   private final InitPluginStepsLoader pluginLoader;
+  private final PluginsDistribution pluginsDistribution;
 
   @Inject
   InitPlugins(final ConsoleUI ui, final SitePaths site,
-      InitFlags initFlags, InitPluginStepsLoader pluginLoader) {
+      InitFlags initFlags, InitPluginStepsLoader pluginLoader,
+      PluginsDistribution pluginsDistribution) {
     this.ui = ui;
     this.site = site;
     this.initFlags = initFlags;
     this.pluginLoader = pluginLoader;
+    this.pluginsDistribution = pluginsDistribution;
   }
 
   @Override
@@ -115,8 +99,13 @@
     initPlugins();
   }
 
+  @Override
+  public void postRun() throws Exception {
+    postInitPlugins();
+  }
+
   private void installPlugins() throws IOException {
-    List<PluginData> plugins = listPlugins(site);
+    List<PluginData> plugins = listPlugins(site, pluginsDistribution);
     for (PluginData plugin : plugins) {
       String pluginName = plugin.name;
       try {
@@ -164,6 +153,12 @@
     }
   }
 
+  private void postInitPlugins() throws Exception {
+    for (InitStep initStep : pluginLoader.getInitSteps()) {
+      initStep.postRun();
+    }
+  }
+
   private static String getVersion(final File plugin) throws IOException {
     final JarFile jarFile = new JarFile(plugin);
     try {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
index e4b827d1..ae621ae 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
@@ -59,4 +59,8 @@
     sendemail.string("SMTP username", "smtpUser", username);
     sendemail.password("smtpUser", "smtpPass");
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index 0c2a3c6..66c2dd0b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -72,9 +72,9 @@
     sshd.set("listenAddress", SocketUtil.format(hostname, port));
 
     if (site.ssh_rsa.exists() || site.ssh_dsa.exists()) {
-      libraries.bouncyCastle.downloadRequired();
+      libraries.bouncyCastleSSL.downloadRequired();
     } else if (!site.ssh_key.exists()) {
-      libraries.bouncyCastle.downloadOptional();
+      libraries.bouncyCastleSSL.downloadOptional();
     }
 
     generateSshHostKeys();
@@ -152,4 +152,8 @@
       System.err.println(" done");
     }
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java
index 4fa3f90..5a9a334 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitStep.java
@@ -17,4 +17,7 @@
 /** A single step in the site initialization process. */
 public interface InitStep {
   public void run() throws Exception;
+
+  /** Executed after the site has been initialized */
+  public void postRun() throws Exception;
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
index 0ad7560..dde0b06 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitUtil.java
@@ -162,10 +162,11 @@
 
   static void extract(final File dst, final Class<?> sibling,
       final String name) throws IOException {
-    final InputStream in = open(sibling, name);
-    if (in != null) {
-      ByteBuffer buf = IO.readWholeStream(in, 8192);
-      copy(dst, buf);
+    try (InputStream in = open(sibling, name)) {
+      if (in != null) {
+        ByteBuffer buf = IO.readWholeStream(in, 8192);
+        copy(dst, buf);
+      }
     }
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
index a03144b..7209990 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
@@ -37,7 +37,8 @@
 
   private final Provider<LibraryDownloader> downloadProvider;
 
-  /* final */LibraryDownloader bouncyCastle;
+  /* final */LibraryDownloader bouncyCastleProvider;
+  /* final */LibraryDownloader bouncyCastleSSL;
   /* final */LibraryDownloader mysqlDriver;
   /* final */LibraryDownloader oracleDriver;
 
@@ -58,29 +59,41 @@
       throw new RuntimeException(e.getMessage(), e);
     }
 
-    for (final Field f : Libraries.class.getDeclaredFields()) {
+    for (Field f : Libraries.class.getDeclaredFields()) {
+      if ((f.getModifiers() & Modifier.STATIC) == 0
+          && f.getType() == LibraryDownloader.class) {
+        try {
+          f.set(this, downloadProvider.get());
+        } catch (IllegalArgumentException | IllegalAccessException e) {
+          throw new IllegalStateException("Cannot initialize " + f.getName());
+        }
+      }
+    }
+
+    for (Field f : Libraries.class.getDeclaredFields()) {
       if ((f.getModifiers() & Modifier.STATIC) == 0
           && f.getType() == LibraryDownloader.class) {
         try {
           init(f, cfg);
-        } catch (IllegalArgumentException e) {
-          throw new IllegalStateException("Cannot initialize " + f.getName());
-        } catch (IllegalAccessException e) {
-          throw new IllegalStateException("Cannot initialize " + f.getName());
+        } catch (IllegalArgumentException | IllegalAccessException
+            | NoSuchFieldException | SecurityException e) {
+          throw new IllegalStateException("Cannot configure " + f.getName());
         }
       }
     }
   }
 
-  private void init(final Field field, final Config cfg)
-      throws IllegalArgumentException, IllegalAccessException {
-    final String n = field.getName();
-    final LibraryDownloader dl = downloadProvider.get();
+  private void init(Field field, Config cfg) throws IllegalArgumentException,
+      IllegalAccessException, NoSuchFieldException, SecurityException {
+    String n = field.getName();
+    LibraryDownloader dl = (LibraryDownloader) field.get(this);
     dl.setName(get(cfg, n, "name"));
     dl.setJarUrl(get(cfg, n, "url"));
     dl.setSHA1(get(cfg, n, "sha1"));
     dl.setRemove(get(cfg, n, "remove"));
-    field.set(this, dl);
+    for (String d : cfg.getStringList("library", n, "needs")) {
+      dl.addNeeds((LibraryDownloader) getClass().getDeclaredField(d).get(this));
+    }
   }
 
   private static String get(Config cfg, String name, String key) {
@@ -93,21 +106,19 @@
   }
 
   private static String read(final String p) throws IOException {
-    InputStream in = Libraries.class.getClassLoader().getResourceAsStream(p);
-    if (in == null) {
-      throw new FileNotFoundException("Cannot load resource " + p);
-    }
-    final Reader r = new InputStreamReader(in, "UTF-8");
-    try {
-      final StringBuilder buf = new StringBuilder();
-      final char[] tmp = new char[512];
-      int n;
-      while (0 < (n = r.read(tmp))) {
-        buf.append(tmp, 0, n);
+    try (InputStream in = Libraries.class.getClassLoader().getResourceAsStream(p)) {
+      if (in == null) {
+        throw new FileNotFoundException("Cannot load resource " + p);
       }
-      return buf.toString();
-    } finally {
-      r.close();
+      try (Reader r = new InputStreamReader(in, "UTF-8")) {
+        final StringBuilder buf = new StringBuilder();
+        final char[] tmp = new char[512];
+        int n;
+        while (0 < (n = r.read(tmp))) {
+          buf.append(tmp, 0, n);
+        }
+        return buf.toString();
+      }
     }
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
index 7caf49a..9555abd 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
@@ -40,6 +40,8 @@
 import java.net.URL;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
 
 /** Get optional or required 3rd party library files into $site_path/lib. */
 class LibraryDownloader {
@@ -51,13 +53,17 @@
   private String jarUrl;
   private String sha1;
   private String remove;
+  private List<LibraryDownloader> needs;
+  private LibraryDownloader neededBy;
   private File dst;
   private boolean download; // download or copy
+  private boolean exists;
 
   @Inject
   LibraryDownloader(ConsoleUI ui, SitePaths site) {
     this.ui = ui;
     this.lib_dir = site.lib_dir;
+    this.needs = new ArrayList<>(2);
   }
 
   void setName(final String name) {
@@ -77,16 +83,27 @@
     this.remove = remove;
   }
 
+  void addNeeds(LibraryDownloader lib) {
+    needs.add(lib);
+  }
+
   void downloadRequired() {
-    this.required = true;
+    setRequired(true);
     download();
   }
 
   void downloadOptional() {
-    this.required = false;
+    required = false;
     download();
   }
 
+  private void setRequired(boolean r) {
+    required = r;
+    for (LibraryDownloader d : needs) {
+      d.setRequired(r);
+    }
+  }
+
   private void download() {
     if (jarUrl == null || !jarUrl.contains("/")) {
       throw new IllegalStateException("Invalid JarUrl for " + name);
@@ -102,9 +119,18 @@
     }
 
     dst = new File(lib_dir, jarName);
-    if (!dst.exists() && shouldGet()) {
+    if (dst.exists()) {
+      exists = true;
+    } else if (shouldGet()) {
       doGet();
     }
+
+    if (exists) {
+      for (LibraryDownloader d : needs) {
+        d.neededBy = this;
+        d.downloadRequired();
+      }
+    }
   }
 
   private boolean shouldGet() {
@@ -115,7 +141,11 @@
       final StringBuilder msg = new StringBuilder();
       msg.append("\n");
       msg.append("Gerrit Code Review is not shipped with %s\n");
-      if (required) {
+      if (neededBy != null) {
+        msg.append(String.format(
+            "** This library is required by %s. **\n",
+            neededBy.name));
+      } else if (required) {
         msg.append("**  This library is required for your configuration. **\n");
       } else {
         msg.append("  If available, Gerrit can take advantage of features\n");
@@ -171,6 +201,7 @@
     }
 
     if (dst.exists()) {
+      exists = true;
       IoUtil.loadJARs(dst);
     }
   }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java
new file mode 100644
index 0000000..6b7386d
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PluginsDistribution.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * Represents the plugins packaged in the Gerrit distribution
+ */
+public interface PluginsDistribution {
+
+  public interface Processor {
+    /**
+     * @param pluginName the name of the plugin (without the .jar extension)
+     * @param in the content of the plugin .jar file. Implementors don't have to
+     *        close this stream.
+     * @throws IOException implementations will typically propagate any
+     *         IOException caused by dealing with the InputStream back to the
+     *         caller
+     */
+    public void process(String pluginName, InputStream in) throws IOException;
+  }
+
+  /**
+   * Iterate over plugins package in the Gerrit distribution
+   *
+   * @param processor invoke for each plugin via its process method
+   * @throws FileNotFoundException if the location of the plugins couldn't be
+   *         determined
+   * @throws IOException in case of any other IO error caused by reading the
+   *         plugin input stream
+   */
+  public void foreach(Processor processor) throws FileNotFoundException, IOException;
+
+  /**
+   * List plugins included in the Gerrit distribution
+   * @return list of plugins names included in the Gerrit distribution
+   * @throws FileNotFoundException if the location of the plugins couldn't be
+   *         determined
+   */
+  public List<String> listPluginNames() throws FileNotFoundException;
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
index 26a422e..bbb1d9f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Section.java
@@ -190,6 +190,6 @@
     if (a == null && b == null) {
       return true;
     }
-    return a != null ? a.equals(b) : false;
+    return a != null && a.equals(b);
   }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 71a4d86..82daf81 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -94,7 +94,6 @@
     extractMailExample("ChangeSubject.vm");
     extractMailExample("Comment.vm");
     extractMailExample("CommentFooter.vm");
-    extractMailExample("CommitMessageEdited.vm");
     extractMailExample("Footer.vm");
     extractMailExample("Merged.vm");
     extractMailExample("MergeFail.vm");
@@ -109,6 +108,16 @@
     }
   }
 
+  public void postRun() throws Exception {
+    for (InitStep step : steps) {
+      if (step instanceof InitPlugins
+          && flags.skipPlugins) {
+        continue;
+      }
+      step.postRun();
+    }
+  }
+
   private void extractMailExample(String orig) throws Exception {
     File ex = new File(site.mail_dir, orig + ".example");
     extract(ex, OutgoingEmail.class, orig);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
index b982ae1..97be0c5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/UpgradeFrom2_0_x.java
@@ -286,4 +286,8 @@
       return null;
     }
   }
+
+  @Override
+  public void postRun() throws Exception {
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java
new file mode 100644
index 0000000..38f08c1
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/shell/JythonShell.java
@@ -0,0 +1,226 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.shell;
+
+import com.google.gerrit.launcher.GerritLauncher;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Properties;
+
+public class JythonShell {
+  private static final Logger log = LoggerFactory.getLogger(JythonShell.class);
+  private static final String STARTUP_RESOURCE = "com/google/gerrit/pgm/Startup.py";
+  private static final String STARTUP_FILE = "Startup.py";
+
+  private Class<?> console;
+  private Class<?> pyObject;
+  private Class<?> pySystemState;
+  private Object shell;
+  private ArrayList <String> injectedVariables;
+
+  public JythonShell() {
+    Properties env = new Properties();
+    // Let us inspect private class members
+    env.setProperty("python.security.respectJavaAccessibility", "false");
+
+    File home = GerritLauncher.getHomeDirectory();
+    if (home != null) {
+      env.setProperty("python.cachedir", new File(home, "jythoncache").getPath());
+    }
+
+    // For package introspection and "import com.google" to work,
+    // Jython needs to inspect actual .jar files (not just classloader)
+    StringBuilder classPath = new StringBuilder();
+    final ClassLoader cl = getClass().getClassLoader();
+    if (cl instanceof java.net.URLClassLoader) {
+      @SuppressWarnings("resource")
+      URLClassLoader ucl = (URLClassLoader) cl;
+      for (URL u : ucl.getURLs()) {
+        if ("file".equals(u.getProtocol())) {
+          if (classPath.length() > 0) {
+            classPath.append(java.io.File.pathSeparatorChar);
+          }
+          classPath.append(u.getFile());
+        }
+      }
+    }
+    env.setProperty("java.class.path", classPath.toString());
+
+    console = findClass("org.python.util.InteractiveConsole");
+    pyObject = findClass("org.python.core.PyObject");
+    pySystemState = findClass("org.python.core.PySystemState");
+
+    runMethod(pySystemState, pySystemState, "initialize",
+      new Class[]  { Properties.class, Properties.class },
+      new Object[] { null, env }
+    );
+
+    try {
+      shell = console.newInstance();
+      log.info("Jython shell instance created.");
+    } catch (InstantiationException e) {
+      throw noInterpreter(e);
+    } catch (IllegalAccessException e) {
+      throw noInterpreter(e);
+    }
+    injectedVariables = new ArrayList<String>();
+    set("Shell", this);
+  }
+
+  protected Object runMethod0(Class<?> klazz, Object instance,
+    String name, Class<?>[] sig, Object[] args)
+      throws InvocationTargetException {
+    try {
+      Method m;
+      m = klazz.getMethod(name, sig);
+      return m.invoke(instance, args);
+    } catch (NoSuchMethodException e) {
+      throw cannotStart(e);
+    } catch (SecurityException e) {
+      throw cannotStart(e);
+    } catch (IllegalArgumentException e) {
+      throw cannotStart(e);
+    } catch (IllegalAccessException e) {
+      throw cannotStart(e);
+    }
+  }
+
+  protected Object runMethod(Class<?> klazz, Object instance,
+    String name, Class<?>[] sig, Object[] args) {
+    try {
+      return runMethod0(klazz, instance, name, sig, args);
+    } catch (InvocationTargetException e) {
+      throw cannotStart(e);
+    }
+  }
+
+  protected Object runInterpreter(String name, Class<?>[] sig, Object[] args) {
+    return runMethod(console, shell, name, sig, args);
+  }
+
+  protected String getDefaultBanner() {
+    return (String)runInterpreter("getDefaultBanner",
+                  new Class[] { }, new Object[] { });
+  }
+
+  protected void printInjectedVariable(String id) {
+    runInterpreter("exec",
+      new Class[]  { String.class },
+      new Object[] { "print '\"%s\" is \"%s\"' % (\"" + id + "\", " + id + ")" }
+    );
+  }
+
+  public void run() {
+    for (String key : injectedVariables) {
+      printInjectedVariable(key);
+    }
+    reload();
+    runInterpreter("interact",
+      new Class[]  { String.class, pyObject },
+      new Object[] { getDefaultBanner() +
+        " running for Gerrit " + com.google.gerrit.common.Version.getVersion(),
+        null });
+  }
+
+  public void set(String key, Object content) {
+    runInterpreter("set",
+      new Class[]  { String.class, Object.class },
+      new Object[] { key, content }
+    );
+    injectedVariables.add(key);
+  }
+
+  private static Class<?> findClass(String klazzname) {
+    try {
+      return Class.forName(klazzname);
+    } catch (ClassNotFoundException e) {
+      throw noShell("Class " + klazzname + " not found", e);
+    }
+  }
+
+  public void reload() {
+    execResource(STARTUP_RESOURCE);
+    execFile(GerritLauncher.getHomeDirectory(), STARTUP_FILE);
+  }
+
+  protected void execResource(final String p) {
+    try (InputStream in = JythonShell.class.getClassLoader().getResourceAsStream(p)) {
+      if (in != null) {
+        execStream(in, "resource " + p);
+      } else {
+        log.error("Cannot load resource " + p);
+      }
+    } catch (IOException e) {
+      log.error(e.getMessage(), e);
+    }
+  }
+
+  protected void execFile(final File parent, final String p) {
+    try {
+      File script = new File(parent, p);
+      if (script.canExecute()) {
+        runMethod0(console, shell, "execfile",
+          new Class[] { String.class },
+          new Object[] { script.getAbsolutePath() }
+        );
+      } else {
+        log.info("User initialization file "
+          + script.getAbsolutePath()
+          + " is not found or not executable");
+      }
+    } catch (InvocationTargetException e) {
+      log.error("Exception occured while loading file " + p + " : ", e);
+    } catch (SecurityException e) {
+      log.error("SecurityException occured while loading file " + p + " : ", e);
+    }
+  }
+
+  protected void execStream(final InputStream in, final String p) {
+    try {
+      runMethod0(console, shell, "execfile",
+        new Class[] { InputStream.class, String.class },
+        new Object[] { in, p }
+      );
+    } catch (InvocationTargetException e) {
+      log.error("Exception occured while loading " + p + " : ", e);
+    }
+  }
+
+  private static UnsupportedOperationException noShell(final String m, Throwable why) {
+    final String prefix = "Cannot create Jython shell: ";
+    final String postfix = "\n     (You might need to install jython.jar in the lib directory)";
+    return new UnsupportedOperationException(prefix + m + postfix, why);
+  }
+
+  private static UnsupportedOperationException noInterpreter(Throwable why) {
+    final String msg = "Cannot create Python interpreter";
+    return noShell(msg, why);
+  }
+
+  private static UnsupportedOperationException cannotStart(Throwable why) {
+    final String msg = "Cannot start Jython shell";
+    return new UnsupportedOperationException(msg, why);
+  }
+}
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 e8cf0ab..9af95ab 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
@@ -179,7 +179,7 @@
         }
         console.printf("       Supported options are:\n");
         for (final String v : allowedValues) {
-          console.printf("         %s\n", v.toString().toLowerCase());
+          console.printf("         %s\n", v.toLowerCase());
         }
       }
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
index 126ef98..da7704d 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ErrorLogFile.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.pgm.util;
 
-import com.google.common.base.Strings;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.util.LogUtil;
 
 import org.apache.log4j.Appender;
 import org.apache.log4j.ConsoleAppender;
@@ -34,8 +34,6 @@
 import java.io.IOException;
 
 public class ErrorLogFile {
-  @SuppressWarnings("deprecation")
-  private static final String LOG4J_CONFIGURATION = LogManager.DEFAULT_CONFIGURATION_KEY;
   static final String LOG_NAME = "error_log";
 
   public static void errorOnlyConsole() {
@@ -60,7 +58,7 @@
     if (!logdir.exists() && !logdir.mkdirs()) {
       throw new Die("Cannot create log directory: " + logdir);
     }
-    if (shouldConfigureLogSystem()) {
+    if (LogUtil.shouldConfigureLogSystem()) {
       initLogSystem(logdir);
     }
 
@@ -76,10 +74,6 @@
     };
   }
 
-  public static boolean shouldConfigureLogSystem() {
-    return Strings.isNullOrEmpty(System.getProperty(LOG4J_CONFIGURATION));
-  }
-
   private static void initLogSystem(final File logdir) {
     final PatternLayout layout = new PatternLayout();
     layout.setConversionPattern("[%d] %-5p %c %x: %m%n");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
index b34bb21..746355b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GarbageCollectionLogFile.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GarbageCollection;
+import com.google.gerrit.server.util.LogUtil;
 
 import org.apache.log4j.Appender;
 import org.apache.log4j.DailyRollingFileAppender;
@@ -42,7 +43,7 @@
     if (!logdir.exists() && !logdir.mkdirs()) {
       throw new Die("Cannot create log directory: " + logdir);
     }
-    if (ErrorLogFile.shouldConfigureLogSystem()) {
+    if (LogUtil.shouldConfigureLogSystem()) {
       initLogSystem(logdir);
     }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GuiceLogger.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GuiceLogger.java
new file mode 100644
index 0000000..d807af6
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/GuiceLogger.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.util;
+
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.StreamHandler;
+
+public class GuiceLogger {
+  private static final Handler HANDLER;
+  static {
+    HANDLER = new StreamHandler(System.out, new Formatter() {
+      public String format(LogRecord record) {
+        return String.format("[Guice %s] %s%n", record.getLevel().getName(),
+            record.getMessage());
+      }
+    });
+    HANDLER.setLevel(Level.ALL);
+  }
+
+  private GuiceLogger() {
+  }
+
+  public static Logger getLogger() {
+    return Logger.getLogger("com.google.inject");
+  }
+
+  public static void enable() {
+    Logger guiceLogger = getLogger();
+    guiceLogger.addHandler(GuiceLogger.HANDLER);
+    guiceLogger.setLevel(Level.ALL);
+  }
+
+  public static void disable() {
+    Logger guiceLogger = getLogger();
+    guiceLogger.setLevel(Level.OFF);
+    guiceLogger.removeHandler(GuiceLogger.HANDLER);
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
index e28af7c..d9a64fc 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/IoUtil.java
@@ -57,15 +57,15 @@
     if (!(cl instanceof URLClassLoader)) {
       throw noAddURL("Not loaded by URLClassLoader", null);
     }
+
+    @SuppressWarnings("resource") // Leave open so classes can be loaded.
     URLClassLoader urlClassLoader = (URLClassLoader) cl;
 
     Method addURL;
     try {
       addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
       addURL.setAccessible(true);
-    } catch (SecurityException e) {
-      throw noAddURL("Method addURL not available", e);
-    } catch (NoSuchMethodException e) {
+    } catch (SecurityException | NoSuchMethodException e) {
       throw noAddURL("Method addURL not available", e);
     }
 
@@ -76,11 +76,8 @@
         if (have.add(url)) {
           addURL.invoke(cl, url);
         }
-      } catch (MalformedURLException e) {
-        throw noAddURL("addURL " + path + " failed", e);
-      } catch (IllegalArgumentException e) {
-        throw noAddURL("addURL " + path + " failed", e);
-      } catch (IllegalAccessException e) {
+      } catch (MalformedURLException | IllegalArgumentException |
+          IllegalAccessException e) {
         throw noAddURL("addURL " + path + " failed", e);
       } catch (InvocationTargetException e) {
         throw noAddURL("addURL " + path + " failed", e.getCause());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java
index cda653e..aed1b9a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/LogFileCompressor.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GarbageCollection;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
 
@@ -97,11 +96,9 @@
 
   private boolean isLive(final File entry) {
     final String name = entry.getName();
-    return ErrorLogFile.LOG_NAME.equals(name) //
-        || GarbageCollection.LOG_NAME.equals(name) //
-        || "sshd_log".equals(name) //
-        || "httpd_log".equals(name) //
-        || "gerrit.run".equals(name) //
+    return name.endsWith("_log")
+        || name.endsWith(".log")
+        || name.endsWith(".run")
         || name.endsWith(".pid");
   }
 
@@ -121,21 +118,15 @@
     final File dst = new File(dir, src.getName() + ".gz");
     final File tmp = new File(dir, ".tmp." + src.getName());
     try {
-      final InputStream in = new FileInputStream(src);
-      try {
-        OutputStream out = new GZIPOutputStream(new FileOutputStream(tmp));
-        try {
-          final byte[] buf = new byte[2048];
-          int n;
-          while (0 < (n = in.read(buf))) {
-            out.write(buf, 0, n);
-          }
-        } finally {
-          out.close();
+      try (InputStream in = new FileInputStream(src);
+          FileOutputStream fo = new FileOutputStream(tmp);
+          OutputStream out = new GZIPOutputStream(fo)) {
+        final byte[] buf = new byte[2048];
+        int n;
+        while (0 < (n = in.read(buf))) {
+          out.write(buf, 0, n);
         }
         tmp.setReadOnly();
-      } finally {
-        in.close();
       }
       if (!tmp.renameTo(dst)) {
         throw new IOException("Cannot rename " + tmp + " to " + dst);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ProxyUtil.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ProxyUtil.java
index cce15bf..c2a52ec 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ProxyUtil.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/ProxyUtil.java
@@ -44,15 +44,15 @@
 
 final class ProxyUtil {
   /**
-   * Configure the JRE's standard HTTP based on <code>http_proxy</code>.
+   * Configure the JRE's standard HTTP based on {@code http_proxy}.
    * <p>
-   * The popular libcurl library honors the <code>http_proxy</code> environment
+   * The popular libcurl library honors the {@code http_proxy} environment
    * variable as a means of specifying an HTTP proxy for requests made behind a
    * firewall. This is not natively recognized by the JRE, so this method can be
    * used by command line utilities to configure the JRE before the first
    * request is sent.
    *
-   * @throws MalformedURLException the value in <code>http_proxy</code> is
+   * @throws MalformedURLException the value in {@code http_proxy} is
    *         unsupportable.
    */
   static void configureHttpProxy() throws MalformedURLException {
@@ -61,7 +61,7 @@
       return;
     }
 
-    final URL u = new URL((s.indexOf("://") == -1) ? "http://" + s : s);
+    final URL u = new URL((!s.contains("://")) ? "http://" + s : s);
     if (!"http".equals(u.getProtocol())) {
       throw new MalformedURLException("Invalid http_proxy: " + s
           + ": Only http supported.");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
index 6ab7395..0614b53 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteLibraryBasedDataSourceProvider.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.pgm.util;
 
-import com.google.common.primitives.Longs;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.schema.DataSourceProvider;
@@ -68,7 +67,7 @@
         @Override
         public int compare(File a, File b) {
           // Sort by reverse last-modified time so newer JARs are first.
-          int cmp = Longs.compare(b.lastModified(), a.lastModified());
+          int cmp = Long.compare(b.lastModified(), a.lastModified());
           if (cmp != 0) {
             return cmp;
           }
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/Startup.py b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/Startup.py
new file mode 100644
index 0000000..92d6e56
--- /dev/null
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/Startup.py
@@ -0,0 +1,31 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -----------------------------------------------------------------------
+# Startup script for Gerrit Inspector - a Jython introspector
+# -----------------------------------------------------------------------
+
+import sys
+
+def help():
+  for (n, v) in vars(sys.modules['__main__']).items():
+    if not n.startswith("__") and not n in ['help', 'reload'] \
+       and str(type(v)) != "<type 'javapackage'>"             \
+       and not str(v).startswith("<module"):
+       print "\"%s\" is \"%s\"" % (n, v)
+  print
+  print "Welcome to the Gerrit Inspector"
+  print "Enter help() to see the above again, EOF to quit and stop Gerrit"
+
+help()
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
index 6f80364..b5e702f 100644
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
@@ -14,12 +14,20 @@
 
 
 # Version should match lib/bouncycastle/BUCK
-[library "bouncyCastle"]
-  name = Bouncy Castle Crypto v144
-  url = http://www.bouncycastle.org/download/bcprov-jdk16-144.jar
-  sha1 = 6327a5f7a3dc45e0fd735adb5d08c5a74c05c20c
+[library "bouncyCastleProvider"]
+  name = Bouncy Castle Crypto Provider v149
+  url = http://www.bouncycastle.org/download/bcprov-jdk15on-149.jar
+  sha1 = f5155f04330459104b79923274db5060c1057b99
   remove = bcprov-.*[.]jar
 
+# Version should match lib/bouncycastle/BUCK
+[library "bouncyCastleSSL"]
+  name = Bouncy Castle Crypto SSL v149
+  url = http://www.bouncycastle.org/download/bcpkix-jdk15on-149.jar
+  sha1 = 924cc7ad2f589630c97b918f044296ebf1bb6855
+  needs = bouncyCastleProvider
+  remove = bcpkix-.*[.]jar
+
 [library "mysqlDriver"]
   name = MySQL Connector/J 5.1.21
   url = http://repo2.maven.org/maven2/mysql/mysql-connector-java/5.1.21/mysql-connector-java-5.1.21.jar
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
index df1f447..37eeda5 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
@@ -21,13 +21,14 @@
 import com.google.gerrit.pgm.util.ConsoleUI;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Provider;
-
-import junit.framework.TestCase;
+import org.junit.Test;
+import static org.junit.Assert.assertNotNull;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 
-public class LibrariesTest extends TestCase {
+public class LibrariesTest {
+  @Test
   public void testCreate() throws FileNotFoundException {
     final SitePaths site = new SitePaths(new File("."));
     final ConsoleUI ui = createStrictMock(ConsoleUI.class);
@@ -41,7 +42,7 @@
       }
     });
 
-    assertNotNull(lib.bouncyCastle);
+    assertNotNull(lib.bouncyCastleProvider);
     assertNotNull(lib.mysqlDriver);
 
     verify(ui);
diff --git a/gerrit-plugin-api/.gitignore b/gerrit-plugin-api/.gitignore
deleted file mode 100644
index e87ebb8..0000000
--- a/gerrit-plugin-api/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/.settings/org.eclipse.core.resources.prefs
-/.settings/org.eclipse.jdt.core.prefs
-/gerrit-plugin-api.iml
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK
new file mode 100644
index 0000000..d808e0f
--- /dev/null
+++ b/gerrit-plugin-api/BUCK
@@ -0,0 +1,62 @@
+SRCS = [
+  'gerrit-server/src/main/java/',
+  'gerrit-httpd/src/main/java/',
+  'gerrit-sshd/src/main/java/',
+]
+
+PLUGIN_API = [
+  '//gerrit-httpd:httpd',
+  '//gerrit-pgm:init-api',
+  '//gerrit-server:server',
+  '//gerrit-sshd:sshd',
+]
+
+java_binary(
+  name = 'plugin-api',
+  deps = [':lib'],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'lib',
+  exported_deps = PLUGIN_API + [
+    '//gerrit-common:annotations',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-reviewdb:server',
+    '//lib:args4j',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:jsch',
+    '//lib:servlet-api-3_1',
+    '//lib/guice:guice',
+    '//lib/guice:guice-assistedinject',
+    '//lib/guice:guice-servlet',
+    '//lib/jgit:jgit',
+    '//lib/log:api',
+    '//lib/mina:sshd',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_binary(
+  name = 'plugin-api-src',
+  deps = [
+    '//gerrit-extension-api:extension-api-src',
+  ] + [d + '-src' for d in PLUGIN_API],
+  visibility = ['PUBLIC'],
+)
+
+java_doc(
+  name = 'plugin-api-javadoc',
+  title = 'Gerrit Review Plugin API Documentation',
+  pkg = 'com.google.gerrit',
+  paths = [n for n in SRCS],
+  srcs = glob([n + '**/*.java' for n in SRCS]),
+  deps = [
+    ':plugin-api',
+    '//lib/bouncycastle:bcprov',
+    '//lib/bouncycastle:bcpg',
+  ],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-plugin-archetype/.gitignore b/gerrit-plugin-archetype/.gitignore
index 80d6257..7075a2f 100644
--- a/gerrit-plugin-archetype/.gitignore
+++ b/gerrit-plugin-archetype/.gitignore
@@ -1,5 +1,4 @@
 /target
 /.classpath
 /.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
+/.settings
diff --git a/gerrit-plugin-archetype/.settings/org.eclipse.core.resources.prefs b/gerrit-plugin-archetype/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index abdea9ac..0000000
--- a/gerrit-plugin-archetype/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/main/resources=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-plugin-archetype/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-archetype/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 470942d..0000000
--- a/gerrit-plugin-archetype/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-plugin-archetype/pom.xml b/gerrit-plugin-archetype/pom.xml
index 80afa56..401f126 100644
--- a/gerrit-plugin-archetype/pom.xml
+++ b/gerrit-plugin-archetype/pom.xml
@@ -20,7 +20,7 @@
 
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-archetype</artifactId>
-  <version>2.8.4</version>
+  <version>2.9-SNAPSHOT</version>
   <name>Gerrit Code Review - Plugin Archetype</name>
 
   <properties>
diff --git a/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index ce8fa1a..21f508b 100644
--- a/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -28,8 +28,12 @@
       <defaultValue>Y</defaultValue>
     </requiredProperty>
 
-    <requiredProperty key="Implementation-Vendor"/>
-    <requiredProperty key="Implementation-Url"/>
+    <requiredProperty key="Implementation-Vendor">
+      <defaultValue>Gerrit Code Review</defaultValue>
+    </requiredProperty>
+    <requiredProperty key="Implementation-Url">
+      <defaultValue>http://code.google.com/p/gerrit/</defaultValue>
+    </requiredProperty>
 
     <requiredProperty key="gerritApiType">
       <defaultValue>plugin</defaultValue>
@@ -58,6 +62,7 @@
       <directory></directory>
       <includes>
         <include>.gitignore</include>
+        <include>.settings/*</include>
         <include>LICENSE</include>
       </includes>
     </fileSet>
diff --git a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.resources.prefs
similarity index 80%
rename from gerrit-httpd/.settings/org.eclipse.core.resources.prefs
rename to gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.resources.prefs
index 839d647..29abf99 100644
--- a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.resources.prefs
@@ -2,4 +2,5 @@
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
 encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-antlr/.settings/org.eclipse.core.runtime.prefs b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
similarity index 100%
rename from gerrit-antlr/.settings/org.eclipse.core.runtime.prefs
rename to gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
diff --git a/gerrit-antlr/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
similarity index 89%
rename from gerrit-antlr/.settings/org.eclipse.jdt.core.prefs
rename to gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
index 134e606..2a585e4 100644
--- a/gerrit-antlr/.settings/org.eclipse.jdt.core.prefs
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
@@ -1,54 +1,72 @@
-#Thu May 19 09:55:53 PDT 2011
 eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
 org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
 org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=ignore
-org.eclipse.jdt.core.compiler.problem.deadCode=ignore
-org.eclipse.jdt.core.compiler.problem.deprecation=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
 org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
 org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=ignore
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
 org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
 org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
 org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
 org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=ignore
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
 org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=ignore
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=ignore
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
 org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
 org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
 org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
 org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
 org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
 org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
 org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
 org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nullReference=ignore
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=ignore
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
 org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
 org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
 org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
 org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
 org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
 org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=ignore
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
 org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
 org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
 org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=ignore
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
@@ -56,18 +74,18 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedImport=ignore
-org.eclipse.jdt.core.compiler.problem.unusedLabel=ignore
-org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
 org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
 org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
 org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=ignore
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=ignore
-org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.7
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/gerrit-antlr/.settings/org.eclipse.jdt.ui.prefs b/gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.ui.prefs
similarity index 100%
rename from gerrit-antlr/.settings/org.eclipse.jdt.ui.prefs
rename to gerrit-plugin-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.ui.prefs
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
index cbf8a52..8060182 100644
--- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2012 The Android Open Source Project
+Copyright (C) 2014 The Android Open Source Project
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -67,8 +67,8 @@
         <artifactId>maven-compiler-plugin</artifactId>
         <version>2.3.2</version>
         <configuration>
-          <source>1.6</source>
-          <target>1.6</target>
+          <source>1.7</source>
+          <target>1.7</target>
           <encoding>UTF-8</encoding>
         </configuration>
       </plugin>
@@ -82,13 +82,6 @@
       <version>${Gerrit-ApiVersion}</version>
       <scope>provided</scope>
     </dependency>
-
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>4.8.1</version>
-      <scope>test</scope>
-    </dependency>
   </dependencies>
 
   <repositories>
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
index 2840112..d7955a6 100644
--- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/Module.java b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
index 0d28349..dfe0c4f 100644
--- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/SshModule.java b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/SshModule.java
index aa15ca5..bf9a531 100644
--- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/SshModule.java
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/src/main/java/SshModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
diff --git a/gerrit-plugin-gwt-archetype/pom.xml b/gerrit-plugin-gwt-archetype/pom.xml
index 7a49b56..56e6c95 100644
--- a/gerrit-plugin-gwt-archetype/pom.xml
+++ b/gerrit-plugin-gwt-archetype/pom.xml
@@ -20,7 +20,7 @@
 
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-gwt-archetype</artifactId>
-  <version>2.8.4</version>
+  <version>2.9-SNAPSHOT</version>
   <name>Gerrit Code Review - Web Ui GWT Plugin Archetype</name>
 
   <properties>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index 78f2941..f619f91 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -18,9 +18,15 @@
   <requiredProperties>
     <requiredProperty key="pluginName"/>
 
-    <requiredProperty key="Implementation-Vendor"/>
-    <requiredProperty key="Implementation-Url"/>
-    <requiredProperty key="Gwt-Version"/>
+    <requiredProperty key="Implementation-Vendor">
+      <defaultValue>Gerrit Code Review</defaultValue>
+    </requiredProperty>
+    <requiredProperty key="Implementation-Url">
+      <defaultValue>http://code.google.com/p/gerrit/</defaultValue>
+    </requiredProperty>
+    <requiredProperty key="Gwt-Version">
+      <defaultValue>2.5.1</defaultValue>
+    </requiredProperty>
 
     <requiredProperty key="gerritApiVersion">
       <defaultValue>${defaultGerritApiVersion}</defaultValue>
@@ -49,6 +55,7 @@
       <directory></directory>
       <includes>
         <include>.gitignore</include>
+        <include>.settings/*</include>
         <include>LICENSE</include>
       </includes>
     </fileSet>
diff --git a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.resources.prefs
similarity index 80%
copy from gerrit-httpd/.settings/org.eclipse.core.resources.prefs
copy to gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.resources.prefs
index 839d647..29abf99 100644
--- a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.resources.prefs
@@ -2,4 +2,5 @@
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
 encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-patch-commonsnet/.settings/org.eclipse.core.runtime.prefs b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
similarity index 100%
rename from gerrit-patch-commonsnet/.settings/org.eclipse.core.runtime.prefs
rename to gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
diff --git a/gerrit-antlr/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
similarity index 89%
copy from gerrit-antlr/.settings/org.eclipse.jdt.core.prefs
copy to gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
index 134e606..2a585e4 100644
--- a/gerrit-antlr/.settings/org.eclipse.jdt.core.prefs
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
@@ -1,54 +1,72 @@
-#Thu May 19 09:55:53 PDT 2011
 eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
 org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
 org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=ignore
-org.eclipse.jdt.core.compiler.problem.deadCode=ignore
-org.eclipse.jdt.core.compiler.problem.deprecation=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
 org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
 org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=ignore
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
 org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
 org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
 org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
 org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=ignore
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
 org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=ignore
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=ignore
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
 org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
 org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
 org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
 org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
 org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
 org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
 org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
 org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nullReference=ignore
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=ignore
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
 org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
 org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
 org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
 org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
 org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
 org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=ignore
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
 org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
 org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
 org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=ignore
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
@@ -56,18 +74,18 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedImport=ignore
-org.eclipse.jdt.core.compiler.problem.unusedLabel=ignore
-org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
 org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
 org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
 org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=ignore
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=ignore
-org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.7
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/gerrit-patch-commonsnet/.settings/org.eclipse.jdt.ui.prefs b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.ui.prefs
similarity index 100%
rename from gerrit-patch-commonsnet/.settings/org.eclipse.jdt.ui.prefs
rename to gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.ui.prefs
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
index 3a28c48..cc9c981 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2012 The Android Open Source Project
+Copyright (C) 2014 The Android Open Source Project
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@
   <name>${pluginName}</name>
 
   <properties>
-    <Gerrit-ApiType>extension</Gerrit-ApiType>
+    <Gerrit-ApiType>plugin</Gerrit-ApiType>
     <Gerrit-ApiVersion>${gerritApiVersion}</Gerrit-ApiVersion>
   </properties>
 
@@ -42,6 +42,8 @@
           <archive>
             <manifestEntries>
               <Gerrit-PluginName>${pluginName}</Gerrit-PluginName>
+              <Gerrit-Module>${package}.Module</Gerrit-Module>
+              <Gerrit-HttpModule>${package}.HttpModule</Gerrit-HttpModule>
               <Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
               <Implementation-URL>${Implementation-Url}</Implementation-URL>
 
@@ -60,8 +62,8 @@
         <artifactId>maven-compiler-plugin</artifactId>
         <version>2.3.2</version>
         <configuration>
-          <source>1.6</source>
-          <target>1.6</target>
+          <source>1.7</source>
+          <target>1.7</target>
           <encoding>UTF-8</encoding>
         </configuration>
       </plugin>
@@ -71,7 +73,7 @@
         <artifactId>gwt-maven-plugin</artifactId>
         <version>${Gwt-Version}</version>
         <configuration>
-          <module>${package}.HelloPlugins</module>
+          <module>${package}.HelloPlugin</module>
           <disableClassMetadata>true</disableClassMetadata>
           <disableCastChecking>true</disableCastChecking>
           <webappDirectory>${project.build.directory}/classes/static</webappDirectory>
@@ -108,13 +110,6 @@
       <version>${Gwt-Version}</version>
       <scope>provided</scope>
     </dependency>
-
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>4.8.1</version>
-      <scope>test</scope>
-    </dependency>
   </dependencies>
 
   <repositories>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloMenu.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloMenu.java
new file mode 100644
index 0000000..0baa044
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloMenu.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ${package};
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.webui.TopMenu;
+import com.google.inject.Inject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class HelloMenu implements TopMenu {
+  private final List<MenuEntry> menuEntries;
+
+  @Inject
+  public HelloMenu(@PluginName String pluginName) {
+    menuEntries = new ArrayList<TopMenu.MenuEntry>();
+    menuEntries.add(new MenuEntry("Hello", Collections
+        .singletonList(new MenuItem("Hello Screen", "#/x/" + pluginName, ""))));
+  }
+
+  @Override
+  public List<MenuEntry> getEntries() {
+    return menuEntries;
+  }
+}
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloPlugins.gwt.xml b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloPlugin.gwt.xml
similarity index 88%
rename from gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloPlugins.gwt.xml
rename to gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloPlugin.gwt.xml
index 4c70fcc..a05527d 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloPlugins.gwt.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HelloPlugin.gwt.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
- Copyright (C) 2012 The Android Open Source Project
+ Copyright (C) 2014 The Android Open Source Project
 
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  See the License for the specific language governing permissions and
  limitations under the License.
 -->
-<module rename-to="hello_gwt_plugins">
+<module rename-to="hello_gwt_plugin">
   <!-- Inherit the core Web Toolkit stuff.                        -->
   <inherits name="com.google.gwt.user.User"/>
   <!-- Other module inherits                                      -->
@@ -24,6 +24,6 @@
   <!-- resources to the plugin. No theme inherits lines were      -->
   <!-- added in order to make this plugin as simple as possible   -->
   <!-- Specify the app entry point class.                         -->
-  <entry-point class="${package}.client.HelloPlugins"/>
+  <entry-point class="${package}.client.HelloPlugin"/>
   <stylesheet src="hello.css"/>
 </module>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
new file mode 100644
index 0000000..c3fbfbe
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/HttpModule.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ${package};
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.GwtPlugin;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
+import com.google.gerrit.httpd.plugins.HttpPluginModule;
+
+public class HttpModule extends HttpPluginModule {
+
+  @Override
+  protected void configureServlets() {
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new GwtPlugin("hello_gwt_plugin"));
+  }
+}
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/MyExtension.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
similarity index 61%
copy from gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/MyExtension.java
copy to gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
index ebdbb26..c5320d2 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/MyExtension.java
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/Module.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Google
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,12 +14,14 @@
 
 package ${package};
 
-import com.google.gerrit.extensions.webui.GwtPlugin;
-import com.google.gerrit.extensions.annotations.Listen;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.TopMenu;
+import com.google.inject.AbstractModule;
 
-@Listen
-public class MyExtension extends GwtPlugin {
-  public MyExtension() {
-    super("hello_gwt_plugins");
+public class Module extends AbstractModule {
+
+  @Override
+  protected void configure() {
+    DynamicSet.bind(binder(), TopMenu.class).to(HelloMenu.class);
   }
 }
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugin.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugin.java
new file mode 100644
index 0000000..62e87ff
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugin.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ${package}.client;
+
+import com.google.gerrit.plugin.client.Plugin;
+import com.google.gerrit.plugin.client.PluginEntryPoint;
+
+import ${package}.client.HelloScreen;
+
+/**
+ * HelloWorld Plugin.
+ */
+public class HelloPlugin extends PluginEntryPoint {
+
+  @Override
+  public void onPluginLoad() {
+    Plugin.get().screen("", new HelloScreen.Factory());
+  }
+}
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugins.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugins.java
deleted file mode 100644
index 5584d85..0000000
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloPlugins.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (C) 2012 Google Inc
-//
-// 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 ${package}.client;
-
-import com.google.gerrit.client.Plugin;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.DialogBox;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.VerticalPanel;
-
-/**
- * HelloWorld Plugins.
- */
-public class HelloPlugins extends Plugin {
-
-  @Override
-  public void onModuleLoad() {
-    Image img = new Image("http://code.google.com/webtoolkit/logo-185x175.png");
-    Button button = new Button("Click me");
-
-    VerticalPanel vPanel = new VerticalPanel();
-    vPanel.setWidth("100%");
-    vPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
-    vPanel.add(img);
-    vPanel.add(button);
-
-    RootPanel.get().add(vPanel);
-
-    // Create the dialog box
-    final DialogBox dialogBox = new DialogBox();
-
-    // The content of the dialog comes from a User specified Preference
-    dialogBox.setText("Hello from GWT Gerrit UI plugin");
-    dialogBox.setAnimationEnabled(true);
-    Button closeButton = new Button("Close");
-    VerticalPanel dialogVPanel = new VerticalPanel();
-    dialogVPanel.setWidth("100%");
-    dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
-    dialogVPanel.add(closeButton);
-
-    closeButton.addClickHandler(new ClickHandler() {
-      public void onClick(ClickEvent event) {
-        dialogBox.hide();
-      }
-    });
-
-    // Set the contents of the Widget
-    dialogBox.setWidget(dialogVPanel);
-
-    button.addClickHandler(new ClickHandler() {
-      public void onClick(ClickEvent event) {
-        dialogBox.center();
-        dialogBox.show();
-      }
-    });
-  }
-}
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloScreen.java b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloScreen.java
new file mode 100644
index 0000000..d067a96
--- /dev/null
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/client/HelloScreen.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ${package}.client;
+
+import com.google.gerrit.plugin.client.screen.Screen;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+public class HelloScreen extends VerticalPanel {
+
+  static class Factory implements Screen.EntryPoint {
+    @Override
+    public void onLoad(Screen screen) {
+      screen.setPageTitle("Hello");
+      screen.show(new HelloScreen());
+    }
+  }
+
+  HelloScreen() {
+    setStyleName("hello-panel");
+    add(new Label("Hello World Screen"));
+  }
+}
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle-thumb.png b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle-thumb.png
deleted file mode 100644
index e970774..0000000
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle-thumb.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle.png b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle.png
deleted file mode 100644
index c041149..0000000
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/gwt-hello-gadgets-igoogle.png
+++ /dev/null
Binary files differ
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/hello.css b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/hello.css
index 73bf5c6..72cf023 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/hello.css
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/public/hello.css
@@ -1,103 +1,3 @@
-/**
- * The file contains styles for a sample plugin derived from the
- * GWT standard theme.
- * Images in the stylesheet have been removed, as well as styles for widgets
- * not currently in use.
- */
-
-body, table td, select {
-  font-family: sans-serif;
-  font-size: small;
+.hello-panel {
+  border-spacing: 0px 5px;
 }
-pre {
-  font-family: "courier new", courier;
-  font-size: small;
-}
-body {
-  color: black;
-  margin: 0px;
-  border: 0px;
-  padding: 0px;
-  background: #fff;
-  direction: ltr;
-}
-a, a:visited, a:hover {
-  color: #0000AA;
-}
-
-/**
- * The reference theme can be used to determine when this style sheet has
- * loaded.  Create a hidden div element with absolute position, assign the style
- * name below, and attach it to the DOM.  Use a timer to detect when the
- * element's height and width are set to 5px.
- */
-.gwt-Reference-standard {
-  height: 5px;
-  width: 5px;
-  zoom: 1;
-}
-
-.gwt-Button {
-  margin: 0;
-  padding: 3px 5px;
-  text-decoration: none;
-  font-size: small;
-  cursor: pointer;
-  cursor: hand;
-  border: 1px outset #ccc;
-}
-.gwt-Button:active {
-  border: 1px inset #ccc;
-}
-.gwt-Button:hover {
-  border-color: #9cf #69e #69e #7af;
-}
-.gwt-Button[disabled] {
-  cursor: default;
-  color: #888;
-}
-.gwt-Button[disabled]:hover {
-  border: 1px outset #ccc;
-}
-
-.gwt-DialogBox .Caption {
-  background: #e3e8f3;
-  padding: 4px 4px 4px 8px;
-  cursor: default;
-  border-bottom: 1px solid #bbbbbb;
-  border-top: 5px solid #d0e4f6;
-  border-left: 5px solid #d0e4f6;
-  border-right: 5px solid #d0e4f6;
-}
-
-.gwt-DialogBox .dialogContent {
-}
-
-.gwt-DialogBox .dialogMiddleCenter {
-  padding: 3px;
-  background: white;
-  border-left: 5px solid #d0e4f6;
-  border-right: 5px solid #d0e4f6;
-  border-bottom: 5px solid #d0e4f6;
-}
-
-.gwt-DialogBox .dialogTopLeftInner {
-  width: 5px;
-  zoom: 1;
-}
-.gwt-DialogBox .dialogTopRightInner {
-  width: 8px;
-  zoom: 1;
-}
-.gwt-DialogBox .dialogBottomLeftInner {
-  width: 5px;
-  height: 8px;
-  zoom: 1;
-}
-
-.gwt-DialogBox .dialogBottomRightInner {
-  width: 5px;
-  height: 8px;
-  zoom: 1;
-}
-
diff --git a/gerrit-plugin-gwtui/.gitignore b/gerrit-plugin-gwtui/.gitignore
deleted file mode 100644
index 2dcf1ed..0000000
--- a/gerrit-plugin-gwtui/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-gwtui-plugin.iml
diff --git a/gerrit-plugin-gwtui/.settings/org.eclipse.core.resources.prefs b/gerrit-plugin-gwtui/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index 839d647..0000000
--- a/gerrit-plugin-gwtui/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,5 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/main/resources=UTF-8
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 955e208..0000000
--- a/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,295 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.codeComplete.argumentPrefixes=
-org.eclipse.jdt.core.codeComplete.argumentSuffixes=
-org.eclipse.jdt.core.codeComplete.fieldPrefixes=
-org.eclipse.jdt.core.codeComplete.fieldSuffixes=
-org.eclipse.jdt.core.codeComplete.localPrefixes=
-org.eclipse.jdt.core.codeComplete.localSuffixes=
-org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
-org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
-org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
-org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
-org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
-org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
-org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.join_lines_in_comments=true
-org.eclipse.jdt.core.formatter.join_wrapped_lines=true
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_on_off_tags=false
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
-org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
-org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.ui.prefs b/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index c018821..0000000
--- a/gerrit-plugin-gwtui/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,7 +0,0 @@
-eclipse.preferences.version=1
-formatter_profile=_Google Format
-formatter_settings_version=12
-org.eclipse.jdt.ui.exception.name=e
-org.eclipse.jdt.ui.gettersetter.use.is=true
-org.eclipse.jdt.ui.keywordthis=false
-org.eclipse.jdt.ui.overrideannotation=true
diff --git a/gerrit-plugin-gwtui/BUCK b/gerrit-plugin-gwtui/BUCK
index 58eda5c..8022bd1 100644
--- a/gerrit-plugin-gwtui/BUCK
+++ b/gerrit-plugin-gwtui/BUCK
@@ -1,20 +1,58 @@
+COMMON = ['gerrit-gwtui-common/src/main/java/']
 SRC = 'src/main/java/com/google/gerrit/'
+SRCS = glob([SRC + '**/*.java'])
 
-gwt_module(
-  name = 'client',
-  srcs = glob([SRC + '**/*.java']),
-  gwtxml = SRC + 'Plugin.gwt.xml',
-  resources = glob(['src/main/resources/**/*']),
+DEPS = [
+  '//lib/gwt:user',
+  '//lib/gwt:dev',
+]
+
+java_binary(
+  name = 'gwtui-api',
   deps = [
-    '//lib/gwt:user',
-    '//lib/gwt:dev',
+    ':gwtui-api-lib',
+    '//gerrit-gwtui-common:client-lib',
   ],
   visibility = ['PUBLIC'],
 )
 
 java_library(
-  name = 'src',
+  name = 'gwtui-api-lib',
+  exported_deps = [':gwtui-api-lib2'],
+  visibility = ['PUBLIC'],
+)
+
+java_library2(
+  name = 'gwtui-api-lib2',
+  srcs = SRCS,
+  resources = glob(['src/main/**/*']),
+  deps = ['//gerrit-gwtui-common:client-lib2'],
+  compile_deps = DEPS,
+  visibility = ['PUBLIC'],
+)
+
+java_binary(
+  name = 'gwtui-api-src',
+  deps = [
+    ':gwtui-api-src-lib',
+    '//gerrit-gwtui-common:client-src-lib',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+java_library(
+  name = 'gwtui-api-src-lib',
   srcs = [],
   resources = glob(['src/main/**/*']),
   visibility = ['PUBLIC'],
 )
+
+java_doc(
+  name = 'gwtui-api-javadoc',
+  title = 'Gerrit Review GWT Extension API Documentation',
+  pkg = 'com.google.gerrit',
+  paths = ['$SRCDIR/src/main/java'] + COMMON,
+  srcs = SRCS + glob(COMMON),
+  deps = DEPS + ['//gerrit-gwtui-common:client-src-lib'],
+  visibility = ['PUBLIC'],
+)
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
deleted file mode 100644
index 5e3b9ea..0000000
--- a/gerrit-plugin-gwtui/pom.xml
+++ /dev/null
@@ -1,161 +0,0 @@
-<?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-plugin-gwtui</artifactId>
-  <version>2.8.4</version>
-  <name>Gerrit Code Review - Plugin GWT UI</name>
-
-  <description>
-    API for UI plugins to build with GWT and integrate with Gerrit
-  </description>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-user</artifactId>
-      <version>2.5.1</version>
-    </dependency>
-
-    <dependency>
-      <groupId>com.google.gwt</groupId>
-      <artifactId>gwt-dev</artifactId>
-      <version>2.5.1</version>
-    </dependency>
-  </dependencies>
-
-   <build>
-     <pluginManagement>
-       <plugins>
-         <plugin>
-           <groupId>org.eclipse.m2e</groupId>
-           <artifactId>lifecycle-mapping</artifactId>
-           <version>1.0.0</version>
-           <configuration>
-             <lifecycleMappingMetadata>
-               <pluginExecutions>
-                 <pluginExecution>
-                   <pluginExecutionFilter>
-                     <groupId>org.codehaus.mojo</groupId>
-                     <artifactId>gwt-maven-plugin</artifactId>
-                     <versionRange>[2.5.0,)</versionRange>
-                     <goals>
-                       <goal>resources</goal>
-                       <goal>compile</goal>
-                       <goal>i18n</goal>
-                       <goal>generateAsync</goal>
-                     </goals>
-                   </pluginExecutionFilter>
-                   <action>
-                     <execute />
-                   </action>
-                 </pluginExecution>
-                 <pluginExecution>
-                   <pluginExecutionFilter>
-                     <groupId>org.apache.maven.plugins</groupId>
-                     <artifactId>maven-war-plugin</artifactId>
-                     <versionRange>[2.1.1,)</versionRange>
-                     <goals>
-                       <goal>exploded</goal>
-                     </goals>
-                   </pluginExecutionFilter>
-                   <action>
-                     <execute />
-                   </action>
-                 </pluginExecution>
-               </pluginExecutions>
-             </lifecycleMappingMetadata>
-          </configuration>
-        </plugin>
-      </plugins>
-    </pluginManagement>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>gwt-maven-plugin</artifactId>
-        <configuration>
-          <module>com.google.gerrit.Plugin</module>
-          <disableClassMetadata>true</disableClassMetadata>
-          <disableCastChecking>true</disableCastChecking>
-        </configuration>
-        <executions>
-          <execution>
-            <goals>
-              <goal>resources</goal>
-              <goal>compile</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-antrun-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-sources</id>
-            <phase>package</phase>
-            <configuration>
-              <tasks>
-                <unzip src="${project.build.directory}/${project.artifactId}-${project.version}-sources.jar" dest="${project.build.directory}/unpack_sources" />
-              </tasks>
-            </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-javadoc-plugin</artifactId>
-        <configuration>
-          <sourcepath>${project.build.directory}/unpack_sources</sourcepath>
-          <encoding>ISO-8859-1</encoding>
-          <quiet>true</quiet>
-          <detectOfflineLinks>false</detectOfflineLinks>
-        </configuration>
-        <executions>
-          <execution>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-            <phase>package</phase>
-          </execution>
-        </executions>
-      </plugin>
-
-    </plugins>
-  </build>
-</project>
-
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/Plugin.gwt.xml b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/Plugin.gwt.xml
index 03edf67..e0b0833 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/Plugin.gwt.xml
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/Plugin.gwt.xml
@@ -14,10 +14,13 @@
  limitations under the License.
 -->
 <module>
-  <define-linker name="gerrit_plugin" class="com.google.gerrit.linker.GerritPluginLinker"/>
+  <inherits name="com.google.gwt.json.JSON"/>
+  <inherits name='com.google.gerrit.GerritGwtUICommon'/>
+
+  <define-linker name="gerrit_plugin" class="com.google.gerrit.plugin.linker.GerritPluginLinker"/>
   <add-linker name="gerrit_plugin"/>
-  <generate-with class="com.google.gerrit.rebind.PluginGenerator">
-    <when-type-assignable class="com.google.gerrit.client.Plugin"/>
+  <generate-with class="com.google.gerrit.plugin.rebind.PluginGenerator">
+    <when-type-assignable class="com.google.gerrit.plugin.client.Plugin"/>
   </generate-with>
-  <source path="client"/>
+  <source path="plugin/client"/>
 </module>
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
new file mode 100644
index 0000000..046488d
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
@@ -0,0 +1,104 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugin.client;
+
+import com.google.gerrit.plugin.client.screen.Screen;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * Wrapper around the plugin instance exposed by Gerrit.
+ *
+ * Listeners for events generated by the main UI must be registered
+ * through this instance.
+ */
+public final class Plugin extends JavaScriptObject {
+  private static final Plugin self = install(
+      GWT.getModuleBaseURL() + GWT.getModuleName() + ".nocache.js");
+
+  /** Obtain the plugin instance wrapper. */
+  public static Plugin get() {
+    return self;
+  }
+
+  /** Installed name of the plugin. */
+  public final String getName() {
+    return getPluginName();
+  }
+
+  /** Installed name of the plugin. */
+  public final native String getPluginName()
+  /*-{ return this.getPluginName() }-*/;
+
+  /** Navigate the UI to the screen identified by the token. */
+  public final native void go(String token)
+  /*-{ return this.go(token) }-*/;
+
+  /** Refresh the current UI. */
+  public final native void refresh()
+  /*-{ return this.refresh() }-*/;
+
+  /** Refresh Gerrit's menu bar. */
+  public final native void refreshMenuBar()
+  /*-{ return this.refreshMenuBar() }-*/;
+
+  /** Show message in Gerrit's ErrorDialog. */
+  public final native void showError(String message)
+  /*-{ return this.showError(message) }-*/;
+
+  /**
+   * Register a screen displayed at {@code /#/x/plugin/token}.
+   *
+   * @param token literal anchor token appearing after the plugin name. For
+   *        regular expression matching use {@code screenRegex()} .
+   * @param entry callback function invoked to create the screen widgets.
+   */
+  public final void screen(String token, Screen.EntryPoint entry) {
+    screen(token, wrap(entry));
+  }
+
+  private final native void screen(String t, JavaScriptObject e)
+  /*-{ this.screen(t, e) }-*/;
+
+  /**
+   * Register a screen displayed at {@code /#/x/plugin/regex}.
+   *
+   * @param regex JavaScript {@code RegExp} expression to match the anchor token
+   *        after the plugin name. Matching groups are exposed through the
+   *        {@code Screen} object passed into the {@code Screen.EntryPoint}.
+   * @param entry callback function invoked to create the screen widgets.
+   */
+  public final void screenRegex(String regex, Screen.EntryPoint entry) {
+    screenRegex(regex, wrap(entry));
+  }
+
+  private final native void screenRegex(String p, JavaScriptObject e)
+  /*-{ this.screen(new $wnd.RegExp(p), e) }-*/;
+
+  protected Plugin() {
+  }
+
+  native void _initialized() /*-{ this._success = true }-*/;
+  native void _loaded() /*-{ this._loadedGwt() }-*/;
+  private static native final Plugin install(String u)
+  /*-{ return $wnd.Gerrit.installGwt(u) }-*/;
+
+  private static final native JavaScriptObject wrap(Screen.EntryPoint b) /*-{
+    return $entry(function(c){
+      b.@com.google.gerrit.plugin.client.screen.Screen.EntryPoint::onLoad(Lcom/google/gerrit/plugin/client/screen/Screen;)(
+        @com.google.gerrit.plugin.client.screen.Screen::new(Lcom/google/gerrit/plugin/client/screen/Screen$Context;)(c));
+    });
+  }-*/;
+}
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/client/Plugin.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java
similarity index 64%
rename from gerrit-plugin-gwtui/src/main/java/com/google/gerrit/client/Plugin.java
rename to gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java
index 1291b79..ca1cdbf 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/client/Plugin.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/PluginEntryPoint.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.client;
+package com.google.gerrit.plugin.client;
 
 import com.google.gwt.core.client.EntryPoint;
 
@@ -25,5 +25,20 @@
  * <li>Bind WebUiPlugin to GwtPlugin implementation in Gerrit-Module</li>
  * </ol>
  */
-public abstract class Plugin implements EntryPoint {
+public abstract class PluginEntryPoint implements EntryPoint {
+  /**
+   * The plugin entry point method, called automatically by loading
+   * a module that declares an implementing class as an entry point.
+   */
+  public abstract void onPluginLoad();
+
+  public final void onModuleLoad() {
+    Plugin self = Plugin.get();
+    try {
+      onPluginLoad();
+      self._initialized();
+    } finally {
+      self._loaded();
+    }
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/NoContent.java
similarity index 77%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
rename to gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/NoContent.java
index 11779f4..55744d5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/MembersInput.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/NoContent.java
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.plugin.client.rpc;
 
-import java.util.List;
+import com.google.gwt.core.client.JavaScriptObject;
 
-public class MembersInput {
-  List<String> members;
+public class NoContent extends JavaScriptObject {
+  protected NoContent() {
+  }
 }
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java
new file mode 100644
index 0000000..cfbc8a5
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/rpc/RestApi.java
@@ -0,0 +1,172 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugin.client.rpc;
+
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public class RestApi {
+  private final StringBuilder path;
+  private boolean hasQueryParams;
+
+  public RestApi(String name) {
+    path = new StringBuilder();
+    path.append(name);
+  }
+
+  public RestApi view(String name) {
+    return idRaw(name);
+  }
+
+  public RestApi view(String pluginName, String name) {
+    return idRaw(pluginName + "~" + name);
+  }
+
+  public RestApi id(String id) {
+    return idRaw(URL.encodeQueryString(id));
+  }
+
+  public RestApi id(int id) {
+    return idRaw(Integer.toString(id));
+  }
+
+  public RestApi idRaw(String name) {
+    if (hasQueryParams) {
+      throw new IllegalStateException();
+    }
+    if (path.charAt(path.length() - 1) != '/') {
+      path.append('/');
+    }
+    path.append(name);
+    return this;
+  }
+
+  public RestApi addParameter(String name, String value) {
+    return addParameterRaw(name, URL.encodeQueryString(value));
+  }
+
+  public RestApi addParameter(String name, String... value) {
+    for (String val : value) {
+      addParameter(name, val);
+    }
+    return this;
+  }
+
+  public RestApi addParameterTrue(String name) {
+    return addParameterRaw(name, null);
+  }
+
+  public RestApi addParameter(String name, boolean value) {
+    return addParameterRaw(name, value ? "t" : "f");
+  }
+
+  public RestApi addParameter(String name, int value) {
+    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) {
+      path.append("&");
+    } else {
+      path.append("?");
+      hasQueryParams = true;
+    }
+    path.append(name);
+    if (value != null) {
+      path.append("=").append(value);
+    }
+    return this;
+  }
+
+  public String path() {
+    return path.toString();
+  }
+
+  public <T extends JavaScriptObject>
+  void get(AsyncCallback<T> cb) {
+    get(path(), wrap(cb));
+  }
+
+  public void getString(AsyncCallback<String> cb) {
+    get(NativeString.unwrap(cb));
+  }
+
+  private native static void get(String p, JavaScriptObject r)
+  /*-{ $wnd.Gerrit.get(p, r) }-*/;
+
+  public <T extends JavaScriptObject>
+  void put(AsyncCallback<T> cb) {
+    put(path(), wrap(cb));
+  }
+
+  private native static void put(String p, JavaScriptObject r)
+  /*-{ $wnd.Gerrit.put(p, r) }-*/;
+
+  public <T extends JavaScriptObject>
+  void put(String content, AsyncCallback<T> cb) {
+    put(path(), content, wrap(cb));
+  }
+
+  private native static
+  void put(String p, String c, JavaScriptObject r)
+  /*-{ $wnd.Gerrit.put(p, c, r) }-*/;
+
+  public <T extends JavaScriptObject>
+  void put(JavaScriptObject content, AsyncCallback<T> cb) {
+    put(path(), content, wrap(cb));
+  }
+
+  private native static
+  void put(String p, JavaScriptObject c, JavaScriptObject r)
+  /*-{ $wnd.Gerrit.put(p, c, r) }-*/;
+
+  public <T extends JavaScriptObject>
+  void post(String content, AsyncCallback<T> cb) {
+    post(path(), content, wrap(cb));
+  }
+
+  private native static
+  void post(String p, String c, JavaScriptObject r)
+  /*-{ $wnd.Gerrit.post(p, c, r) }-*/;
+
+  public <T extends JavaScriptObject>
+  void post(JavaScriptObject content, AsyncCallback<T> cb) {
+    post(path(), content, wrap(cb));
+  }
+
+  private native static
+  void post(String p, JavaScriptObject c, JavaScriptObject r)
+  /*-{ $wnd.Gerrit.post(p, c, r) }-*/;
+
+  public void delete(AsyncCallback<NoContent> cb) {
+    delete(path(), wrap(cb));
+  }
+
+  private native static void delete(String p, JavaScriptObject r)
+  /*-{ $wnd.Gerrit.del(p, r) }-*/;
+
+  private native static <T extends JavaScriptObject>
+  JavaScriptObject wrap(AsyncCallback<T> b) /*-{
+    return function(r) {
+      b.@com.google.gwt.user.client.rpc.AsyncCallback::onSuccess(Ljava/lang/Object;)(r)
+    }
+  }-*/;
+}
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/screen/Screen.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/screen/Screen.java
new file mode 100644
index 0000000..2a872fd
--- /dev/null
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/screen/Screen.java
@@ -0,0 +1,140 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugin.client.screen;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Screen contributed by this plugin.
+ *
+ * Screens should be registered early at module load:
+ *
+ * <pre>
+ * &#064;Override
+ * public void onModuleLoad() {
+ *   Plugin.get().screen(&quot;hi&quot;, new Screen.EntryPoint() {
+ *     &#064;Override
+ *     public void onLoad(Screen screen) {
+ *       screen.setPageTitle(&quot;Hi&quot;);
+ *       screen.show(new Label(&quot;World&quot;));
+ *     }
+ *   });
+ * }
+ * </pre>
+ */
+public final class Screen extends SimplePanel {
+  /** Initializes a screen for display. */
+  public interface EntryPoint {
+    /**
+     * Invoked when the screen has been created, but not yet displayed.
+     * <p>
+     * The implementation should create a single widget to define the content of
+     * this screen and added it to the passed screen instance. When the screen
+     * is ready to be displayed, call {@link Screen#show()}.
+     * <p>
+     * To use multiple widgets, compose them in panels such as {@code FlowPanel}
+     * and add only the top level widget to the screen.
+     * <p>
+     * The screen is already attached to the browser DOM in an invisible area.
+     * Any widgets added to the screen will immediately receive {@code onLoad()}.
+     * GWT will fire {@code onUnload()} when the screen is removed from the UI,
+     * generally caused by the user navigating to another screen.
+     *
+     * @param screen panel that will contain the screen widget.
+     */
+    public void onLoad(Screen screen);
+  }
+
+  static final class Context extends JavaScriptObject {
+    final native Element body() /*-{ return this.body }-*/;
+    final native JsArrayString token_match() /*-{ return this.token_match }-*/;
+    final native void show() /*-{ this.show() }-*/;
+    final native void setTitle(String t) /*-{ this.setTitle(t) }-*/;
+    final native void setWindowTitle(String t) /*-{ this.setWindowTitle(t) }-*/;
+    final native void detach(Screen s) /*-{
+      this.onUnload($entry(function(){
+        s.@com.google.gwt.user.client.ui.Widget::onDetach()();
+      }));
+    }-*/;
+
+    protected Context() {
+    }
+  }
+
+  private final Context ctx;
+
+  Screen(Context ctx) {
+    super(ctx.body());
+    this.ctx = ctx;
+    onAttach();
+    ctx.detach(this);
+  }
+
+  /** @return the token suffix after {@code "/#/x/plugin-name/"}. */
+  public final String getToken() {
+    return getToken(0);
+  }
+
+  /**
+   * @param group groups range from 1 to {@code getTokenGroups() - 1}. Token
+   *        group 0 is the entire token, see {@link #getToken()}.
+   * @return the token from the regex match group.
+   */
+  public final String getToken(int group) {
+    return ctx.token_match().get(group);
+  }
+
+  /** @return total number of token groups. */
+  public final int getTokenGroups() {
+    return ctx.token_match().length();
+  }
+
+  /**
+   * Set the page title text; appears above the widget.
+   *
+   * @param titleText text to display above the widget.
+   */
+  public final void setPageTitle(String titleText) {
+    ctx.setTitle(titleText);
+  }
+
+  /**
+   * Set the window title text; appears in the browser window title bar.
+   *
+   * @param titleText text to display in the window title bar.
+   */
+  public final void setWindowTitle(String titleText) {
+    ctx.setWindowTitle(titleText);
+  }
+
+  /**
+   * Add the widget and immediately show the screen.
+   *
+   * @param w child containing the content.
+   */
+  public final void show(Widget w) {
+    setWidget(w);
+    ctx.show();
+  }
+
+  /** Show this screen in the web interface. */
+  public final void show() {
+    ctx.show();
+  }
+}
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/linker/GerritPluginLinker.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/linker/GerritPluginLinker.java
similarity index 96%
rename from gerrit-plugin-gwtui/src/main/java/com/google/gerrit/linker/GerritPluginLinker.java
rename to gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/linker/GerritPluginLinker.java
index e50334e..18f6e54 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/linker/GerritPluginLinker.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/linker/GerritPluginLinker.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.linker;
+package com.google.gerrit.plugin.linker;
 
 import com.google.gwt.core.ext.LinkerContext;
 import com.google.gwt.core.linker.CrossSiteIframeLinker;
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/rebind/PluginGenerator.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java
similarity index 98%
rename from gerrit-plugin-gwtui/src/main/java/com/google/gerrit/rebind/PluginGenerator.java
rename to gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java
index 37c3e96..8278280 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/rebind/PluginGenerator.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/rebind/PluginGenerator.java
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.rebind;
+package com.google.gerrit.plugin.rebind;
 
 import java.io.PrintWriter;
 
diff --git a/gerrit-plugin-js-archetype/pom.xml b/gerrit-plugin-js-archetype/pom.xml
index 14a6286..eb32b11 100644
--- a/gerrit-plugin-js-archetype/pom.xml
+++ b/gerrit-plugin-js-archetype/pom.xml
@@ -20,7 +20,7 @@
 
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-js-archetype</artifactId>
-  <version>2.8.4</version>
+  <version>2.9-SNAPSHOT</version>
   <name>Gerrit Code Review - Web UI JavaScript Plugin Archetype</name>
 
   <properties>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index 054caae..e4978dd 100644
--- a/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -18,8 +18,12 @@
   <requiredProperties>
     <requiredProperty key="pluginName"/>
 
-    <requiredProperty key="Implementation-Vendor"/>
-    <requiredProperty key="Implementation-Url"/>
+    <requiredProperty key="Implementation-Vendor">
+      <defaultValue>Gerrit Code Review</defaultValue>
+    </requiredProperty>
+    <requiredProperty key="Implementation-Url">
+      <defaultValue>http://code.google.com/p/gerrit/</defaultValue>
+    </requiredProperty>
 
     <requiredProperty key="gerritApiType">
       <defaultValue>js</defaultValue>
@@ -55,6 +59,7 @@
       <directory></directory>
       <includes>
         <include>.gitignore</include>
+        <include>.settings/*</include>
         <include>LICENSE</include>
       </includes>
     </fileSet>
diff --git a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.resources.prefs
similarity index 80%
copy from gerrit-httpd/.settings/org.eclipse.core.resources.prefs
copy to gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.resources.prefs
index 839d647..29abf99 100644
--- a/gerrit-httpd/.settings/org.eclipse.core.resources.prefs
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.resources.prefs
@@ -2,4 +2,5 @@
 encoding//src/main/java=UTF-8
 encoding//src/main/resources=UTF-8
 encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
 encoding/<project>=UTF-8
diff --git a/gerrit-patch-commonsnet/.settings/org.eclipse.core.runtime.prefs b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
similarity index 100%
copy from gerrit-patch-commonsnet/.settings/org.eclipse.core.runtime.prefs
copy to gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.core.runtime.prefs
diff --git a/gerrit-antlr/.settings/org.eclipse.jdt.core.prefs b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
similarity index 89%
copy from gerrit-antlr/.settings/org.eclipse.jdt.core.prefs
copy to gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
index 134e606..2a585e4 100644
--- a/gerrit-antlr/.settings/org.eclipse.jdt.core.prefs
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.core.prefs
@@ -1,54 +1,72 @@
-#Thu May 19 09:55:53 PDT 2011
 eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
 org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
 org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=ignore
-org.eclipse.jdt.core.compiler.problem.deadCode=ignore
-org.eclipse.jdt.core.compiler.problem.deprecation=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
 org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
 org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=ignore
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
 org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
 org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
 org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
 org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=ignore
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
 org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=ignore
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=ignore
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
 org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
 org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
 org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
 org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
 org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
 org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
 org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
 org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nullReference=ignore
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=ignore
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
 org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
 org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
 org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
 org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
 org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
 org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=ignore
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
 org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
 org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
 org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=ignore
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
 org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
 org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
 org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
@@ -56,18 +74,18 @@
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedImport=ignore
-org.eclipse.jdt.core.compiler.problem.unusedLabel=ignore
-org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
 org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
 org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
 org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
 org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=ignore
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=ignore
-org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.7
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/gerrit-patch-commonsnet/.settings/org.eclipse.jdt.ui.prefs b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.ui.prefs
similarity index 100%
copy from gerrit-patch-commonsnet/.settings/org.eclipse.jdt.ui.prefs
copy to gerrit-plugin-js-archetype/src/main/resources/archetype-resources/.settings/org.eclipse.jdt.ui.prefs
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
index 85de7b5..cc513c2 100644
--- a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2012 The Android Open Source Project
+Copyright (C) 2014 The Android Open Source Project
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -61,8 +61,8 @@
         <artifactId>maven-compiler-plugin</artifactId>
         <version>2.3.2</version>
         <configuration>
-          <source>1.6</source>
-          <target>1.6</target>
+          <source>1.7</source>
+          <target>1.7</target>
           <encoding>UTF-8</encoding>
         </configuration>
       </plugin>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/java/MyJsExtension.java b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/java/MyJsExtension.java
index bec914dd..bb4e0c5 100644
--- a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/java/MyJsExtension.java
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/src/main/java/MyJsExtension.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
diff --git a/gerrit-prettify/.gitignore b/gerrit-prettify/.gitignore
deleted file mode 100644
index 8cf95ef..0000000
--- a/gerrit-prettify/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-prettify.iml
\ No newline at end of file
diff --git a/gerrit-prettify/.settings/org.eclipse.core.resources.prefs b/gerrit-prettify/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index abdea9ac..0000000
--- a/gerrit-prettify/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/main/resources=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-prettify/.settings/org.eclipse.core.runtime.prefs b/gerrit-prettify/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-prettify/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-prettify/.settings/org.eclipse.jdt.core.prefs b/gerrit-prettify/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 5f73a7f..0000000
--- a/gerrit-prettify/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:35 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-prettify/.settings/org.eclipse.jdt.ui.prefs b/gerrit-prettify/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-prettify/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-prettify/BUCK b/gerrit-prettify/BUCK
index 79dc760..e753ad9 100644
--- a/gerrit-prettify/BUCK
+++ b/gerrit-prettify/BUCK
@@ -11,10 +11,12 @@
     'src/main/java/com/google/gerrit/prettify/client/*.properties',
   ]),
   deps = [
-    ':google-code-prettify',
     '//gerrit-patch-jgit:client',
     '//gerrit-reviewdb:client',
     '//gerrit-gwtexpui:SafeHtml',
+  ],
+  compile_deps = [
+    ':google-code-prettify',
     '//lib:guava',
     '//lib:gwtjsonrpc',
     '//lib/gwt:user',
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java
index df3cd0e..d4aef2a 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/client/PrettyFormatter.java
@@ -408,7 +408,7 @@
 
         String line = src.get(index) + "\n";
         for (int c = 0; c < line.length();) {
-          if (charEdits.size() <= lastIdx) {
+          if (charEdits == null || (charEdits.size() <= lastIdx)) {
             appendShowBareCR(buf, line.substring(c), false);
             break;
           }
diff --git a/gerrit-reviewdb/.gitignore b/gerrit-reviewdb/.gitignore
deleted file mode 100644
index 812ddd0..0000000
--- a/gerrit-reviewdb/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-reviewdb.iml
\ No newline at end of file
diff --git a/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs b/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index abdea9ac..0000000
--- a/gerrit-reviewdb/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/main/resources=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-reviewdb/.settings/org.eclipse.core.runtime.prefs b/gerrit-reviewdb/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-reviewdb/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-reviewdb/.settings/org.eclipse.jdt.core.prefs b/gerrit-reviewdb/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 5f73a7f..0000000
--- a/gerrit-reviewdb/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:35 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-reviewdb/.settings/org.eclipse.jdt.ui.prefs b/gerrit-reviewdb/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-reviewdb/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-reviewdb/BUCK b/gerrit-reviewdb/BUCK
index 05674cf..b133f1a 100644
--- a/gerrit-reviewdb/BUCK
+++ b/gerrit-reviewdb/BUCK
@@ -4,7 +4,7 @@
   name = 'client',
   srcs = glob([SRC + 'client/**/*.java']),
   gwtxml = SRC + 'ReviewDB.gwt.xml',
-  deps = [
+  compile_deps = [
     '//lib:gwtorm',
     '//lib:gwtorm_src'
   ],
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
index 5e71975..e131f7a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
@@ -56,7 +56,7 @@
  */
 public final class Account {
   public static enum FieldName {
-    FULL_NAME, USER_NAME, REGISTER_NEW_EMAIL;
+    FULL_NAME, USER_NAME, REGISTER_NEW_EMAIL
   }
 
   public static final String USER_NAME_PATTERN_FIRST = "[a-zA-Z0-9]";
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 afafd46..a19ae08 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
@@ -55,9 +55,33 @@
     }
   }
 
+  public static enum Theme {
+    // Light themes
+    DEFAULT,
+    ECLIPSE,
+    ELEGANT,
+    NEAT,
+    // Dark themes
+    MIDNIGHT,
+    NIGHT,
+    TWILIGHT;
+
+    public boolean isDark() {
+      switch (this) {
+        case MIDNIGHT:
+        case NIGHT:
+        case TWILIGHT:
+          return true;
+        default:
+          return false;
+      }
+    }
+  }
+
   public static AccountDiffPreference createDefault(Account.Id accountId) {
     AccountDiffPreference p = new AccountDiffPreference(accountId);
     p.setIgnoreWhitespace(Whitespace.IGNORE_NONE);
+    p.setTheme(Theme.DEFAULT);
     p.setTabSize(8);
     p.setLineLength(100);
     p.setSyntaxHighlighting(true);
@@ -116,6 +140,18 @@
   @Column(id = 15)
   protected boolean showLineEndings;
 
+  @Column(id = 16)
+  protected boolean hideTopMenu;
+
+  @Column(id = 17)
+  protected boolean hideLineNumbers;
+
+  @Column(id = 18)
+  protected boolean renderEntireFile;
+
+  @Column(id = 19, length = 20, notNull = false)
+  protected String theme;
+
   protected AccountDiffPreference() {
   }
 
@@ -139,6 +175,9 @@
     this.context = p.context;
     this.retainHeader = p.retainHeader;
     this.manualReview = p.manualReview;
+    this.hideTopMenu = p.hideTopMenu;
+    this.hideLineNumbers = p.hideLineNumbers;
+    this.renderEntireFile = p.renderEntireFile;
   }
 
   public Account.Id getAccountId() {
@@ -259,4 +298,36 @@
   public void setManualReview(boolean manual) {
     manualReview = manual;
   }
+
+  public boolean isHideTopMenu() {
+    return hideTopMenu;
+  }
+
+  public void setHideTopMenu(boolean hide) {
+    hideTopMenu = hide;
+  }
+
+  public boolean isHideLineNumbers() {
+    return hideLineNumbers;
+  }
+
+  public void setHideLineNumbers(boolean hide) {
+    hideLineNumbers = hide;
+  }
+
+  public boolean isRenderEntireFile() {
+    return renderEntireFile;
+  }
+
+  public void setRenderEntireFile(boolean render) {
+    renderEntireFile = render;
+  }
+
+  public Theme getTheme() {
+    return theme != null ? Theme.valueOf(theme) : null;
+  }
+
+  public void setTheme(Theme theme) {
+    this.theme = theme != null ? theme.name() : null;
+  }
 }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
index 0705284..4be5113 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
@@ -20,7 +20,7 @@
 /** Association of an external account identifier to a local {@link Account}. */
 public final class AccountExternalId {
   /**
-   * Scheme used for {@link AuthType#LDAP}, {@link AuthType#HTTP},
+   * Scheme used for {@link AuthType#LDAP}, {@link AuthType#CLIENT_SSL_CERT_LDAP},
    * {@link AuthType#HTTP_LDAP}, and {@link AuthType#LDAP_BIND} usernames.
    * <p>
    * The name {@code gerrit:} was a very poor choice.
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
index 6cc83e5..2257ea2 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
@@ -27,12 +27,12 @@
 
   /** Preferred scheme type to download a change. */
   public static enum DownloadScheme {
-    ANON_GIT, ANON_HTTP, HTTP, SSH, REPO_DOWNLOAD, DEFAULT_DOWNLOADS;
+    ANON_GIT, ANON_HTTP, HTTP, SSH, REPO_DOWNLOAD, DEFAULT_DOWNLOADS
   }
 
   /** Preferred method to download a change. */
   public static enum DownloadCommand {
-    REPO_DOWNLOAD, PULL, CHECKOUT, CHERRY_PICK, FORMAT_PATCH, DEFAULT_DOWNLOADS;
+    REPO_DOWNLOAD, PULL, CHECKOUT, CHERRY_PICK, FORMAT_PATCH, DEFAULT_DOWNLOADS
   }
 
   public static enum DateFormat {
@@ -46,7 +46,10 @@
     ISO("MM-dd", "yyyy-MM-dd"),
 
     /** European style dates: 27. Apr, 27.04.2010 */
-    EURO("d. MMM", "dd.MM.yyyy");
+    EURO("d. MMM", "dd.MM.yyyy"),
+
+    /** UK style dates: 27/04, 27/04/2010 */
+    UK("dd/MM", "dd/MM/yyyy");
 
     private final String shortFormat;
     private final String longFormat;
@@ -69,7 +72,7 @@
     COLLAPSE_ALL,
     EXPAND_MOST_RECENT,
     EXPAND_RECENT,
-    EXPAND_ALL;
+    EXPAND_ALL
   }
 
   public static enum DiffView {
@@ -152,6 +155,9 @@
   @Column(id = 15, length = 20, notNull = false)
   protected String changeScreen;
 
+  @Column(id = 16)
+  protected boolean sizeBarInChangeTable;
+
   public AccountGeneralPreferences() {
   }
 
@@ -294,6 +300,14 @@
     changeScreen = ui != null ? ui.name() : null;
   }
 
+  public boolean isSizeBarInChangeTable() {
+    return sizeBarInChangeTable;
+  }
+
+  public void setSizeBarInChangeTable(boolean sizeBarInChangeTable) {
+    this.sizeBarInChangeTable = sizeBarInChangeTable;
+  }
+
   public void resetToDefaults() {
     maximumPageSize = DEFAULT_PAGESIZE;
     showSiteHeader = true;
@@ -309,5 +323,6 @@
     commentVisibilityStrategy = null;
     diffView = null;
     changeScreen = null;
+    sizeBarInChangeTable = true;
   }
 }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
index ea9c52d..46f7873 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGroup.java
@@ -81,13 +81,7 @@
 
   /** @return true if the UUID is for a group managed within Gerrit. */
   public static boolean isInternalGroup(AccountGroup.UUID uuid) {
-    return uuid.get().startsWith("global:")
-        || uuid.get().matches("^[0-9a-f]{40}$");
-  }
-
-  /** @return true if the UUID is for a system group managed within Gerrit. */
-  public static boolean isSystemGroup(AccountGroup.UUID uuid) {
-    return uuid.get().startsWith("global:");
+    return uuid.get().matches("^[0-9a-f]{40}$");
   }
 
   /** Synthetic key to link to within the database */
@@ -122,41 +116,6 @@
     }
   }
 
-  public static enum Type {
-    /**
-     * System defined and managed group, e.g. anonymous users.
-     * <p>
-     * These groups must be explicitly named by {@link SystemConfig} and are
-     * specially handled throughout the code. In UI contexts their membership is
-     * not displayed. When computing effective group membership for any given
-     * user account, these groups are automatically handled using specialized
-     * branch conditions.
-     */
-    SYSTEM,
-
-    /**
-     * Group defined within our database.
-     * <p>
-     * An internal group has its membership fully enumerated in the database.
-     * The membership can be viewed and edited through the web UI by any user
-     * who is a member of the owner group. These groups are not treated special
-     * in the code.
-     */
-    INTERNAL;
-  }
-
-  /** Common UUID assigned to the "Project Owners" placeholder group. */
-  public static final AccountGroup.UUID PROJECT_OWNERS =
-      new AccountGroup.UUID("global:Project-Owners");
-
-  /** Common UUID assigned to the "Anonymous Users" group. */
-  public static final AccountGroup.UUID ANONYMOUS_USERS =
-      new AccountGroup.UUID("global:Anonymous-Users");
-
-  /** Common UUID assigned to the "Registered Users" group. */
-  public static final AccountGroup.UUID REGISTERED_USERS =
-      new AccountGroup.UUID("global:Registered-Users");
-
   /** Unique name of this group within the system. */
   @Column(id = 1)
   protected NameKey name;
@@ -169,10 +128,6 @@
   @Column(id = 4, length = Integer.MAX_VALUE, notNull = false)
   protected String description;
 
-  /** Is the membership managed by some external means? */
-  @Column(id = 5, length = 8)
-  protected String groupType;
-
   @Column(id = 7)
   protected boolean visibleToAll;
 
@@ -198,7 +153,6 @@
     visibleToAll = false;
     groupUUID = uuid;
     ownerGroupUUID = groupUUID;
-    setType(Type.INTERNAL);
   }
 
   public AccountGroup.Id getId() {
@@ -233,14 +187,6 @@
     ownerGroupUUID = uuid;
   }
 
-  public Type getType() {
-    return Type.valueOf(groupType);
-  }
-
-  public void setType(final Type t) {
-    groupType = t.name();
-  }
-
   public void setVisibleToAll(final boolean visibleToAll) {
     this.visibleToAll = visibleToAll;
   }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java
index b615fc5..6af9610 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AuthType.java
@@ -80,5 +80,5 @@
   CUSTOM_EXTENSION,
 
   /** Development mode to enable becoming anyone you want. */
-  DEVELOPMENT_BECOME_ANY_ACCOUNT;
+  DEVELOPMENT_BECOME_ANY_ACCOUNT
 }
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 8adba94..86f77db 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
@@ -400,6 +400,23 @@
     setLastSha1MergeTested(null);
   }
 
+  public Change(Change other) {
+    changeId = other.changeId;
+    changeKey = other.changeKey;
+    rowVersion = other.rowVersion;
+    createdOn = other.createdOn;
+    lastUpdatedOn = other.lastUpdatedOn;
+    sortKey = other.sortKey;
+    owner = other.owner;
+    dest = other.dest;
+    open = other.open;
+    status = other.status;
+    currentPatchSetId = other.currentPatchSetId;
+    subject = other.subject;
+    topic = other.topic;
+    mergeable = other.mergeable;
+  }
+
   /** Legacy 32 bit integer identity for a change. */
   public Change.Id getId() {
     return changeId;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
index 916219b..fc13a33 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchLineComment.java
@@ -128,9 +128,9 @@
     key = id;
     lineNbr = line;
     author = a;
-    this.parentUuid = parentUuid;
+    setParentUuid(parentUuid);
     setStatus(Status.DRAFT);
-    updated(when);
+    setWrittenOn(when);
   }
 
   public PatchLineComment.Key getKey() {
@@ -177,10 +177,6 @@
     message = s;
   }
 
-  public void updated(Timestamp when) {
-    writtenOn = when;
-  }
-
   public void setWrittenOn(Timestamp ts) {
     writtenOn = ts;
   }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
index 54c556d..613978a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSet.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.reviewdb.client;
 
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
+
 import com.google.gwtorm.client.Column;
 import com.google.gwtorm.client.IntKey;
 
@@ -21,8 +23,6 @@
 
 /** A single revision of a {@link Change}. */
 public final class PatchSet {
-  private static final String REFS_CHANGES = "refs/changes/";
-
   /** Is the reference name a change reference? */
   public static boolean isRef(final String name) {
     if (name == null || !name.startsWith(REFS_CHANGES)) {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
index 2c0f1f4..83a9b67 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
@@ -19,6 +19,7 @@
 import com.google.gwtorm.client.StringKey;
 
 import java.sql.Timestamp;
+import java.util.Objects;
 
 /** An approval (or negative approval) on a patch set. */
 public final class PatchSetApproval {
@@ -129,20 +130,11 @@
   @Column(id = 3)
   protected Timestamp granted;
 
-  /** <i>Cached copy of Change.open.</i> */
-  @Column(id = 4)
-  protected boolean changeOpen;
-
-  /** <i>Cached copy of Change.sortKey</i>; only if {@link #changeOpen} = false */
-  @Column(id = 5, length = 16, notNull = false)
-  protected String changeSortKey;
-
   protected PatchSetApproval() {
   }
 
   public PatchSetApproval(PatchSetApproval.Key k, short v, Timestamp ts) {
     key = k;
-    changeOpen = true;
     setValue(v);
     setGranted(ts);
   }
@@ -150,7 +142,6 @@
   public PatchSetApproval(final PatchSet.Id psId, final PatchSetApproval src) {
     key =
         new PatchSetApproval.Key(psId, src.getAccountId(), src.getLabelId());
-    changeOpen = true;
     value = src.getValue();
     granted = src.granted;
   }
@@ -187,11 +178,6 @@
     granted = ts;
   }
 
-  public void cache(final Change c) {
-    changeOpen = c.open;
-    changeSortKey = c.sortKey;
-  }
-
   public String getLabel() {
     return getLabelId().get();
   }
@@ -205,4 +191,20 @@
     return new StringBuilder().append('[').append(key).append(": ")
         .append(value).append(']').toString();
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o instanceof PatchSetApproval) {
+      PatchSetApproval p = (PatchSetApproval) o;
+      return Objects.equals(key, p.key)
+          && Objects.equals(value, p.value)
+          && Objects.equals(granted, p.granted);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(key, value, granted);
+  }
 }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
index f3cf471..46e9b22 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Project.java
@@ -74,7 +74,7 @@
 
     MERGE_ALWAYS,
 
-    CHERRY_PICK;
+    CHERRY_PICK
   }
 
   public static enum State {
@@ -82,13 +82,13 @@
 
     READ_ONLY,
 
-    HIDDEN;
+    HIDDEN
   }
 
   public static enum InheritableBoolean {
     TRUE,
     FALSE,
-    INHERIT;
+    INHERIT
   }
 
   protected NameKey name;
@@ -240,8 +240,8 @@
   /**
    * Returns the name key of the parent project.
    *
-   * @return name key of the parent project, <code>null</code> if this project
-   *         is the wild project, <code>null</code> or the name key of the wild
+   * @return name key of the parent project, {@code null} if this project
+   *         is the wild project, {@code null} or the name key of the wild
    *         project if this project is a direct child of the wild project
    */
   public Project.NameKey getParent() {
@@ -252,7 +252,7 @@
    * Returns the name key of the parent project.
    *
    * @param allProjectsName name key of the wild project
-   * @return name key of the parent project, <code>null</code> if this project
+   * @return name key of the parent project, {@code null} if this project
    *         is the wild project
    */
   public Project.NameKey getParent(final Project.NameKey allProjectsName) {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
new file mode 100644
index 0000000..ede5c26
--- /dev/null
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.reviewdb.client;
+
+/** Constants and utilities for Gerrit-specific ref names. */
+public class RefNames {
+  public static final String REFS_CHANGES = "refs/changes/";
+
+  /** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
+  public static final String REFS_REJECT_COMMITS = "refs/meta/reject-commits";
+
+  /** Configuration settings for a project {@code refs/meta/config} */
+  public static final String REFS_CONFIG = "refs/meta/config";
+
+  /** Configurations of project-specific dashboards (canned search queries). */
+  public static final String REFS_DASHBOARDS = "refs/meta/dashboards/";
+
+  /**
+   * Prefix applied to merge commit base nodes.
+   * <p>
+   * References in this directory should take the form
+   * {@code refs/cache-automerge/xx/yyyy...} where xx is
+   * the first two digits of the merge commit's object
+   * name, and yyyyy... is the remaining 38. The reference
+   * should point to a treeish that is the automatic merge
+   * result of the merge commit's parents.
+   */
+  public static final String REFS_CACHE_AUTOMERGE = "refs/cache-automerge/";
+
+  private RefNames() {
+  }
+}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RevId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RevId.java
index c3a535b..1a64ee7 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RevId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RevId.java
@@ -41,7 +41,7 @@
   }
 
   /**
-   * @return if {@link #isComplete()}, <code>this</code>; otherwise a new RevId
+   * @return if {@link #isComplete()}, {@code this}; otherwise a new RevId
    *         with 'z' appended on the end.
    */
   public RevId max() {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountAccess.java
index 29a426b..6603c17 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountAccess.java
@@ -46,4 +46,7 @@
 
   @Query("ORDER BY accountId LIMIT ?")
   ResultSet<Account> firstNById(int n) throws OrmException;
+
+  @Query("ORDER BY accountId")
+  ResultSet<Account> all() throws OrmException;
 }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java
index 9618bc3..54cdbfa 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.reviewdb.server;
 
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
@@ -42,14 +41,6 @@
   @Query("WHERE dest.projectName = ?")
   ResultSet<Change> byProject(Project.NameKey p) throws OrmException;
 
-  @Deprecated
-  @Query("WHERE owner = ? AND open = true ORDER BY createdOn, changeId")
-  ResultSet<Change> byOwnerOpen(Account.Id id) throws OrmException;
-
-  @Deprecated
-  @Query("WHERE owner = ? AND open = false ORDER BY lastUpdatedOn")
-  ResultSet<Change> byOwnerClosedAll(Account.Id id) throws OrmException;
-
   @Query("WHERE dest = ? AND status = '" + Change.STATUS_SUBMITTED
       + "' ORDER BY lastUpdatedOn")
   ResultSet<Change> submitted(Branch.NameKey dest) throws OrmException;
@@ -57,65 +48,17 @@
   @Query("WHERE status = '" + Change.STATUS_SUBMITTED + "'")
   ResultSet<Change> allSubmitted() throws OrmException;
 
-  @Deprecated
-  @Query("WHERE open = true AND sortKey > ? ORDER BY sortKey LIMIT ?")
-  ResultSet<Change> allOpenPrev(String sortKey, int limit) throws OrmException;
-
-  @Deprecated
-  @Query("WHERE open = true AND sortKey < ? ORDER BY sortKey DESC LIMIT ?")
-  ResultSet<Change> allOpenNext(String sortKey, int limit) throws OrmException;
-
   @Query("WHERE open = true AND dest.projectName = ?")
   ResultSet<Change> byProjectOpenAll(Project.NameKey p) throws OrmException;
 
   @Query("WHERE open = true AND dest = ?")
   ResultSet<Change> byBranchOpenAll(Branch.NameKey p) throws OrmException;
 
-  @Deprecated
-  @Query("WHERE open = true AND dest.projectName = ? AND sortKey > ?"
-      + " ORDER BY sortKey LIMIT ?")
-  ResultSet<Change> byProjectOpenPrev(Project.NameKey p, String sortKey,
-      int limit) throws OrmException;
-
   @Query("WHERE open = true AND dest.projectName = ? AND sortKey < ?"
       + " ORDER BY sortKey DESC LIMIT ?")
   ResultSet<Change> byProjectOpenNext(Project.NameKey p, String sortKey,
       int limit) throws OrmException;
 
-  @Deprecated
-  @Query("WHERE open = false AND status = ? AND dest.projectName = ? AND sortKey > ?"
-      + " ORDER BY sortKey LIMIT ?")
-  ResultSet<Change> byProjectClosedPrev(char status, Project.NameKey p,
-      String sortKey, int limit) throws OrmException;
-
-  @Deprecated
-  @Query("WHERE open = false AND status = ? AND dest.projectName = ? AND sortKey < ?"
-      + " ORDER BY sortKey DESC LIMIT ?")
-  ResultSet<Change> byProjectClosedNext(char status, Project.NameKey p,
-      String sortKey, int limit) throws OrmException;
-
-  @Deprecated
-  @Query("WHERE open = false AND status = ? AND sortKey > ? ORDER BY sortKey LIMIT ?")
-  ResultSet<Change> allClosedPrev(char status, String sortKey, int limit)
-      throws OrmException;
-
-  @Deprecated
-  @Query("WHERE open = false AND status = ? AND sortKey < ? ORDER BY sortKey DESC LIMIT ?")
-  ResultSet<Change> allClosedNext(char status, String sortKey, int limit)
-      throws OrmException;
-
-  @Deprecated
-  @Query("WHERE open = false AND status = ? AND dest = ? AND sortKey > ?"
-      + " ORDER BY sortKey LIMIT ?")
-  ResultSet<Change> byBranchClosedPrev(char status, Branch.NameKey p,
-      String sortKey, int limit) throws OrmException;
-
-  @Deprecated
-  @Query("WHERE open = false AND status = ? AND dest = ? AND sortKey < ?"
-      + " ORDER BY sortKey DESC LIMIT ?")
-  ResultSet<Change> byBranchClosedNext(char status, Branch.NameKey p,
-      String sortKey, int limit) throws OrmException;
-
   @Query
   ResultSet<Change> all() throws OrmException;
 }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetApprovalAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetApprovalAccess.java
index 25dfdbd..e90e05a 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetApprovalAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/PatchSetApprovalAccess.java
@@ -38,14 +38,4 @@
   @Query("WHERE key.patchSetId = ? AND key.accountId = ?")
   ResultSet<PatchSetApproval> byPatchSetUser(PatchSet.Id patchSet,
       Account.Id account) throws OrmException;
-
-  @Deprecated
-  @Query("WHERE changeOpen = true AND key.accountId = ?")
-  ResultSet<PatchSetApproval> openByUser(Account.Id account)
-      throws OrmException;
-
-  @Deprecated
-  @Query("WHERE changeOpen = false AND key.accountId = ? ORDER BY changeSortKey")
-  ResultSet<PatchSetApproval> closedByUserAll(Account.Id account)
-      throws OrmException;
 }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java
index 1a6f817..85f2b26 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDb.java
@@ -98,9 +98,6 @@
   @Relation(id = 26)
   PatchLineCommentAccess patchComments();
 
-  @Relation(id = 27)
-  TrackingIdAccess trackingIds();
-
   @Relation(id = 28)
   SubmoduleSubscriptionAccess submoduleSubscriptions();
 
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SubmoduleSubscriptionAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SubmoduleSubscriptionAccess.java
index 6dce287..8c67009 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SubmoduleSubscriptionAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/SubmoduleSubscriptionAccess.java
@@ -33,15 +33,15 @@
       throws OrmException;
 
   /**
-   * Fetches all <code>SubmoduleSubscription</code>s in which some branch of
-   * <code>superProject</code> subscribes a branch.
+   * Fetches all {@code SubmoduleSubscription}s in which some branch of
+   * {@code superProject} subscribes a branch.
    *
    * Use {@link #bySuperProject(Branch.NameKey)} to fetch for a branch instead
    * of a project.
    *
    * @param superProject the project to fetch subscriptions for
-   * @return <code>SubmoduleSubscription</code>s that are subscribed by some
-   * branch of <code>superProject</code>.
+   * @return {@code SubmoduleSubscription}s that are subscribed by some
+   * branch of {@code superProject}.
    * @throws OrmException
    */
   @Query("WHERE key.superProject.projectName = ?")
@@ -53,15 +53,15 @@
       throws OrmException;
 
   /**
-   * Fetches all <code>SubmoduleSubscription</code>s in which some branch of
-   * <code>submodule</code> is subscribed.
+   * Fetches all {@code SubmoduleSubscription}s in which some branch of
+   * {@code submodule} is subscribed.
    *
    * Use {@link #bySubmodule(Branch.NameKey)} to fetch for a branch instead of
    * a project.
    *
    * @param submodule the project to fetch subscriptions for.
-   * @return <code>SubmoduleSubscription</code>s that subscribe some branch of
-   * <code>submodule</code>.
+   * @return {@code SubmoduleSubscription}s that subscribe some branch of
+   * {@code submodule}.
    * @throws OrmException
    */
   @Query("WHERE submodule.projectName = ?")
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
deleted file mode 100644
index 9f2bae5..0000000
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/TrackingIdAccess.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2010 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.reviewdb.server;
-
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.TrackingId;
-import com.google.gwtorm.server.Access;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.PrimaryKey;
-import com.google.gwtorm.server.Query;
-import com.google.gwtorm.server.ResultSet;
-
-public interface TrackingIdAccess extends Access<TrackingId, TrackingId.Key> {
-  @PrimaryKey("key")
-  TrackingId get(TrackingId.Key key) throws OrmException;
-
-  @Query("WHERE key.changeId = ?")
-  ResultSet<TrackingId> byChange(Change.Id change) throws OrmException;
-
-  @Deprecated
-  @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 495f7b1..165c8be 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
@@ -69,23 +69,11 @@
 
 -- *********************************************************************
 -- ChangeAccess
---    covers:             byOwnerOpen
-CREATE INDEX changes_byOwnerOpen
-ON changes (open, owner_account_id, created_on, change_id);
-
---    covers:             byOwnerClosed
-CREATE INDEX changes_byOwnerClosed
-ON changes (open, owner_account_id, last_updated_on);
-
 --    covers:             submitted, allSubmitted
 CREATE INDEX changes_submitted
 ON changes (status, dest_project_name, dest_branch_name, last_updated_on);
 
---    covers:             allOpenPrev, allOpenNext
-CREATE INDEX changes_allOpen
-ON changes (open, sort_key);
-
---    covers:             byProjectOpenPrev, byProjectOpenNext
+--    covers:             byProjectOpenAll
 CREATE INDEX changes_byProjectOpen
 ON changes (open, dest_project_name, sort_key);
 
@@ -93,31 +81,11 @@
 CREATE INDEX changes_byProject
 ON changes (dest_project_name);
 
---    covers:             allClosedPrev, allClosedNext
-CREATE INDEX changes_allClosed
-ON changes (open, status, sort_key);
-
---    covers:             byBranchClosedPrev, byBranchClosedNext
-CREATE INDEX changes_byBranchClosed
-ON changes (status, dest_project_name, dest_branch_name, sort_key);
-
 CREATE INDEX changes_key
 ON changes (change_key);
 
 
 -- *********************************************************************
--- PatchSetApprovalAccess
---    @PrimaryKey covers: byPatchSet, byPatchSetUser
---    covers:             openByUser
-CREATE INDEX patch_set_approvals_openByUser
-ON patch_set_approvals (change_open, account_id);
-
---    covers:             closedByUser
-CREATE INDEX patch_set_approvals_closedByU
-ON patch_set_approvals (change_open, account_id, change_sort_key);
-
-
--- *********************************************************************
 -- ChangeMessageAccess
 --    @PrimaryKey covers: byChange
 
@@ -141,18 +109,6 @@
 
 
 -- *********************************************************************
--- ProjectAccess
---    @PrimaryKey covers: all, suggestByName
-
-
--- *********************************************************************
--- TrackingIdAccess
---
-CREATE INDEX tracking_ids_byTrkKey
-ON tracking_ids (tracking_key);
-
-
--- *********************************************************************
 -- StarredChangeAccess
 --    @PrimaryKey covers: byAccount
 
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 5b8c463..ad92293 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
@@ -117,64 +117,25 @@
 
 -- *********************************************************************
 -- ChangeAccess
---    covers:             byOwnerOpen
-CREATE INDEX changes_byOwnerOpen
-ON changes (owner_account_id, created_on, change_id)
-WHERE open = 'Y';
-
---    covers:             byOwnerClosed
-CREATE INDEX changes_byOwnerClosed
-ON changes (owner_account_id, last_updated_on)
-WHERE open = 'N';
-
 --    covers:             submitted, allSubmitted
 CREATE INDEX changes_submitted
 ON changes (dest_project_name, dest_branch_name, last_updated_on)
 WHERE status = 's';
 
---    covers:             allOpenPrev, allOpenNext
-CREATE INDEX changes_allOpen
-ON changes (sort_key)
-WHERE open = 'Y';
-
---    covers:             byProjectOpenPrev, byProjectOpenNext
+--    covers:             byProjectOpenAll
 CREATE INDEX changes_byProjectOpen
 ON changes (dest_project_name, sort_key)
 WHERE open = 'Y';
 
---    covers:             allClosedPrev, allClosedNext
-CREATE INDEX changes_allClosed
-ON changes (status, sort_key)
-WHERE open = 'N';
-
 --    covers:             byProject
 CREATE INDEX changes_byProject
 ON changes (dest_project_name);
 
---    covers:             byBranchClosedPrev, byBranchClosedNext
-CREATE INDEX changes_byBranchClosed
-ON changes (status, dest_project_name, dest_branch_name, sort_key)
-WHERE open = 'N';
-
 CREATE INDEX changes_key
 ON changes (change_key);
 
 
 -- *********************************************************************
--- PatchSetApprovalAccess
---    @PrimaryKey covers: byPatchSet, byPatchSetUser
---    covers:             openByUser
-CREATE INDEX patch_set_approvals_openByUser
-ON patch_set_approvals (account_id)
-WHERE change_open = 'Y';
-
---    covers:             closedByUser
-CREATE INDEX patch_set_approvals_closedByU
-ON patch_set_approvals (account_id, change_sort_key)
-WHERE change_open = 'N';
-
-
--- *********************************************************************
 -- ChangeMessageAccess
 --    @PrimaryKey covers: byChange
 
@@ -197,20 +158,6 @@
 CREATE INDEX patch_set_ancestors_desc
 ON patch_set_ancestors (ancestor_revision);
 
-
--- *********************************************************************
--- ProjectAccess
---    @PrimaryKey covers: all, suggestByName
---    covers:             ownedByGroup
-
-
--- *********************************************************************
--- TrackingIdAccess
---
-CREATE INDEX tracking_ids_byTrkKey
-ON tracking_ids (tracking_key);
-
-
 -- *********************************************************************
 -- StarredChangeAccess
 --    @PrimaryKey covers: byAccount
diff --git a/gerrit-server/.gitignore b/gerrit-server/.gitignore
deleted file mode 100644
index 9324efe..0000000
--- a/gerrit-server/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-server.iml
\ No newline at end of file
diff --git a/gerrit-server/.settings/org.eclipse.core.resources.prefs b/gerrit-server/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index 1daeba9..0000000
--- a/gerrit-server/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,7 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/main/resources=UTF-8
-encoding//src/test/java=UTF-8
-encoding//src/test/resources=UTF-8
-encoding//target/generated-sources/prolog-java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-server/.settings/org.eclipse.core.runtime.prefs b/gerrit-server/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-server/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-server/.settings/org.eclipse.jdt.core.prefs b/gerrit-server/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 2f45466..0000000
--- a/gerrit-server/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,264 +0,0 @@
-#Fri Jul 16 23:39:13 PDT 2010
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.join_lines_in_comments=true
-org.eclipse.jdt.core.formatter.join_wrapped_lines=true
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-server/.settings/org.eclipse.jdt.ui.prefs b/gerrit-server/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-server/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 1db7555..070764a 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -1,17 +1,29 @@
-SRCS = glob([
-  'src/main/java/**/*.java',
-  'src/test/java/com/google/gerrit/server/project/Util.java'
-])
+CONSTANTS_SRC = [
+  'src/main/java/com/google/gerrit/server/documentation/Constants.java',
+]
+
+SRCS = glob(
+  ['src/main/java/**/*.java'],
+  excludes = CONSTANTS_SRC,
+)
 RESOURCES =  glob(['src/main/resources/**/*'])
 
+java_library2(
+  name = 'constants',
+  srcs = CONSTANTS_SRC,
+  visibility = ['PUBLIC'],
+)
+
 # TODO(sop) break up gerrit-server java_library(), its too big
 java_library2(
   name = 'server',
   srcs = SRCS,
   resources = RESOURCES,
   deps = [
+    ':constants',
     '//gerrit-antlr:query_exception',
     '//gerrit-antlr:query_parser',
+    '//gerrit-common:annotations',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
     '//gerrit-patch-commonsnet:commons-net',
@@ -41,17 +53,23 @@
     '//lib/commons:dbcp',
     '//lib/commons:lang',
     '//lib/commons:net',
+    '//lib/commons:validator',
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
     '//lib/guice:guice-servlet',
     '//lib/jgit:jgit',
+    '//lib/jgit:jgit-archive',
     '//lib/joda:joda-time',
     '//lib/log:api',
     '//lib/prolog:prolog-cafe',
+    '//lib/lucene:analyzers-common',
+    '//lib/lucene:core',
+    '//lib/lucene:query-parser',
   ],
   compile_deps = [
     '//lib/bouncycastle:bcprov',
     '//lib/bouncycastle:bcpg',
+    '//lib/bouncycastle:bcpkix',
   ],
   visibility = ['PUBLIC'],
 )
@@ -62,7 +80,10 @@
   visibility = ['PUBLIC'],
 )
 
-TESTUTIL = glob(['src/test/java/com/google/gerrit/testutil/**/*.java'])
+TESTUTIL = glob([
+  'src/test/java/com/google/gerrit/testutil/**/*.java',
+  'src/test/java/com/google/gerrit/server/project/Util.java',
+  ])
 java_library(
   name = 'testutil',
   srcs = TESTUTIL,
@@ -71,7 +92,6 @@
     '//gerrit-common:server',
     '//gerrit-cache-h2:cache-h2',
     '//gerrit-lucene:lucene',
-    '//gerrit-reviewdb:client',
     '//gerrit-reviewdb:server',
     '//lib:guava',
     '//lib:gwtorm',
@@ -102,7 +122,6 @@
     '//lib/guice:guice',
     '//lib/prolog:prolog-cafe',
   ],
-  export_deps = True,
 )
 
 java_test(
@@ -111,29 +130,68 @@
   resources = glob(['src/test/resources/com/google/gerrit/rules/**/*']),
   deps = [
     ':prolog_test_case',
+    ':server',
+    ':testutil',
+    '//gerrit-common:server',
+    '//gerrit-reviewdb:server',
     '//gerrit-server/src/main/prolog:common',
+    '//lib:gwtorm',
+    '//lib:junit',
+    '//lib/jgit:jgit',
+    '//lib/guice:guice',
+    '//lib/prolog:prolog-cafe',
   ],
 )
 
+QUERY_TESTS = glob(
+  ['src/test/java/com/google/gerrit/server/query/**/*.java'],
+)
+
+java_test(
+  name = 'query_tests',
+  srcs = QUERY_TESTS,
+  deps = [
+    ':server',
+    ':testutil',
+    '//gerrit-antlr:query_exception',
+    '//gerrit-antlr:query_parser',
+    '//gerrit-common:annotations',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server/src/main/prolog:common',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:junit',
+    '//lib/antlr:java_runtime',
+    '//lib/guice:guice',
+    '//lib/jgit:jgit',
+    '//lib/jgit:junit',
+    '//lib/joda:joda-time',
+  ],
+  source_under_test = [':server'],
+)
+
 java_test(
   name = 'server_tests',
   srcs = glob(
     ['src/test/java/**/*.java'],
-    excludes = TESTUTIL + PROLOG_TESTS + PROLOG_TEST_CASE
+    excludes = TESTUTIL + PROLOG_TESTS + PROLOG_TEST_CASE + QUERY_TESTS
   ),
   deps = [
     ':server',
     ':testutil',
     '//gerrit-antlr:query_exception',
-    '//gerrit-antlr:query_parser',
+    '//gerrit-common:annotations',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
     '//gerrit-reviewdb:server',
+    '//gerrit-server/src/main/prolog:common',
+    '//lib:args4j',
     '//lib:easymock',
     '//lib:guava',
     '//lib:gwtorm',
     '//lib:junit',
-    '//lib/antlr:java_runtime',
     '//lib/guice:guice',
     '//lib/jgit:jgit',
     '//lib/jgit:junit',
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 1798f5a..17940c0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -853,7 +853,7 @@
       output = new StringWriter();
       InputStreamReader input = new InputStreamReader(is);
       char[] buffer = new char[4096];
-      int n = 0;
+      int n;
       while ((n = input.read(buffer)) != -1) {
         output.write(buffer, 0, n);
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/Version.java b/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
index 8ad4d48..e69360a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/Version.java
@@ -14,12 +14,16 @@
 
 package com.google.gerrit.common;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 
 public class Version {
+  private static final Logger log = LoggerFactory.getLogger(Version.class);
   private static final String version;
 
   public static String getVersion() {
@@ -31,13 +35,11 @@
   }
 
   private static String loadVersion() {
-    InputStream in = Version.class.getResourceAsStream("Version");
-    if (in == null) {
-      return null;
-    }
-    try {
-      BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
-      try {
+    try (InputStream in = Version.class.getResourceAsStream("Version")) {
+      if (in == null) {
+        return null;
+      }
+      try (BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"))) {
         String vs = r.readLine();
         if (vs != null && vs.startsWith("v")) {
           vs = vs.substring(1);
@@ -46,10 +48,9 @@
           vs = null;
         }
         return vs;
-      } finally {
-        r.close();
       }
     } catch (IOException e) {
+      log.error(e.getMessage(), e);
       return null;
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/lifecycle/LifecycleModule.java b/gerrit-server/src/main/java/com/google/gerrit/lifecycle/LifecycleModule.java
index 04682f5..a22e6da 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/lifecycle/LifecycleModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/lifecycle/LifecycleModule.java
@@ -1,7 +1,7 @@
 package com.google.gerrit.lifecycle;
 
 import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.inject.AbstractModule;
+import com.google.gerrit.server.config.FactoryModule;
 import com.google.inject.Singleton;
 import com.google.inject.binder.LinkedBindingBuilder;
 import com.google.inject.internal.UniqueAnnotations;
@@ -9,7 +9,7 @@
 import java.lang.annotation.Annotation;
 
 /** Module to support registering a unique LifecyleListener. */
-public abstract class LifecycleModule extends AbstractModule {
+public abstract class LifecycleModule extends FactoryModule {
   /**
    * Create a unique listener binding.
    * <p>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
index a0196fd..8202ac2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologCompiler.java
@@ -15,9 +15,9 @@
 package com.google.gerrit.rules;
 
 import com.google.gerrit.common.Version;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
@@ -64,7 +64,7 @@
   }
 
   public static enum Status {
-    NO_RULES, COMPILED;
+    NO_RULES, COMPILED
   }
 
   private final File ruleDir;
@@ -79,7 +79,7 @@
   }
 
   public Status call() throws IOException, CompileException {
-    ObjectId metaConfig = git.resolve(GitRepositoryManager.REF_CONFIG);
+    ObjectId metaConfig = git.resolve(RefNames.REFS_CONFIG);
     if (metaConfig == null) {
       return Status.NO_RULES;
     }
@@ -235,10 +235,9 @@
       mf.getMainAttributes().putValue("Source-Commit", metaConfig.name());
       mf.getMainAttributes().putValue("Source-Blob", rulesId.name());
 
-      FileOutputStream stream = new FileOutputStream(tmpjar);
-      JarOutputStream out = new JarOutputStream(stream, mf);
-      byte buffer[] = new byte[10240];
-      try {
+      try (FileOutputStream stream = new FileOutputStream(tmpjar);
+          JarOutputStream out = new JarOutputStream(stream, mf)) {
+        byte buffer[] = new byte[10240];
         for (String path : toBeJared) {
           JarEntry jarAdd = new JarEntry(path);
           File f = new File(tempDir, path);
@@ -260,8 +259,6 @@
           }
           out.closeEntry();
         }
-      } finally {
-        out.close();
       }
 
       if (!tmpjar.renameTo(archiveFile)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
index dad53b9..4a3cd9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -66,7 +67,7 @@
  * <p>
  * Rules are loaded from the {@code site_path/cache/rules/rules-SHA1.jar}, where
  * {@code SHA1} is the SHA1 of the Prolog {@code rules.pl} in a project's
- * {@link GitRepositoryManager#REF_CONFIG} branch.
+ * {@link RefNames#REFS_CONFIG} branch.
  */
 @Singleton
 public class RulesCache {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
index 2dbd33b..356b7d5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
@@ -35,11 +35,11 @@
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
 
 import com.googlecode.prolog_cafe.lang.Prolog;
 import com.googlecode.prolog_cafe.lang.SystemException;
 
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 
@@ -48,15 +48,23 @@
 
 public final class StoredValues {
   public static final StoredValue<ReviewDb> REVIEW_DB = create(ReviewDb.class);
-  public static final StoredValue<Change> CHANGE = create(Change.class);
   public static final StoredValue<ChangeData> CHANGE_DATA = create(ChangeData.class);
   public static final StoredValue<PatchSet> PATCH_SET = create(PatchSet.class);
   public static final StoredValue<ChangeControl> CHANGE_CONTROL = create(ChangeControl.class);
 
+  public static Change getChange(Prolog engine) throws SystemException {
+    ChangeData cd = CHANGE_DATA.get(engine);
+    try {
+      return cd.change();
+    } catch (OrmException e) {
+      throw new SystemException("Cannot load change " + cd.getId());
+    }
+  }
+
   public static final StoredValue<PatchSetInfo> PATCH_SET_INFO = new StoredValue<PatchSetInfo>() {
     @Override
     public PatchSetInfo createValue(Prolog engine) {
-      Change change = StoredValues.CHANGE.get(engine);
+      Change change = getChange(engine);
       PatchSet ps = StoredValues.PATCH_SET.get(engine);
       PrologEnvironment env = (PrologEnvironment) engine.control;
       PatchSetInfoFactory patchInfoFactory =
@@ -75,7 +83,7 @@
       PrologEnvironment env = (PrologEnvironment) engine.control;
       PatchSetInfo psInfo = StoredValues.PATCH_SET_INFO.get(engine);
       PatchListCache plCache = env.getArgs().getPatchListCache();
-      Change change = StoredValues.CHANGE.get(engine);
+      Change change = getChange(engine);
       Project.NameKey projectKey = change.getProject();
       ObjectId a = null;
       ObjectId b = ObjectId.fromString(psInfo.getRevId());
@@ -96,13 +104,11 @@
     public Repository createValue(Prolog engine) {
       PrologEnvironment env = (PrologEnvironment) engine.control;
       GitRepositoryManager gitMgr = env.getArgs().getGitRepositoryManager();
-      Change change = StoredValues.CHANGE.get(engine);
+      Change change = getChange(engine);
       Project.NameKey projectKey = change.getProject();
       final Repository repo;
       try {
         repo = gitMgr.openRepository(projectKey);
-      } catch (RepositoryNotFoundException e) {
-        throw new SystemException(e.getMessage());
       } catch (IOException e) {
         throw new SystemException(e.getMessage());
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/AccessPath.java b/gerrit-server/src/main/java/com/google/gerrit/server/AccessPath.java
index 31df875..cb720c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/AccessPath.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/AccessPath.java
@@ -32,5 +32,5 @@
   SSH_COMMAND,
 
   /** Access from a Git client using any Git protocol. */
-  GIT;
+  GIT
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
index f1fe72d..ff09df5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
@@ -14,12 +14,12 @@
 
 package com.google.gerrit.server;
 
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.account.CapabilityControl;
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.account.ListGroupMembership;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.inject.Inject;
 
 import java.util.Collection;
@@ -35,7 +35,7 @@
 
   @Override
   public GroupMembership getEffectiveGroups() {
-    return new ListGroupMembership(Collections.singleton(AccountGroup.ANONYMOUS_USERS));
+    return new ListGroupMembership(Collections.singleton(SystemGroupBackend.ANONYMOUS_USERS));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
new file mode 100644
index 0000000..14aa2e3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
@@ -0,0 +1,175 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.server.change.ChangeKind.NO_CODE_CHANGE;
+import static com.google.gerrit.server.change.ChangeKind.TRIVIAL_REBASE;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Table;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.ChangeKind;
+import com.google.gerrit.server.change.ChangeKindCache;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LabelNormalizer;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.NavigableSet;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+/**
+ * Copies approvals between patch sets.
+ * <p>
+ * The result of a copy may either be stored, as when stamping approvals in the
+ * database at submit time, or refreshed on demand, as when reading approvals
+ * from the notedb.
+ */
+public class ApprovalCopier {
+  private final GitRepositoryManager repoManager;
+  private final ProjectCache projectCache;
+  private final ChangeKindCache changeKindCache;
+  private final LabelNormalizer labelNormalizer;
+  private final ChangeData.Factory changeDataFactory;
+
+  @Inject
+  ApprovalCopier(GitRepositoryManager repoManager,
+      ProjectCache projectCache,
+      ChangeKindCache changeKindCache,
+      LabelNormalizer labelNormalizer,
+      ChangeData.Factory changeDataFactory) {
+    this.repoManager = repoManager;
+    this.projectCache = projectCache;
+    this.changeKindCache = changeKindCache;
+    this.labelNormalizer = labelNormalizer;
+    this.changeDataFactory = changeDataFactory;
+  }
+
+  public void copy(ReviewDb db, ChangeControl ctl, PatchSet ps)
+      throws OrmException {
+    db.patchSetApprovals().insert(getForPatchSet(db, ctl, ps));
+  }
+
+  private Iterable<PatchSetApproval> getForPatchSet(ReviewDb db,
+      ChangeControl ctl, PatchSet ps) throws OrmException {
+    ChangeData cd = changeDataFactory.create(db, ctl);
+    try {
+      ProjectState project =
+          projectCache.checkedGet(cd.change().getDest().getParentKey());
+      ListMultimap<PatchSet.Id, PatchSetApproval> all = cd.approvals();
+
+      Table<String, Account.Id, PatchSetApproval> byUser =
+          HashBasedTable.create();
+      for (PatchSetApproval psa : all.get(ps.getId())) {
+        byUser.put(psa.getLabel(), psa.getAccountId(), psa);
+      }
+
+      TreeMap<Integer, PatchSet> patchSets = getPatchSets(cd);
+      NavigableSet<Integer> allPsIds = patchSets.navigableKeySet();
+
+      Repository repo =
+          repoManager.openRepository(project.getProject().getNameKey());
+      try {
+        // Walk patch sets strictly less than current in descending order.
+        Collection<PatchSet> allPrior = patchSets.descendingMap()
+            .tailMap(ps.getId().get(), false)
+            .values();
+        for (PatchSet priorPs : allPrior) {
+          List<PatchSetApproval> priorApprovals = all.get(priorPs.getId());
+          if (priorApprovals.isEmpty()) {
+            continue;
+          }
+
+          ChangeKind kind = changeKindCache.getChangeKind(project, repo,
+              ObjectId.fromString(priorPs.getRevision().get()),
+              ObjectId.fromString(ps.getRevision().get()));
+
+          for (PatchSetApproval psa : priorApprovals) {
+            if (!byUser.contains(psa.getLabel(), psa.getAccountId())
+                && canCopy(project, psa, ps.getId(), allPsIds, kind)) {
+              byUser.put(psa.getLabel(), psa.getAccountId(),
+                  copy(psa, ps.getId()));
+            }
+          }
+        }
+        return labelNormalizer.normalize(ctl, byUser.values()).getNormalized();
+      } finally {
+        repo.close();
+      }
+    } catch (IOException e) {
+      throw new OrmException(e);
+    }
+  }
+
+  private static TreeMap<Integer, PatchSet> getPatchSets(ChangeData cd)
+      throws OrmException {
+    Collection<PatchSet> patchSets = cd.patches();
+    TreeMap<Integer, PatchSet> result = Maps.newTreeMap();
+    for (PatchSet ps : patchSets) {
+      result.put(ps.getId().get(), ps);
+    }
+    return result;
+  }
+
+  private static boolean canCopy(ProjectState project, PatchSetApproval psa,
+      PatchSet.Id psId, NavigableSet<Integer> allPsIds, ChangeKind kind)
+      throws OrmException {
+    int n = psa.getKey().getParentKey().get();
+    checkArgument(n != psId.get());
+    LabelType type = project.getLabelTypes().byLabel(psa.getLabelId());
+    if (type == null) {
+      return false;
+    } else if (Objects.equal(n, previous(allPsIds, psId.get())) && (
+        type.isCopyMinScore() && type.isMaxNegative(psa)
+        || type.isCopyMaxScore() && type.isMaxPositive(psa))) {
+      // Copy min/max score only from the immediately preceding patch set (which
+      // may not be psId.get() - 1).
+      return true;
+    }
+    return (type.isCopyAllScoresOnTrivialRebase() && kind == TRIVIAL_REBASE)
+        || (type.isCopyAllScoresIfNoCodeChange() && kind == NO_CODE_CHANGE);
+  }
+
+  private static PatchSetApproval copy(PatchSetApproval src, PatchSet.Id psId) {
+    if (src.getKey().getParentKey().equals(psId)) {
+      return src;
+    }
+    return new PatchSetApproval(psId, src);
+  }
+
+  private static <T> T previous(NavigableSet<T> s, T v) {
+    SortedSet<T> head = s.headSet(v);
+    return !head.isEmpty() ? head.last() : null;
+  }
+}
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 deb6f30..b648548 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
@@ -14,8 +14,21 @@
 
 package com.google.gerrit.server;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.SetMultimap;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
@@ -26,11 +39,17 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.PatchSetInserter.ChangeKind;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.notedb.ReviewerState;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -43,104 +62,214 @@
  * even if the reviewer hasn't actually given a score to the change.  To
  * mark the "no score" case, a dummy approval, which may live in any of
  * the available categories, with a score of 0 is used.
+ * <p>
+ * The methods in this class only modify the gwtorm database.
  */
 public class ApprovalsUtil {
-  private final ReviewDb db;
-
-  @Inject
-  public ApprovalsUtil(ReviewDb db) {
-    this.db = db;
-  }
-
-  /**
-   * Resync the changeOpen status which is cached in the approvals table for
-   * performance reasons
-   */
-  public void syncChangeStatus(final Change change) throws OrmException {
-    final List<PatchSetApproval> approvals =
-        db.patchSetApprovals().byChange(change.getId()).toList();
-    for (PatchSetApproval a : approvals) {
-      a.cache(change);
-    }
-    db.patchSetApprovals().update(approvals);
-  }
-
-  /**
-   * Copy min/max scores from one patch set to another.
-   *
-   * @throws OrmException
-   */
-  public static void copyLabels(ReviewDb db, LabelTypes labelTypes,
-      PatchSet.Id source, PatchSet dest, ChangeKind changeKind) throws OrmException {
-    Iterable<PatchSetApproval> sourceApprovals =
-        db.patchSetApprovals().byPatchSet(source);
-    copyLabels(db, labelTypes, sourceApprovals, source, dest, changeKind);
-  }
-
-  /**
-   * Copy a set's min/max scores from one patch set to another.
-   *
-   * @throws OrmException
-   */
-  public static void copyLabels(ReviewDb db, LabelTypes labelTypes,
-      Iterable<PatchSetApproval> sourceApprovals, PatchSet.Id source,
-      PatchSet dest, ChangeKind changeKind) throws OrmException {
-    List<PatchSetApproval> copied = Lists.newArrayList();
-    for (PatchSetApproval a : sourceApprovals) {
-      if (source.equals(a.getPatchSetId())) {
-        LabelType type = labelTypes.byLabel(a.getLabelId());
-        if (type == null) {
-          continue;
-        } else if (type.isCopyMinScore() && type.isMaxNegative(a)) {
-          copied.add(new PatchSetApproval(dest.getId(), a));
-        } else if (type.isCopyMaxScore() && type.isMaxPositive(a)) {
-          copied.add(new PatchSetApproval(dest.getId(), a));
-        } else if (type.isCopyAllScoresOnTrivialRebase()
-            && ChangeKind.TRIVIAL_REBASE.equals(changeKind)) {
-          copied.add(new PatchSetApproval(dest.getId(), a));
-        } else if (type.isCopyAllScoresIfNoCodeChange()
-            && ChangeKind.NO_CODE_CHANGE.equals(changeKind)) {
-          copied.add(new PatchSetApproval(dest.getId(), a));
+  private static Ordering<PatchSetApproval> SORT_APPROVALS = Ordering.natural()
+      .onResultOf(new Function<PatchSetApproval, Timestamp>() {
+        @Override
+        public Timestamp apply(PatchSetApproval a) {
+          return a.getGranted();
         }
+      });
+
+  public static List<PatchSetApproval> sortApprovals(
+      Iterable<PatchSetApproval> approvals) {
+    return SORT_APPROVALS.sortedCopy(approvals);
+  }
+
+  private static Iterable<PatchSetApproval> filterApprovals(
+      Iterable<PatchSetApproval> psas, final Account.Id accountId) {
+    return Iterables.filter(psas, new Predicate<PatchSetApproval>() {
+      @Override
+      public boolean apply(PatchSetApproval input) {
+        return Objects.equal(input.getAccountId(), accountId);
+      }
+    });
+  }
+
+  private final NotesMigration migration;
+
+  @VisibleForTesting
+  @Inject
+  public ApprovalsUtil(NotesMigration migration) {
+    this.migration = migration;
+  }
+
+  /**
+   * Get all reviewers for a change.
+   *
+   * @param db review database.
+   * @param notes change notes.
+   * @return multimap of reviewers keyed by state, where each account appears
+   *     exactly once in {@link SetMultimap#values()}, and
+   *     {@link ReviewerState#REMOVED} is not present.
+   * @throws OrmException if reviewers for the change could not be read.
+   */
+  public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers(
+      ReviewDb db, ChangeNotes notes) throws OrmException {
+    if (!migration.readPatchSetApprovals()) {
+      return getReviewers(db.patchSetApprovals().byChange(notes.getChangeId()));
+    }
+    return notes.load().getReviewers();
+  }
+
+  /**
+   * Get all reviewers for a change.
+   *
+   * @param allApprovals all approvals to consider; must all belong to the same
+   *     change.
+   * @return multimap of reviewers keyed by state, where each account appears
+   *     exactly once in {@link SetMultimap#values()}, and
+   *     {@link ReviewerState#REMOVED} is not present.
+   */
+  public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers(
+      ChangeNotes notes, Iterable<PatchSetApproval> allApprovals)
+      throws OrmException {
+    if (!migration.readPatchSetApprovals()) {
+      return getReviewers(allApprovals);
+    }
+    return notes.load().getReviewers();
+  }
+
+  private static ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers(
+      Iterable<PatchSetApproval> allApprovals) {
+    PatchSetApproval first = null;
+    SetMultimap<ReviewerState, Account.Id> reviewers =
+        LinkedHashMultimap.create();
+    for (PatchSetApproval psa : allApprovals) {
+      if (first == null) {
+        first = psa;
+      } else {
+        checkArgument(
+            first.getKey().getParentKey().getParentKey().equals(
+              psa.getKey().getParentKey().getParentKey()),
+            "multiple change IDs: %s, %s", first.getKey(), psa.getKey());
+      }
+      Account.Id id = psa.getAccountId();
+      if (psa.getValue() != 0) {
+        reviewers.put(ReviewerState.REVIEWER, id);
+        reviewers.remove(ReviewerState.CC, id);
+      } else if (!reviewers.containsEntry(ReviewerState.REVIEWER, id)) {
+        reviewers.put(ReviewerState.CC, id);
       }
     }
-    db.patchSetApprovals().insert(copied);
+    return ImmutableSetMultimap.copyOf(reviewers);
   }
 
-  public void addReviewers(ReviewDb db, LabelTypes labelTypes, Change change,
-      PatchSet ps, PatchSetInfo info, Set<Account.Id> wantReviewers,
-      Set<Account.Id> existingReviewers) throws OrmException {
+  public List<PatchSetApproval> addReviewers(ReviewDb db,
+      ChangeUpdate update, LabelTypes labelTypes, Change change, PatchSet ps,
+      PatchSetInfo info, Iterable<Account.Id> wantReviewers,
+      Collection<Account.Id> existingReviewers) throws OrmException {
+    return addReviewers(db, update, labelTypes, change, ps.getId(),
+        ps.isDraft(), info.getAuthor().getAccount(),
+        info.getCommitter().getAccount(), wantReviewers, existingReviewers);
+  }
+
+  public List<PatchSetApproval> addReviewers(ReviewDb db, ChangeNotes notes,
+      ChangeUpdate update, LabelTypes labelTypes, Change change,
+      Iterable<Account.Id> wantReviewers) throws OrmException {
+    PatchSet.Id psId = change.currentPatchSetId();
+    return addReviewers(db, update, labelTypes, change, psId, false, null, null,
+        wantReviewers, getReviewers(db, notes).values());
+  }
+
+  private List<PatchSetApproval> addReviewers(ReviewDb db, ChangeUpdate update,
+      LabelTypes labelTypes, Change change, PatchSet.Id psId, boolean isDraft,
+      Account.Id authorId, Account.Id committerId,
+      Iterable<Account.Id> wantReviewers,
+      Collection<Account.Id> existingReviewers) throws OrmException {
     List<LabelType> allTypes = labelTypes.getLabelTypes();
     if (allTypes.isEmpty()) {
-      return;
+      return ImmutableList.of();
     }
 
-    Set<Account.Id> need = Sets.newHashSet(wantReviewers);
-    Account.Id authorId = info.getAuthor() != null
-        ? info.getAuthor().getAccount()
-        : null;
-    if (authorId != null && !ps.isDraft()) {
+    Set<Account.Id> need = Sets.newLinkedHashSet(wantReviewers);
+    if (authorId != null && !isDraft) {
       need.add(authorId);
     }
 
-    Account.Id committerId = info.getCommitter() != null
-        ? info.getCommitter().getAccount()
-        : null;
-    if (committerId != null && !ps.isDraft()) {
+    if (committerId != null && !isDraft) {
       need.add(committerId);
     }
     need.remove(change.getOwner());
     need.removeAll(existingReviewers);
+    if (need.isEmpty()) {
+      return ImmutableList.of();
+    }
 
     List<PatchSetApproval> cells = Lists.newArrayListWithCapacity(need.size());
     LabelId labelId = Iterables.getLast(allTypes).getLabelId();
     for (Account.Id account : need) {
-      PatchSetApproval psa = new PatchSetApproval(
-          new PatchSetApproval.Key(ps.getId(), account, labelId),
-          (short) 0, TimeUtil.nowTs());
-      psa.cache(change);
-      cells.add(psa);
+      cells.add(new PatchSetApproval(
+          new PatchSetApproval.Key(psId, account, labelId),
+          (short) 0, TimeUtil.nowTs()));
+      update.putReviewer(account, ReviewerState.REVIEWER);
     }
     db.patchSetApprovals().insert(cells);
+    return Collections.unmodifiableList(cells);
+  }
+
+  public ListMultimap<PatchSet.Id, PatchSetApproval> byChange(ReviewDb db,
+      ChangeNotes notes) throws OrmException {
+    if (!migration.readPatchSetApprovals()) {
+      ImmutableListMultimap.Builder<PatchSet.Id, PatchSetApproval> result =
+          ImmutableListMultimap.builder();
+      for (PatchSetApproval psa
+          : db.patchSetApprovals().byChange(notes.getChangeId())) {
+        result.put(psa.getPatchSetId(), psa);
+      }
+      return result.build();
+    }
+    return notes.load().getApprovals();
+  }
+
+  public List<PatchSetApproval> byPatchSet(ReviewDb db, ChangeNotes notes,
+      PatchSet.Id psId) throws OrmException {
+    if (!migration.readPatchSetApprovals()) {
+      return sortApprovals(db.patchSetApprovals().byPatchSet(psId));
+    }
+    return notes.load().getApprovals().get(psId);
+  }
+
+  public List<PatchSetApproval> byPatchSetUser(ReviewDb db,
+      ChangeNotes notes, PatchSet.Id psId, Account.Id accountId)
+      throws OrmException {
+    if (!migration.readPatchSetApprovals()) {
+      return sortApprovals(
+          db.patchSetApprovals().byPatchSetUser(psId, accountId));
+    }
+    return ImmutableList.copyOf(
+        filterApprovals(byPatchSet(db, notes, psId), accountId));
+  }
+
+  public PatchSetApproval getSubmitter(ReviewDb db, ChangeNotes notes,
+      PatchSet.Id c) {
+    if (c == null) {
+      return null;
+    }
+    try {
+      return getSubmitter(c, byPatchSet(db, notes, c));
+    } catch (OrmException e) {
+      return null;
+    }
+  }
+
+  public static PatchSetApproval getSubmitter(PatchSet.Id c,
+      Iterable<PatchSetApproval> approvals) {
+    if (c == null) {
+      return null;
+    }
+    PatchSetApproval submitter = null;
+    for (PatchSetApproval a : approvals) {
+      if (a.getPatchSetId().equals(c) && a.getValue() > 0 && a.isSubmit()) {
+        if (submitter == null
+            || a.getGranted().compareTo(submitter.getGranted()) > 0) {
+          submitter = a;
+        }
+      }
+    }
+    return submitter;
   }
 }
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 24a4b16..939be28 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
@@ -21,41 +21,40 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.primitives.Ints;
-import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetAncestor;
 import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.client.TrackingId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.ChangeMessages;
 import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.config.TrackingFooter;
-import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.events.CommitReceivedEvent;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
 import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.mail.CommitMessageEditedSender;
 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;
 import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gerrit.server.util.IdGenerator;
 import com.google.gerrit.server.util.MagicBranch;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmConcurrencyException;
 import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -64,7 +63,6 @@
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.FooterLine;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
@@ -78,11 +76,8 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
 
 public class ChangeUtil {
   /**
@@ -91,7 +86,7 @@
    * We overrun approximately 4,083 years later, so ~6092.
    */
   @VisibleForTesting
-  public static final long SORT_KEY_EPOCH_MINS =
+  private static final long SORT_KEY_EPOCH_MINS =
       MINUTES.convert(1222819200L, SECONDS);
 
   private static final Object uuidLock = new Object();
@@ -125,7 +120,7 @@
     return u + '_' + l;
   }
 
-  public static void touch(final Change change, ReviewDb db)
+  public static void touch(Change change, ReviewDb db)
       throws OrmException {
     try {
       updated(change);
@@ -144,67 +139,14 @@
     }
   }
 
-  public static void updated(final Change c) {
+  public static void updated(Change c) {
     c.setLastUpdatedOn(TimeUtil.nowTs());
     computeSortKey(c);
   }
 
-  public static void updateTrackingIds(ReviewDb db, Change change,
-      TrackingFooters trackingFooters, List<FooterLine> footerLines)
-      throws OrmException {
-    if (trackingFooters.getTrackingFooters().isEmpty() || footerLines.isEmpty()) {
-      return;
-    }
-
-    final Set<TrackingId> want = new HashSet<TrackingId>();
-    final Set<TrackingId> have = new HashSet<TrackingId>( //
-        db.trackingIds().byChange(change.getId()).toList());
-
-    for (final TrackingFooter footer : trackingFooters.getTrackingFooters()) {
-      for (final FooterLine footerLine : footerLines) {
-        if (footerLine.matches(footer.footerKey())) {
-          // supporting multiple tracking-ids on a single line
-          final Matcher m = footer.match().matcher(footerLine.getValue());
-          while (m.find()) {
-            if (m.group().isEmpty()) {
-              continue;
-            }
-
-            String idstr;
-            if (m.groupCount() > 0) {
-              idstr = m.group(1);
-            } else {
-              idstr = m.group();
-            }
-
-            if (idstr.isEmpty()) {
-              continue;
-            }
-            if (idstr.length() > TrackingId.TRACKING_ID_MAX_CHAR) {
-              continue;
-            }
-
-            want.add(new TrackingId(change.getId(), idstr, footer.system()));
-          }
-        }
-      }
-    }
-
-    // Only insert the rows we don't have, and delete rows we don't match.
-    //
-    final Set<TrackingId> toInsert = new HashSet<TrackingId>(want);
-    final Set<TrackingId> toDelete = new HashSet<TrackingId>(have);
-
-    toInsert.removeAll(have);
-    toDelete.removeAll(want);
-
-    db.trackingIds().insert(toInsert);
-    db.trackingIds().delete(toDelete);
-  }
-
   public static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
       throws OrmException {
-    final int cnt = src.getParentCount();
+    int cnt = src.getParentCount();
     List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
     for (int p = 0; p < cnt; p++) {
       PatchSetAncestor a =
@@ -215,269 +157,7 @@
     db.patchSetAncestors().insert(toInsert);
   }
 
-  public static Change.Id revert(RefControl refControl, PatchSet.Id patchSetId,
-      IdentifiedUser user, CommitValidators commitValidators, String message,
-      ReviewDb db, RevertedSender.Factory revertedSenderFactory,
-      ChangeHooks hooks, Repository git,
-      PatchSetInfoFactory patchSetInfoFactory, PersonIdent myIdent,
-      ChangeInserter.Factory changeInserterFactory)
-          throws NoSuchChangeException, EmailException,
-      OrmException, MissingObjectException, IncorrectObjectTypeException,
-      IOException, InvalidChangeOperationException {
-    final Change.Id changeId = patchSetId.getParentKey();
-    final PatchSet patch = db.patchSets().get(patchSetId);
-    if (patch == null) {
-      throw new NoSuchChangeException(changeId);
-    }
-    final Change changeToRevert = db.changes().get(changeId);
-
-    final RevWalk revWalk = new RevWalk(git);
-    try {
-      RevCommit commitToRevert =
-          revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
-
-      PersonIdent authorIdent =
-          user.newCommitterIdent(myIdent.getWhen(), myIdent.getTimeZone());
-
-      RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
-      revWalk.parseHeaders(parentToCommitToRevert);
-
-      CommitBuilder revertCommitBuilder = new CommitBuilder();
-      revertCommitBuilder.addParentId(commitToRevert);
-      revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
-      revertCommitBuilder.setAuthor(authorIdent);
-      revertCommitBuilder.setCommitter(authorIdent);
-
-      if (message == null) {
-        message = MessageFormat.format(
-            ChangeMessages.get().revertChangeDefaultMessage,
-            changeToRevert.getSubject(), patch.getRevision().get());
-      }
-
-      final ObjectId computedChangeId =
-          ChangeIdUtil.computeChangeId(parentToCommitToRevert.getTree(),
-              commitToRevert, authorIdent, myIdent, message);
-      revertCommitBuilder.setMessage(ChangeIdUtil.insertId(message, computedChangeId, true));
-
-      RevCommit revertCommit;
-      final ObjectInserter oi = git.newObjectInserter();
-      try {
-        ObjectId id = oi.insert(revertCommitBuilder);
-        oi.flush();
-        revertCommit = revWalk.parseCommit(id);
-      } finally {
-        oi.release();
-      }
-
-      final Change change = new Change(
-          new Change.Key("I" + computedChangeId.name()),
-          new Change.Id(db.nextChangeId()),
-          user.getAccountId(),
-          changeToRevert.getDest(),
-          TimeUtil.nowTs());
-      change.setTopic(changeToRevert.getTopic());
-      ChangeInserter ins =
-          changeInserterFactory.create(refControl, change, revertCommit);
-      PatchSet ps = ins.getPatchSet();
-
-      String ref = refControl.getRefName();
-      final String cmdRef =
-          MagicBranch.NEW_PUBLISH_CHANGE
-              + ref.substring(ref.lastIndexOf('/') + 1);
-      CommitReceivedEvent commitReceivedEvent =
-          new CommitReceivedEvent(new ReceiveCommand(ObjectId.zeroId(),
-              revertCommit.getId(), cmdRef), refControl.getProjectControl()
-              .getProject(), refControl.getRefName(), revertCommit, user);
-
-      try {
-        commitValidators.validateForGerritCommits(commitReceivedEvent);
-      } catch (CommitValidationException e) {
-        throw new InvalidChangeOperationException(e.getMessage());
-      }
-
-      final RefUpdate ru = git.updateRef(ps.getRefName());
-      ru.setExpectedOldObjectId(ObjectId.zeroId());
-      ru.setNewObjectId(revertCommit);
-      ru.disableRefLog();
-      if (ru.update(revWalk) != RefUpdate.Result.NEW) {
-        throw new IOException(String.format(
-            "Failed to create ref %s in %s: %s", ps.getRefName(),
-            change.getDest().getParentKey().get(), ru.getResult()));
-      }
-
-      final ChangeMessage cmsg = new ChangeMessage(
-          new ChangeMessage.Key(changeId, ChangeUtil.messageUUID(db)),
-          user.getAccountId(), TimeUtil.nowTs(), patchSetId);
-      final StringBuilder msgBuf =
-          new StringBuilder("Patch Set " + patchSetId.get() + ": Reverted");
-      msgBuf.append("\n\n");
-      msgBuf.append("This patchset was reverted in change: " + change.getKey().get());
-      cmsg.setMessage(msgBuf.toString());
-
-      ins.setMessage(cmsg).insert();
-
-      try {
-        final RevertedSender cm = revertedSenderFactory.create(change);
-        cm.setFrom(user.getAccountId());
-        cm.setChangeMessage(cmsg);
-        cm.send();
-      } catch (Exception err) {
-        log.error("Cannot send email for revert change " + change.getId(),
-            err);
-      }
-
-      return change.getId();
-    } finally {
-      revWalk.release();
-    }
-  }
-
-  public static Change.Id editCommitMessage(final PatchSet.Id patchSetId,
-      final RefControl refControl, final IdentifiedUser user,
-      final String message, final ReviewDb db,
-      final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory,
-      Repository git, PersonIdent myIdent,
-      PatchSetInserter.Factory patchSetInserterFactory)
-      throws NoSuchChangeException, EmailException, OrmException,
-      MissingObjectException, IncorrectObjectTypeException, IOException,
-      InvalidChangeOperationException, PatchSetInfoNotAvailableException {
-    final Change.Id changeId = patchSetId.getParentKey();
-    final PatchSet originalPS = db.patchSets().get(patchSetId);
-    if (originalPS == null) {
-      throw new NoSuchChangeException(changeId);
-    }
-
-    if (message == null || message.length() == 0) {
-      throw new InvalidChangeOperationException(
-          "The commit message cannot be empty");
-    }
-
-    final RevWalk revWalk = new RevWalk(git);
-    try {
-      RevCommit commit =
-          revWalk.parseCommit(ObjectId.fromString(originalPS.getRevision()
-              .get()));
-      if (commit.getFullMessage().equals(message)) {
-        throw new InvalidChangeOperationException(
-            "New commit message cannot be same as existing commit message");
-      }
-
-      Date now = myIdent.getWhen();
-      Change change = db.changes().get(changeId);
-      PersonIdent authorIdent =
-          user.newCommitterIdent(now, myIdent.getTimeZone());
-
-      CommitBuilder commitBuilder = new CommitBuilder();
-      commitBuilder.setTreeId(commit.getTree());
-      commitBuilder.setParentIds(commit.getParents());
-      commitBuilder.setAuthor(commit.getAuthorIdent());
-      commitBuilder.setCommitter(authorIdent);
-      commitBuilder.setMessage(message);
-
-      RevCommit newCommit;
-      final ObjectInserter oi = git.newObjectInserter();
-      try {
-        ObjectId id = oi.insert(commitBuilder);
-        oi.flush();
-        newCommit = revWalk.parseCommit(id);
-      } finally {
-        oi.release();
-      }
-
-      PatchSet.Id id = nextPatchSetId(git, change.currentPatchSetId());
-      final PatchSet newPatchSet = new PatchSet(id);
-      newPatchSet.setCreatedOn(new Timestamp(now.getTime()));
-      newPatchSet.setUploader(user.getAccountId());
-      newPatchSet.setRevision(new RevId(newCommit.name()));
-
-      final String msg =
-          "Patch Set " + newPatchSet.getPatchSetId()
-              + ": Commit message was updated";
-
-      change = patchSetInserterFactory
-          .create(git, revWalk, refControl, user, change, newCommit)
-          .setPatchSet(newPatchSet)
-          .setMessage(msg)
-          .setCopyLabels(true)
-          .setValidatePolicy(RECEIVE_COMMITS)
-          .setDraft(originalPS.isDraft())
-          .insert();
-
-      return change.getId();
-    } finally {
-      revWalk.release();
-    }
-  }
-
-  public static void deleteDraftChange(PatchSet.Id patchSetId,
-      GitRepositoryManager gitManager,
-      GitReferenceUpdated gitRefUpdated, ReviewDb db, ChangeIndexer indexer)
-      throws NoSuchChangeException, OrmException, IOException {
-    final Change.Id changeId = patchSetId.getParentKey();
-    deleteDraftChange(changeId, gitManager, gitRefUpdated, db, indexer);
-  }
-
-  public static void deleteDraftChange(Change.Id changeId,
-      GitRepositoryManager gitManager,
-      GitReferenceUpdated gitRefUpdated, ReviewDb db, ChangeIndexer indexer)
-      throws NoSuchChangeException, OrmException, IOException {
-    Change change = db.changes().get(changeId);
-    if (change == null || change.getStatus() != Change.Status.DRAFT) {
-      throw new NoSuchChangeException(changeId);
-    }
-
-    for (PatchSet ps : db.patchSets().byChange(changeId)) {
-      // These should all be draft patch sets.
-      deleteOnlyDraftPatchSet(ps, change, gitManager, gitRefUpdated, db);
-    }
-
-    db.changeMessages().delete(db.changeMessages().byChange(changeId));
-    db.starredChanges().delete(db.starredChanges().byChange(changeId));
-    db.trackingIds().delete(db.trackingIds().byChange(changeId));
-    db.changes().delete(Collections.singleton(change));
-    indexer.delete(change);
-  }
-
-  public static void deleteOnlyDraftPatchSet(final PatchSet patch,
-      final Change change, GitRepositoryManager gitManager,
-      final GitReferenceUpdated gitRefUpdated, final ReviewDb db)
-      throws NoSuchChangeException, OrmException, IOException {
-    final PatchSet.Id patchSetId = patch.getId();
-    if (!patch.isDraft()) {
-      throw new NoSuchChangeException(patchSetId.getParentKey());
-    }
-
-    Repository repo = gitManager.openRepository(change.getProject());
-    try {
-      RefUpdate update = repo.updateRef(patch.getRefName());
-      update.setForceUpdate(true);
-      update.disableRefLog();
-      switch (update.delete()) {
-        case NEW:
-        case FAST_FORWARD:
-        case FORCED:
-        case NO_CHANGE:
-          // Successful deletion.
-          break;
-        default:
-          throw new IOException("Failed to delete ref " + patch.getRefName() +
-              " in " + repo.getDirectory() + ": " + update.getResult());
-      }
-      gitRefUpdated.fire(change.getProject(), update);
-    } finally {
-      repo.close();
-    }
-
-    db.accountPatchReviews().delete(db.accountPatchReviews().byPatchSet(patchSetId));
-    db.changeMessages().delete(db.changeMessages().byPatchSet(patchSetId));
-    db.patchComments().delete(db.patchComments().byPatchSet(patchSetId));
-    db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(patchSetId));
-    db.patchSetAncestors().delete(db.patchSetAncestors().byPatchSet(patchSetId));
-
-    db.patchSets().delete(Collections.singleton(patch));
-  }
-
-  public static String sortKey(long lastUpdatedMs, int id){
+  public static String sortKey(long lastUpdatedMs, int id) {
     long lastUpdatedMins = MINUTES.convert(lastUpdatedMs, MILLISECONDS);
     long minsSinceEpoch = lastUpdatedMins - SORT_KEY_EPOCH_MINS;
     StringBuilder r = new StringBuilder(16);
@@ -514,6 +194,312 @@
     return nextPatchSetId(git.getRefDatabase().getRefs(RefDatabase.ALL), id);
   }
 
+  private final Provider<CurrentUser> userProvider;
+  private final CommitValidators.Factory commitValidatorsFactory;
+  private final Provider<ReviewDb> db;
+  private final RevertedSender.Factory revertedSenderFactory;
+  private final ChangeInserter.Factory changeInserterFactory;
+  private final PatchSetInserter.Factory patchSetInserterFactory;
+  private final GitRepositoryManager gitManager;
+  private final GitReferenceUpdated gitRefUpdated;
+  private final ChangeIndexer indexer;
+
+  @Inject
+  ChangeUtil(Provider<CurrentUser> userProvider,
+      CommitValidators.Factory commitValidatorsFactory,
+      Provider<ReviewDb> db,
+      RevertedSender.Factory revertedSenderFactory,
+      ChangeInserter.Factory changeInserterFactory,
+      PatchSetInserter.Factory patchSetInserterFactory,
+      GitRepositoryManager gitManager,
+      GitReferenceUpdated gitRefUpdated,
+      ChangeIndexer indexer) {
+    this.userProvider = userProvider;
+    this.commitValidatorsFactory = commitValidatorsFactory;
+    this.db = db;
+    this.revertedSenderFactory = revertedSenderFactory;
+    this.changeInserterFactory = changeInserterFactory;
+    this.patchSetInserterFactory = patchSetInserterFactory;
+    this.gitManager = gitManager;
+    this.gitRefUpdated = gitRefUpdated;
+    this.indexer = indexer;
+  }
+
+  public Change.Id revert(ChangeControl ctl, PatchSet.Id patchSetId,
+      String message, PersonIdent myIdent, SshInfo sshInfo)
+      throws NoSuchChangeException, EmailException, OrmException,
+      MissingObjectException, IncorrectObjectTypeException, IOException,
+      InvalidChangeOperationException {
+    Change.Id changeId = patchSetId.getParentKey();
+    PatchSet patch = db.get().patchSets().get(patchSetId);
+    if (patch == null) {
+      throw new NoSuchChangeException(changeId);
+    }
+    Change changeToRevert = db.get().changes().get(changeId);
+
+    Repository git;
+    try {
+      git = gitManager.openRepository(ctl.getChange().getProject());
+    } catch (RepositoryNotFoundException e) {
+      throw new NoSuchChangeException(changeId, e);
+    }
+    try {
+      RevWalk revWalk = new RevWalk(git);
+      try {
+        RevCommit commitToRevert =
+            revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
+
+        PersonIdent authorIdent =
+            user().newCommitterIdent(myIdent.getWhen(), myIdent.getTimeZone());
+
+        RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
+        revWalk.parseHeaders(parentToCommitToRevert);
+
+        CommitBuilder revertCommitBuilder = new CommitBuilder();
+        revertCommitBuilder.addParentId(commitToRevert);
+        revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
+        revertCommitBuilder.setAuthor(authorIdent);
+        revertCommitBuilder.setCommitter(authorIdent);
+
+        if (message == null) {
+          message = MessageFormat.format(
+              ChangeMessages.get().revertChangeDefaultMessage,
+              changeToRevert.getSubject(), patch.getRevision().get());
+        }
+
+        ObjectId computedChangeId =
+            ChangeIdUtil.computeChangeId(parentToCommitToRevert.getTree(),
+                commitToRevert, authorIdent, myIdent, message);
+        revertCommitBuilder.setMessage(
+            ChangeIdUtil.insertId(message, computedChangeId, true));
+
+        RevCommit revertCommit;
+        ObjectInserter oi = git.newObjectInserter();
+        try {
+          ObjectId id = oi.insert(revertCommitBuilder);
+          oi.flush();
+          revertCommit = revWalk.parseCommit(id);
+        } finally {
+          oi.release();
+        }
+
+        RefControl refControl = ctl.getRefControl();
+        Change change = new Change(
+            new Change.Key("I" + computedChangeId.name()),
+            new Change.Id(db.get().nextChangeId()),
+            user().getAccountId(),
+            changeToRevert.getDest(),
+            TimeUtil.nowTs());
+        change.setTopic(changeToRevert.getTopic());
+        ChangeInserter ins =
+            changeInserterFactory.create(refControl, change, revertCommit);
+        PatchSet ps = ins.getPatchSet();
+
+        String ref = refControl.getRefName();
+        String cmdRef = MagicBranch.NEW_PUBLISH_CHANGE
+            + ref.substring(ref.lastIndexOf('/') + 1);
+        CommitReceivedEvent commitReceivedEvent = new CommitReceivedEvent(
+            new ReceiveCommand(ObjectId.zeroId(), revertCommit.getId(), cmdRef),
+            refControl.getProjectControl().getProject(),
+            refControl.getRefName(), revertCommit, user());
+
+        try {
+          commitValidatorsFactory.create(refControl, sshInfo, git)
+              .validateForGerritCommits(commitReceivedEvent);
+        } catch (CommitValidationException e) {
+          throw new InvalidChangeOperationException(e.getMessage());
+        }
+
+        RefUpdate ru = git.updateRef(ps.getRefName());
+        ru.setExpectedOldObjectId(ObjectId.zeroId());
+        ru.setNewObjectId(revertCommit);
+        ru.disableRefLog();
+        if (ru.update(revWalk) != RefUpdate.Result.NEW) {
+          throw new IOException(String.format(
+              "Failed to create ref %s in %s: %s", ps.getRefName(),
+              change.getDest().getParentKey().get(), ru.getResult()));
+        }
+
+        ChangeMessage cmsg = new ChangeMessage(
+            new ChangeMessage.Key(changeId, messageUUID(db.get())),
+            user().getAccountId(), TimeUtil.nowTs(), patchSetId);
+        StringBuilder msgBuf = new StringBuilder();
+        msgBuf.append("Patch Set ").append(patchSetId.get()).append(": Reverted");
+        msgBuf.append("\n\n");
+        msgBuf.append("This patchset was reverted in change: ")
+              .append(change.getKey().get());
+        cmsg.setMessage(msgBuf.toString());
+
+        ins.setMessage(cmsg).insert();
+
+        try {
+          RevertedSender cm = revertedSenderFactory.create(change);
+          cm.setFrom(user().getAccountId());
+          cm.setChangeMessage(cmsg);
+          cm.send();
+        } catch (Exception err) {
+          log.error("Cannot send email for revert change " + change.getId(),
+              err);
+        }
+
+        return change.getId();
+      } finally {
+        revWalk.release();
+      }
+    } finally {
+      git.close();
+    }
+  }
+
+  public Change.Id editCommitMessage(ChangeControl ctl, PatchSet.Id patchSetId,
+      String message, PersonIdent myIdent)
+      throws NoSuchChangeException, EmailException, OrmException,
+      MissingObjectException, IncorrectObjectTypeException, IOException,
+      InvalidChangeOperationException, PatchSetInfoNotAvailableException {
+    Change.Id changeId = patchSetId.getParentKey();
+    PatchSet originalPS = db.get().patchSets().get(patchSetId);
+    if (originalPS == null) {
+      throw new NoSuchChangeException(changeId);
+    }
+
+    if (message == null || message.length() == 0) {
+      throw new InvalidChangeOperationException(
+          "The commit message cannot be empty");
+    }
+
+    Repository git;
+    try {
+      git = gitManager.openRepository(ctl.getChange().getProject());
+    } catch (RepositoryNotFoundException e) {
+      throw new NoSuchChangeException(changeId, e);
+    }
+    try {
+      RevWalk revWalk = new RevWalk(git);
+      try {
+        RevCommit commit =
+            revWalk.parseCommit(ObjectId.fromString(originalPS.getRevision()
+                .get()));
+        if (commit.getFullMessage().equals(message)) {
+          throw new InvalidChangeOperationException(
+              "New commit message cannot be same as existing commit message");
+        }
+
+        Date now = myIdent.getWhen();
+        Change change = db.get().changes().get(changeId);
+        PersonIdent authorIdent =
+            user().newCommitterIdent(now, myIdent.getTimeZone());
+
+        CommitBuilder commitBuilder = new CommitBuilder();
+        commitBuilder.setTreeId(commit.getTree());
+        commitBuilder.setParentIds(commit.getParents());
+        commitBuilder.setAuthor(commit.getAuthorIdent());
+        commitBuilder.setCommitter(authorIdent);
+        commitBuilder.setMessage(message);
+
+        RevCommit newCommit;
+        ObjectInserter oi = git.newObjectInserter();
+        try {
+          ObjectId id = oi.insert(commitBuilder);
+          oi.flush();
+          newCommit = revWalk.parseCommit(id);
+        } finally {
+          oi.release();
+        }
+
+        PatchSet.Id id = nextPatchSetId(git, change.currentPatchSetId());
+        PatchSet newPatchSet = new PatchSet(id);
+        newPatchSet.setCreatedOn(new Timestamp(now.getTime()));
+        newPatchSet.setUploader(user().getAccountId());
+        newPatchSet.setRevision(new RevId(newCommit.name()));
+
+        String msg = "Patch Set " + newPatchSet.getPatchSetId()
+            + ": Commit message was updated";
+
+        change = patchSetInserterFactory
+            .create(git, revWalk, ctl, newCommit)
+            .setPatchSet(newPatchSet)
+            .setMessage(msg)
+            .setCopyLabels(true)
+            .setValidatePolicy(RECEIVE_COMMITS)
+            .setDraft(originalPS.isDraft())
+            .insert();
+
+        return change.getId();
+      } finally {
+        revWalk.release();
+      }
+    } finally {
+      git.close();
+    }
+  }
+
+  public void deleteDraftChange(PatchSet.Id patchSetId)
+      throws NoSuchChangeException, OrmException, IOException {
+    deleteDraftChange(patchSetId.getParentKey());
+  }
+
+  public void deleteDraftChange(Change.Id changeId)
+      throws NoSuchChangeException, OrmException, IOException {
+    ReviewDb db = this.db.get();
+    Change change = db.changes().get(changeId);
+    if (change == null || change.getStatus() != Change.Status.DRAFT) {
+      throw new NoSuchChangeException(changeId);
+    }
+
+    for (PatchSet ps : db.patchSets().byChange(changeId)) {
+      // These should all be draft patch sets.
+      deleteOnlyDraftPatchSet(ps, change);
+    }
+
+    db.changeMessages().delete(db.changeMessages().byChange(changeId));
+    db.starredChanges().delete(db.starredChanges().byChange(changeId));
+    db.changes().delete(Collections.singleton(change));
+    indexer.delete(db, change);
+  }
+
+  public void deleteOnlyDraftPatchSet(PatchSet patch, Change change)
+      throws NoSuchChangeException, OrmException, IOException {
+    PatchSet.Id patchSetId = patch.getId();
+    if (!patch.isDraft()) {
+      throw new NoSuchChangeException(patchSetId.getParentKey());
+    }
+
+    Repository repo = gitManager.openRepository(change.getProject());
+    try {
+      RefUpdate update = repo.updateRef(patch.getRefName());
+      update.setForceUpdate(true);
+      update.disableRefLog();
+      switch (update.delete()) {
+        case NEW:
+        case FAST_FORWARD:
+        case FORCED:
+        case NO_CHANGE:
+          // Successful deletion.
+          break;
+        default:
+          throw new IOException("Failed to delete ref " + patch.getRefName() +
+              " in " + repo.getDirectory() + ": " + update.getResult());
+      }
+      gitRefUpdated.fire(change.getProject(), update);
+    } finally {
+      repo.close();
+    }
+
+    ReviewDb db = this.db.get();
+    db.accountPatchReviews().delete(db.accountPatchReviews().byPatchSet(patchSetId));
+    db.changeMessages().delete(db.changeMessages().byPatchSet(patchSetId));
+    db.patchComments().delete(db.patchComments().byPatchSet(patchSetId));
+    // No need to delete from notedb; draft patch sets will be filtered out.
+    db.patchSetApprovals().delete(db.patchSetApprovals().byPatchSet(patchSetId));
+    db.patchSetAncestors().delete(db.patchSetAncestors().byPatchSet(patchSetId));
+
+    db.patchSets().delete(Collections.singleton(patch));
+  }
+
+  private IdentifiedUser user() {
+    return (IdentifiedUser) userProvider.get();
+  }
+
   private static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
     return new PatchSet.Id(id.getParentKey(), id.get() + 1);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java b/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java
index bda15d0..0416ba4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/DefaultFileExtensionRegistry.java
@@ -37,6 +37,7 @@
       .put(".gitmodules", INI)
       .put("project.config", INI)
       .put("BUCK", PYTHON)
+      .put("bucklet", newMimeType(PYTHON.toString(), 1))
       .put("defs", newMimeType(PYTHON.toString(), 1))
       .put("py", newMimeType(PYTHON.toString(), 1))
       .put("go", newMimeType("text/x-go", 1))
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 4f033d0..6d798cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.common.data.AccountInfo;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.StarredChange;
@@ -31,10 +30,10 @@
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.account.ListGroupMembership;
-import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
@@ -69,23 +68,21 @@
     private final AuthConfig authConfig;
     private final String anonymousCowardName;
     private final Provider<String> canonicalUrl;
-    private final Realm realm;
     private final AccountCache accountCache;
     private final GroupBackend groupBackend;
 
     @Inject
-    GenericFactory(
-        CapabilityControl.Factory capabilityControlFactory,
-        final AuthConfig authConfig,
-        final @AnonymousCowardName String anonymousCowardName,
-        final @CanonicalWebUrl Provider<String> canonicalUrl,
-        final Realm realm, final AccountCache accountCache,
-        final GroupBackend groupBackend) {
+    public GenericFactory(
+        @Nullable CapabilityControl.Factory capabilityControlFactory,
+        AuthConfig authConfig,
+        @AnonymousCowardName String anonymousCowardName,
+        @CanonicalWebUrl Provider<String> canonicalUrl,
+        AccountCache accountCache,
+        GroupBackend groupBackend) {
       this.capabilityControlFactory = capabilityControlFactory;
       this.authConfig = authConfig;
       this.anonymousCowardName = anonymousCowardName;
       this.canonicalUrl = canonicalUrl;
-      this.realm = realm;
       this.accountCache = accountCache;
       this.groupBackend = groupBackend;
     }
@@ -96,20 +93,20 @@
 
     public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory,
-          authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+          authConfig, anonymousCowardName, canonicalUrl, accountCache,
           groupBackend, null, db, id, null);
     }
 
     public IdentifiedUser create(SocketAddress remotePeer, Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory,
-          authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+          authConfig, anonymousCowardName, canonicalUrl, accountCache,
           groupBackend, Providers.of(remotePeer), null, id,  null);
     }
 
     public CurrentUser runAs(SocketAddress remotePeer, Account.Id id,
         @Nullable CurrentUser caller) {
       return new IdentifiedUser(capabilityControlFactory,
-          authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+          authConfig, anonymousCowardName, canonicalUrl, accountCache,
           groupBackend, Providers.of(remotePeer), null, id, caller);
     }
   }
@@ -126,7 +123,6 @@
     private final AuthConfig authConfig;
     private final String anonymousCowardName;
     private final Provider<String> canonicalUrl;
-    private final Realm realm;
     private final AccountCache accountCache;
     private final GroupBackend groupBackend;
 
@@ -139,7 +135,7 @@
         final AuthConfig authConfig,
         final @AnonymousCowardName String anonymousCowardName,
         final @CanonicalWebUrl Provider<String> canonicalUrl,
-        final Realm realm, final AccountCache accountCache,
+        final AccountCache accountCache,
         final GroupBackend groupBackend,
 
         final @RemotePeer Provider<SocketAddress> remotePeerProvider,
@@ -148,7 +144,6 @@
       this.authConfig = authConfig;
       this.anonymousCowardName = anonymousCowardName;
       this.canonicalUrl = canonicalUrl;
-      this.realm = realm;
       this.accountCache = accountCache;
       this.groupBackend = groupBackend;
 
@@ -158,13 +153,13 @@
 
     public IdentifiedUser create(Account.Id id) {
       return new IdentifiedUser(capabilityControlFactory,
-          authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+          authConfig, anonymousCowardName, canonicalUrl, accountCache,
           groupBackend, remotePeerProvider, dbProvider, id, null);
     }
 
     public IdentifiedUser runAs(Account.Id id, CurrentUser caller) {
       return new IdentifiedUser(capabilityControlFactory,
-          authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+          authConfig, anonymousCowardName, canonicalUrl, accountCache,
           groupBackend, remotePeerProvider, dbProvider, id, caller);
     }
   }
@@ -174,8 +169,8 @@
 
   private static final GroupMembership registeredGroups =
       new ListGroupMembership(ImmutableSet.of(
-          AccountGroup.ANONYMOUS_USERS,
-          AccountGroup.REGISTERED_USERS));
+          SystemGroupBackend.ANONYMOUS_USERS,
+          SystemGroupBackend.REGISTERED_USERS));
 
   private final Provider<String> canonicalUrl;
   private final AccountCache accountCache;
@@ -204,7 +199,7 @@
       final AuthConfig authConfig,
       final String anonymousCowardName,
       final Provider<String> canonicalUrl,
-      final Realm realm, final AccountCache accountCache,
+      final AccountCache accountCache,
       final GroupBackend groupBackend,
       @Nullable final Provider<SocketAddress> remotePeerProvider,
       @Nullable final Provider<ReviewDb> dbProvider,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ProjectUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ProjectUtil.java
index ea1f1d1..d18fe3d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ProjectUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ProjectUtil.java
@@ -29,9 +29,9 @@
    *
    * @param repoManager Git repository manager to open the git repository
    * @param branch the branch for which it should be checked if it exists
-   * @return <code>true</code> if the specified branch exists or if
-   *         <code>HEAD</code> points to this branch, otherwise
-   *         <code>false</code>
+   * @return {@code true} if the specified branch exists or if
+   *         {@code HEAD} points to this branch, otherwise
+   *         {@code false}
    * @throws RepositoryNotFoundException the repository of the branch's project
    *         does not exist.
    * @throws IOException error while retrieving the branch from the repository.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java
index fe1072d..2133dfb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StringUtil.java
@@ -22,7 +22,7 @@
    * corresponds to its ASCII value, i.e. the string representation of
    * ASCII 0 is found in the first element of this array.
    */
-  static String[] NON_PRINTABLE_CHARS =
+  private static final String[] NON_PRINTABLE_CHARS =
     { "\\x00", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a",
       "\\b",   "\\t",   "\\n",   "\\v",   "\\f",   "\\r",   "\\x0e", "\\x0f",
       "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
index 4002c1a..3a0a27d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
@@ -29,11 +29,11 @@
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.group.GroupJson;
@@ -154,7 +154,7 @@
 
     public ProjectAccessInfo(ProjectControl pc, ProjectConfig config) {
       final RefControl metaConfigControl =
-          pc.controlForRef(GitRepositoryManager.REF_CONFIG);
+          pc.controlForRef(RefNames.REFS_CONFIG);
       local = Maps.newHashMap();
       ownerOf = Sets.newHashSet();
       Map<AccountGroup.UUID, Boolean> visibleGroups =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 0e10080..65b166b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -118,7 +118,7 @@
   private static AccountState missing(Account.Id accountId) {
     Account account = new Account(accountId, TimeUtil.nowTs());
     Collection<AccountExternalId> ids = Collections.emptySet();
-    Set<AccountGroup.UUID> anon = ImmutableSet.of(AccountGroup.ANONYMOUS_USERS);
+    Set<AccountGroup.UUID> anon = ImmutableSet.of();
     return new AccountState(account, anon, ids);
   }
 
@@ -167,13 +167,10 @@
       for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
         final AccountGroup.Id groupId = g.getAccountGroupId();
         final AccountGroup group = groupCache.get(groupId);
-        if (group != null && group.getType() == AccountGroup.Type.INTERNAL) {
+        if (group != null && group.getGroupUUID() != null) {
           internalGroups.add(group.getGroupUUID());
         }
       }
-
-      internalGroups.add(AccountGroup.REGISTERED_USERS);
-      internalGroups.add(AccountGroup.ANONYMOUS_USERS);
       internalGroups = Collections.unmodifiableSet(internalGroups);
 
       return new AccountState(account, internalGroups, externalIds);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
index 1440eac..825bd3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.account;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.reviewdb.client.Account;
@@ -21,10 +23,12 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.AccountsSection;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import java.util.HashSet;
 import java.util.Set;
 
 /** Access control management for one account's access to other accounts. */
@@ -100,14 +104,15 @@
         && ((IdentifiedUser) currentUser).getAccountId().equals(otherUser)) {
       return true;
     }
+    if (currentUser.getCapabilities().canViewAllAccounts()) {
+      return true;
+    }
 
     switch (accountVisibility) {
       case ALL:
         return true;
       case SAME_GROUP: {
         Set<AccountGroup.UUID> usersGroups = groupsOf(otherUser);
-        usersGroups.remove(AccountGroup.ANONYMOUS_USERS);
-        usersGroups.remove(AccountGroup.REGISTERED_USERS);
         for (PermissionRule rule : accountsSection.getSameGroupVisibility()) {
           if (rule.isBlock() || rule.isDeny()) {
             usersGroups.remove(rule.getGroup().getUUID());
@@ -121,8 +126,6 @@
       }
       case VISIBLE_GROUP: {
         Set<AccountGroup.UUID> usersGroups = groupsOf(otherUser);
-        usersGroups.remove(AccountGroup.ANONYMOUS_USERS);
-        usersGroups.remove(AccountGroup.REGISTERED_USERS);
         for (AccountGroup.UUID usersGroup : usersGroups) {
           try {
             if (groupControlFactory.controlFor(usersGroup).isVisible()) {
@@ -139,10 +142,17 @@
       default:
         throw new IllegalStateException("Bad AccountVisibility " + accountVisibility);
     }
-    return currentUser.getCapabilities().canAdministrateServer();
+    return false;
   }
 
   private Set<AccountGroup.UUID> groupsOf(Account.Id account) {
-    return userFactory.create(account).getEffectiveGroups().getKnownGroups();
+    return new HashSet<>(Sets.filter(
+      userFactory.create(account).getEffectiveGroups().getKnownGroups(),
+      new Predicate<AccountGroup.UUID>() {
+        @Override
+        public boolean apply(AccountGroup.UUID in) {
+          return !SystemGroupBackend.isSystemGroup(in);
+        }
+      }));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java
index a4881a4..4dc9d79 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java
@@ -34,7 +34,7 @@
     AVATARS,
 
     /** Unique user identity to login to Gerrit, may be deprecated. */
-    USERNAME;
+    USERNAME
   }
 
   public abstract void fillAccountInfo(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
index dfc9546..97abbf6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfo.java
@@ -66,7 +66,7 @@
       if (info == null) {
         info = new AccountInfo(id);
         if (detailed) {
-          info._account_id = id.get();
+          info._accountId = id.get();
         }
         created.put(id, info);
       }
@@ -75,7 +75,7 @@
 
     public void put(AccountInfo info) {
       if (detailed) {
-        info._account_id = info._id.get();
+        info._accountId = info._id.get();
       }
       provided.add(info);
     }
@@ -106,7 +106,7 @@
     _id = id;
   }
 
-  public Integer _account_id;
+  public Integer _accountId;
   public String name;
   public String email;
   public String username;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountVisibility.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountVisibility.java
index 7452da3..7ee8db6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountVisibility.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountVisibility.java
@@ -29,5 +29,5 @@
    * Other accounts are not visible to the given user unless they are explicitly
    * collaborating on a change.
    */
-  NONE;
+  NONE
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
index 7d3c06e..213ecd1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
@@ -59,7 +59,7 @@
   @Override
   public AccountResource parse(TopLevelResource root, IdString id)
       throws ResourceNotFoundException, AuthException, OrmException {
-    IdentifiedUser user = _parse(id.get());
+    IdentifiedUser user = parseId(id.get());
     if (user == null) {
       throw new ResourceNotFoundException(id);
     } else if (!accountControlFactory.get().canSee(user.getAccount())) {
@@ -81,18 +81,33 @@
    */
   public IdentifiedUser parse(String id) throws AuthException,
       UnprocessableEntityException, OrmException {
-    IdentifiedUser user = _parse(id);
+    IdentifiedUser user = parseId(id);
     if (user == null) {
       throw new UnprocessableEntityException(String.format(
           "Account Not Found: %s", id));
+    } else if (!accountControlFactory.get().canSee(user.getAccount())) {
+      throw new UnprocessableEntityException(String.format(
+          "Account Not Found: %s", id));
     }
     return user;
   }
 
-  private IdentifiedUser _parse(String id) throws AuthException, OrmException {
-    CurrentUser user = self.get();
-
+  /**
+   * Parses an account ID and returns the user without making any permission
+   * check whether the current user can see the account.
+   *
+   * @param id ID of the account, can be a string of the format
+   *        "Full Name <email@example.com>", just the email address, a full name
+   *        if it is unique, an account ID, a user name or 'self' for the
+   *        calling user
+   * @return the user, null if no user is found for the given account ID
+   * @throws AuthException thrown if 'self' is used as account ID and the
+   *         current user is not authenticated
+   * @throws OrmException
+   */
+  public IdentifiedUser parseId(String id) throws AuthException, OrmException {
     if (id.equals("self")) {
+      CurrentUser user = self.get();
       if (user.isIdentifiedUser()) {
         return (IdentifiedUser) user;
       } else if (user instanceof AnonymousUser) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
index 2cff009..4d73fe1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
@@ -14,19 +14,19 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.base.Charsets;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.common.io.ByteSource;
 import com.google.gerrit.common.errors.InvalidSshKeyException;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RawInput;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AddSshKey.Input;
 import com.google.gerrit.server.account.GetSshKeys.SshKeyInfo;
 import com.google.gerrit.server.ssh.SshKeyCache;
@@ -57,12 +57,16 @@
 
   @Override
   public Response<SshKeyInfo> apply(AccountResource rsrc, Input input)
-      throws AuthException, MethodNotAllowedException, BadRequestException,
-      ResourceConflictException, OrmException, IOException {
+      throws AuthException, BadRequestException, OrmException, IOException {
     if (self.get() != rsrc.getUser()
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("not allowed to add SSH keys");
     }
+    return apply(rsrc.getUser(), input);
+  }
+
+  public Response<SshKeyInfo> apply(IdentifiedUser user, Input input)
+      throws BadRequestException, OrmException, IOException {
     if (input == null) {
       input = new Input();
     }
@@ -72,7 +76,7 @@
 
     int max = 0;
     for (AccountSshKey k : dbProvider.get().accountSshKeys()
-        .byAccount(rsrc.getUser().getAccountId())) {
+        .byAccount(user.getAccountId())) {
       max = Math.max(max, k.getKey().get());
     }
 
@@ -82,14 +86,14 @@
       public InputStream openStream() throws IOException {
         return rawKey.getInputStream();
       }
-    }.asCharSource(Charsets.UTF_8).read();
+    }.asCharSource(UTF_8).read();
 
     try {
       AccountSshKey sshKey =
           sshKeyCache.create(new AccountSshKey.Id(
-              rsrc.getUser().getAccountId(), max + 1), sshPublicKey);
+              user.getAccountId(), max + 1), sshPublicKey);
       dbProvider.get().accountSshKeys().insert(Collections.singleton(sshKey));
-      sshKeyCache.evict(rsrc.getUser().getUserName());
+      sshKeyCache.evict(user.getUserName());
       return Response.<SshKeyInfo>created(new SshKeyInfo(sshKey));
     } catch (InvalidSshKeyException e) {
       throw new BadRequestException(e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java
index 796b44e..0247dbe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java
@@ -20,7 +20,7 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.group.SystemGroupBackend;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -86,9 +86,8 @@
     return r != null ? r : Collections.<PermissionRule> emptyList();
   }
 
-  private static final GroupReference anonymous = new GroupReference(
-      AccountGroup.ANONYMOUS_USERS,
-      "Anonymous Users");
+  private static final GroupReference anonymous = SystemGroupBackend
+      .getGroup(SystemGroupBackend.ANONYMOUS_USERS);
 
   private static void configureDefaults(Map<String, List<PermissionRule>> out,
       AccessSection section) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index cafc540..aad22eb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -19,7 +19,6 @@
 import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
@@ -27,6 +26,7 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.PeerDaemonUser;
 import com.google.gerrit.server.git.QueueProvider;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
@@ -110,6 +110,12 @@
       || canAdministrateServer();
   }
 
+  /** @return true if the user can view all accounts. */
+  public boolean canViewAllAccounts() {
+    return canPerform(GlobalCapability.VIEW_ALL_ACCOUNTS)
+      || canAdministrateServer();
+  }
+
   /** @return true if the user can view the server caches. */
   public boolean canViewCaches() {
     return canPerform(GlobalCapability.VIEW_CACHES)
@@ -177,7 +183,7 @@
       if (match(groups, r)) {
         switch (r.getAction()) {
           case INTERACTIVE:
-            if (!isGenericGroup(r.getGroup())) {
+            if (!SystemGroupBackend.isAnonymousOrRegistered(r.getGroup())) {
               return QueueProvider.QueueType.INTERACTIVE;
             }
             break;
@@ -202,14 +208,13 @@
     }
   }
 
-  private static boolean isGenericGroup(GroupReference group) {
-    return AccountGroup.ANONYMOUS_USERS.equals(group.getUUID())
-        || AccountGroup.REGISTERED_USERS.equals(group.getUUID());
-  }
-
   /** True if the user has this permission. Works only for non labels. */
   public boolean canPerform(String permissionName) {
-    return !access(permissionName).isEmpty();
+    if (GlobalCapability.ADMINISTRATE_SERVER.equals(permissionName)) {
+      return canAdministrateServer();
+    } else {
+      return !access(permissionName).isEmpty();
+    }
   }
 
   /** The range of permitted values associated with a label permission. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
index 340746e..2d42f0d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -41,8 +41,11 @@
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
+import org.apache.commons.validator.routines.EmailValidator;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -64,7 +67,7 @@
   }
 
   private final ReviewDb db;
-  private final IdentifiedUser currentUser;
+  private final Provider<IdentifiedUser> currentUser;
   private final GroupsCollection groupsCollection;
   private final SshKeyCache sshKeyCache;
   private final AccountCache accountCache;
@@ -73,7 +76,7 @@
   private final String username;
 
   @Inject
-  CreateAccount(ReviewDb db, IdentifiedUser currentUser,
+  CreateAccount(ReviewDb db, Provider<IdentifiedUser> currentUser,
       GroupsCollection groupsCollection, SshKeyCache sshKeyCache,
       AccountCache accountCache, AccountByEmailCache byEmailCache,
       AccountInfo.Loader.Factory infoLoader,
@@ -89,7 +92,7 @@
   }
 
   @Override
-  public Object apply(TopLevelResource rsrc, Input input)
+  public Response<AccountInfo> apply(TopLevelResource rsrc, Input input)
       throws BadRequestException, ResourceConflictException,
       UnprocessableEntityException, OrmException {
     if (input == null) {
@@ -121,10 +124,14 @@
       throw new ResourceConflictException(
           "username '" + username + "' already exists");
     }
-    if (input.email != null
-        && db.accountExternalIds().get(getEmailKey(input.email)) != null) {
-      throw new UnprocessableEntityException(
-          "email '" + input.email + "' already exists");
+    if (input.email != null) {
+      if (db.accountExternalIds().get(getEmailKey(input.email)) != null) {
+        throw new UnprocessableEntityException(
+            "email '" + input.email + "' already exists");
+      }
+      if (!EmailValidator.getInstance().isValid(input.email)) {
+        throw new BadRequestException("invalid email address");
+      }
     }
 
     try {
@@ -164,7 +171,7 @@
           new AccountGroupMember(new AccountGroupMember.Key(id, groupId));
       db.accountGroupMembersAudit().insert(Collections.singleton(
           new AccountGroupMemberAudit(
-              m, currentUser.getAccountId(), TimeUtil.nowTs())));
+              m, currentUser.get().getAccountId(), TimeUtil.nowTs())));
       db.accountGroupMembers().insert(Collections.singleton(m));
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
index 4fda74c..60c448c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.reviewdb.client.Account.FieldName;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.CreateEmail.Input;
 import com.google.gerrit.server.account.GetEmails.EmailInfo;
 import com.google.gerrit.server.config.AuthConfig;
@@ -35,6 +36,7 @@
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
+import org.apache.commons.validator.routines.EmailValidator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -78,8 +80,8 @@
   }
 
   @Override
-  public Object apply(AccountResource rsrc, Input input) throws AuthException,
-      BadRequestException, ResourceConflictException,
+  public Response<EmailInfo> apply(AccountResource rsrc, Input input)
+      throws AuthException, BadRequestException, ResourceConflictException,
       ResourceNotFoundException, OrmException, EmailException,
       MethodNotAllowedException {
     if (self.get() != rsrc.getUser()
@@ -87,16 +89,12 @@
       throw new AuthException("not allowed to add email address");
     }
 
-    if (!realm.allowsEdit(FieldName.REGISTER_NEW_EMAIL)) {
-      throw new MethodNotAllowedException("realm does not allow adding emails");
-    }
-
     if (input == null) {
       input = new Input();
     }
 
-    if (input.email != null && !email.equals(input.email)) {
-      throw new BadRequestException("email address must match URL");
+    if (!EmailValidator.getInstance().isValid(email)) {
+      throw new BadRequestException("invalid email address");
     }
 
     if (input.noConfirmation
@@ -104,19 +102,34 @@
       throw new AuthException("must be administrator to use no_confirmation");
     }
 
+    return apply(rsrc.getUser(), input);
+  }
+
+  public Response<EmailInfo> apply(IdentifiedUser user, Input input)
+      throws AuthException, BadRequestException, ResourceConflictException,
+      ResourceNotFoundException, OrmException, EmailException,
+      MethodNotAllowedException {
+    if (!realm.allowsEdit(FieldName.REGISTER_NEW_EMAIL)) {
+      throw new MethodNotAllowedException("realm does not allow adding emails");
+    }
+
+    if (input.email != null && !email.equals(input.email)) {
+      throw new BadRequestException("email address must match URL");
+    }
+
     EmailInfo info = new EmailInfo();
     info.email = email;
     if (input.noConfirmation
         || authConfig.getAuthType() == AuthType.DEVELOPMENT_BECOME_ANY_ACCOUNT) {
       try {
-        accountManager.link(rsrc.getUser().getAccountId(),
+        accountManager.link(user.getAccountId(),
             AuthRequest.forEmail(email));
       } catch (AccountException e) {
         throw new ResourceConflictException(e.getMessage());
       }
       if (input.preferred) {
         putPreferredProvider.get().apply(
-            new AccountResource.Email(rsrc.getUser(), email),
+            new AccountResource.Email(user, email),
             null);
         info.preferred = true;
       }
@@ -124,10 +137,7 @@
       try {
         registerNewEmailFactory.create(email).send();
         info.pendingConfirmation = true;
-      } catch (EmailException e) {
-        log.error("Cannot send email verification message to " + email, e);
-        throw e;
-      } catch (RuntimeException e) {
+      } catch (EmailException | RuntimeException e) {
         log.error("Cannot send email verification message to " + email, e);
         throw e;
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateGroupArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateGroupArgs.java
new file mode 100644
index 0000000..807eed6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateGroupArgs.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+
+import java.util.Collection;
+
+public class CreateGroupArgs {
+  private AccountGroup.NameKey groupName;
+  public String groupDescription;
+  public boolean visibleToAll;
+  public AccountGroup.Id ownerGroupId;
+  public Collection<? extends Account.Id> initialMembers;
+  public Collection<? extends AccountGroup.UUID> initialGroups;
+
+  public AccountGroup.NameKey getGroup() {
+    return groupName;
+  }
+
+  public String getGroupName() {
+    return groupName != null ? groupName.get() : null;
+  }
+
+  public void setGroupName(String n) {
+    groupName = n != null ? new AccountGroup.NameKey(n) : null;
+  }
+
+  public void setGroupName(AccountGroup.NameKey n) {
+    groupName = n;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
index d44bc2c..4382655 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
@@ -43,7 +43,7 @@
   }
 
   @Override
-  public Object apply(AccountResource rsrc, Input input)
+  public Response<?> apply(AccountResource rsrc, Input input)
       throws ResourceNotFoundException, OrmException {
     Account a = dbProvider.get().accounts().get(rsrc.getUser().getAccountId());
     if (a == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
index 4b38b9f..5e0597b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.DeleteEmail.Input;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -48,25 +49,31 @@
   }
 
   @Override
-  public Object apply(AccountResource.Email rsrc, Input input)
+  public Response<?> apply(AccountResource.Email rsrc, Input input)
       throws AuthException, ResourceNotFoundException,
       ResourceConflictException, MethodNotAllowedException, OrmException {
     if (self.get() != rsrc.getUser()
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("not allowed to delete email address");
     }
+    return apply(rsrc.getUser(), rsrc.getEmail());
+  }
+
+  public Response<?> apply(IdentifiedUser user, String email)
+      throws ResourceNotFoundException, ResourceConflictException,
+      MethodNotAllowedException, OrmException {
     if (!realm.allowsEdit(FieldName.REGISTER_NEW_EMAIL)) {
       throw new MethodNotAllowedException("realm does not allow deleting emails");
     }
     AccountExternalId.Key key = new AccountExternalId.Key(
-        AccountExternalId.SCHEME_MAILTO, rsrc.getEmail());
+        AccountExternalId.SCHEME_MAILTO, email);
     AccountExternalId extId = dbProvider.get().accountExternalIds().get(key);
     if (extId == null) {
-      throw new ResourceNotFoundException(rsrc.getEmail());
+      throw new ResourceNotFoundException(email);
     }
     try {
-      accountManager.unlink(rsrc.getUser().getAccountId(),
-          AuthRequest.forEmail(rsrc.getEmail()));
+      accountManager.unlink(user.getAccountId(),
+          AuthRequest.forEmail(email));
     } catch (AccountException e) {
       throw new ResourceConflictException(e.getMessage());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
index cf60df1..bbba48a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
@@ -40,7 +40,7 @@
   }
 
   @Override
-  public Object apply(AccountResource.SshKey rsrc, Input input)
+  public Response<?> apply(AccountResource.SshKey rsrc, Input input)
       throws OrmException {
     dbProvider.get().accountSshKeys()
         .deleteKeys(Collections.singleton(rsrc.getSshKey().getKey()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java
index 59091e6..c664377 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/EmailExpander.java
@@ -62,7 +62,7 @@
 
     @Override
     public boolean canExpand(final String user) {
-      return user.indexOf(' ') < 0;
+      return !user.contains(" ");
     }
 
     public String expand(final String user) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java
index 76c7ddb..c042e18 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetActive.java
@@ -14,16 +14,16 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
 
 public class GetActive implements RestReadView<AccountResource> {
   @Override
-  public Object apply(AccountResource rsrc) throws ResourceNotFoundException {
+  public Object apply(AccountResource rsrc) {
     if (rsrc.getUser().getAccount().isActive()) {
-      return Response.ok("");
+      return BinaryResult.create("ok\n");
     }
-    throw new ResourceNotFoundException();
+    return Response.none();
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
index 615d09e..e02f5a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
@@ -24,6 +24,7 @@
 import static com.google.gerrit.common.data.GlobalCapability.PRIORITY;
 import static com.google.gerrit.common.data.GlobalCapability.RUN_GC;
 import static com.google.gerrit.common.data.GlobalCapability.STREAM_EVENTS;
+import static com.google.gerrit.common.data.GlobalCapability.VIEW_ALL_ACCOUNTS;
 import static com.google.gerrit.common.data.GlobalCapability.VIEW_CACHES;
 import static com.google.gerrit.common.data.GlobalCapability.VIEW_CONNECTIONS;
 import static com.google.gerrit.common.data.GlobalCapability.VIEW_QUEUE;
@@ -36,7 +37,6 @@
 import com.google.gerrit.extensions.config.CapabilityDefinition;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.CurrentUser;
@@ -59,7 +59,7 @@
   @Option(name = "--format", usage = "(deprecated) output format")
   private OutputFormat format;
 
-  @Option(name = "-q", metaVar = "CAP", multiValued = true, usage = "Capability to inspect")
+  @Option(name = "-q", metaVar = "CAP", usage = "Capability to inspect")
   void addQuery(String name) {
     if (query == null) {
       query = Sets.newHashSet();
@@ -79,8 +79,7 @@
   }
 
   @Override
-  public Object apply(AccountResource resource)
-      throws BadRequestException, Exception {
+  public Object apply(AccountResource resource) throws AuthException {
     if (self.get() != resource.getUser()
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("restricted to administrator");
@@ -106,18 +105,19 @@
       }
     }
 
+    have.put(ACCESS_DATABASE, cc.canAccessDatabase());
     have.put(CREATE_ACCOUNT, cc.canCreateAccount());
     have.put(CREATE_GROUP, cc.canCreateGroup());
     have.put(CREATE_PROJECT, cc.canCreateProject());
     have.put(EMAIL_REVIEWERS, cc.canEmailReviewers());
-    have.put(KILL_TASK, cc.canKillTask());
-    have.put(VIEW_CACHES, cc.canViewCaches());
     have.put(FLUSH_CACHES, cc.canFlushCaches());
-    have.put(VIEW_CONNECTIONS, cc.canViewConnections());
-    have.put(VIEW_QUEUE, cc.canViewQueue());
+    have.put(KILL_TASK, cc.canKillTask());
     have.put(RUN_GC, cc.canRunGC());
     have.put(STREAM_EVENTS, cc.canStreamEvents());
-    have.put(ACCESS_DATABASE, cc.canAccessDatabase());
+    have.put(VIEW_ALL_ACCOUNTS, cc.canViewAllAccounts());
+    have.put(VIEW_CACHES, cc.canViewCaches());
+    have.put(VIEW_CONNECTIONS, cc.canViewConnections());
+    have.put(VIEW_QUEUE, cc.canViewQueue());
 
     QueueProvider.QueueType queue = cc.getQueueType();
     if (queue != QueueProvider.QueueType.INTERACTIVE
@@ -178,7 +178,7 @@
 
   static class CheckOne implements RestReadView<AccountResource.Capability> {
     @Override
-    public Object apply(Capability resource) {
+    public BinaryResult apply(Capability resource) {
       return BinaryResult.create("ok\n");
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
index 8e65a23..ccb0418 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
@@ -16,31 +16,43 @@
 
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
 public class GetDiffPreferences implements RestReadView<AccountResource> {
-
   private final Provider<CurrentUser> self;
+  private final Provider<ReviewDb> db;
 
   @Inject
-  GetDiffPreferences(Provider<CurrentUser> self) {
+  GetDiffPreferences(Provider<CurrentUser> self, Provider<ReviewDb> db) {
     this.self = self;
+    this.db = db;
   }
 
   @Override
-  public DiffPreferencesInfo apply(AccountResource rsrc) throws AuthException {
+  public DiffPreferencesInfo apply(AccountResource rsrc)
+      throws AuthException, OrmException {
     if (self.get() != rsrc.getUser()
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("restricted to administrator");
     }
-    return DiffPreferencesInfo.parse(rsrc.getUser().getAccountDiffPreference());
+
+    Account.Id userId = rsrc.getUser().getAccountId();
+    AccountDiffPreference a = db.get().accountDiffPreferences().get(userId);
+    if (a == null) {
+      a = new AccountDiffPreference(userId);
+    }
+    return DiffPreferencesInfo.parse(a);
   }
 
-  static class DiffPreferencesInfo {
+  public static class DiffPreferencesInfo {
     static DiffPreferencesInfo parse(AccountDiffPreference p) {
       DiffPreferencesInfo info = new DiffPreferencesInfo();
       info.context = p.getContext();
@@ -55,24 +67,32 @@
       info.showWhitespaceErrors = p.isShowWhitespaceErrors() ? true : null;
       info.skipDeleted = p.isSkipDeleted() ? true : null;
       info.skipUncommented = p.isSkipUncommented() ? true : null;
+      info.hideTopMenu = p.isHideTopMenu() ? true : null;
+      info.hideLineNumbers = p.isHideLineNumbers() ? true : null;
       info.syntaxHighlighting = p.isSyntaxHighlighting() ? true : null;
       info.tabSize = p.getTabSize();
+      info.renderEntireFile = p.isRenderEntireFile() ? true : null;
+      info.theme = p.getTheme();
       return info;
     }
 
-    short context;
-    Boolean expandAllComments;
-    Whitespace ignoreWhitespace;
-    Boolean intralineDifference;
-    int lineLength;
-    Boolean manualReview;
-    Boolean retainHeader;
-    Boolean showLineEndings;
-    Boolean showTabs;
-    Boolean showWhitespaceErrors;
-    Boolean skipDeleted;
-    Boolean skipUncommented;
-    Boolean syntaxHighlighting;
-    int tabSize;
+    public short context;
+    public Boolean expandAllComments;
+    public Whitespace ignoreWhitespace;
+    public Boolean intralineDifference;
+    public int lineLength;
+    public Boolean manualReview;
+    public Boolean retainHeader;
+    public Boolean showLineEndings;
+    public Boolean showTabs;
+    public Boolean showWhitespaceErrors;
+    public Boolean skipDeleted;
+    public Boolean skipUncommented;
+    public Boolean syntaxHighlighting;
+    public Boolean hideTopMenu;
+    public Boolean hideLineNumbers;
+    public Boolean renderEntireFile;
+    public int tabSize;
+    public Theme theme;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
index 8eaf4b3..7fc82f9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetHttpPassword.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
@@ -33,7 +32,7 @@
 
   @Override
   public String apply(AccountResource rsrc) throws AuthException,
-      ResourceNotFoundException, OrmException {
+      ResourceNotFoundException {
     if (self.get() != rsrc.getUser()
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("not allowed to get http password");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
index 4d3b913..8eb6947 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
@@ -15,7 +15,9 @@
 package com.google.gerrit.server.account;
 
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
@@ -24,26 +26,34 @@
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
 public class GetPreferences implements RestReadView<AccountResource> {
   private final Provider<CurrentUser> self;
+  private final Provider<ReviewDb> db;
 
   @Inject
-  GetPreferences(Provider<CurrentUser> self) {
+  GetPreferences(Provider<CurrentUser> self, Provider<ReviewDb> db) {
     this.self = self;
+    this.db = db;
   }
 
   @Override
-  public PreferenceInfo apply(AccountResource rsrc) throws AuthException {
+  public PreferenceInfo apply(AccountResource rsrc)
+      throws AuthException, ResourceNotFoundException, OrmException {
     if (self.get() != rsrc.getUser()
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("restricted to administrator");
     }
-    return new PreferenceInfo(rsrc.getUser().getAccount()
-        .getGeneralPreferences());
+    Account a = db.get().accounts().get(rsrc.getUser().getAccountId());
+    if (a == null) {
+      throw new ResourceNotFoundException();
+    }
+    return new PreferenceInfo(a.getGeneralPreferences());
   }
 
   static class PreferenceInfo {
@@ -60,6 +70,7 @@
     Boolean reversePatchSetOrder;
     Boolean showUsernameInReviewCategory;
     Boolean relativeDateInChangeTable;
+    Boolean sizeBarInChangeTable;
     CommentVisibilityStrategy commentVisibilityStrategy;
     DiffView diffView;
     ChangeScreen changeScreen;
@@ -76,6 +87,7 @@
       reversePatchSetOrder = p.isReversePatchSetOrder() ? true : null;
       showUsernameInReviewCategory = p.isShowUsernameInReviewCategory() ? true : null;
       relativeDateInChangeTable = p.isRelativeDateInChangeTable() ? true : null;
+      sizeBarInChangeTable = p.isSizeBarInChangeTable() ? true : null;
       commentVisibilityStrategy = p.getCommentVisibilityStrategy();
       diffView = p.getDiffView();
       changeScreen = p.getChangeScreen();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
index f198b77..8c878d75 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -45,10 +46,13 @@
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("not allowed to get SSH keys");
     }
+    return apply(rsrc.getUser());
+  }
 
+  public List<SshKeyInfo> apply(IdentifiedUser user) throws OrmException {
     List<SshKeyInfo> sshKeys = Lists.newArrayList();
     for (AccountSshKey sshKey : dbProvider.get().accountSshKeys()
-        .byAccount(rsrc.getUser().getAccountId()).toList()) {
+        .byAccount(user.getAccountId()).toList()) {
       sshKeys.add(new SshKeyInfo(sshKey));
     }
     return sshKeys;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
index b301839..b33e3f7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -172,9 +172,7 @@
 
   private static AccountGroup missing(AccountGroup.Id key) {
     AccountGroup.NameKey name = new AccountGroup.NameKey("Deleted Group" + key);
-    AccountGroup g = new AccountGroup(name, key, null);
-    g.setType(AccountGroup.Type.SYSTEM);
-    return g;
+    return new AccountGroup(name, key, null);
   }
 
   static class ByIdLoader extends
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
index 4d74e4d..9b4f4df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupDetailFactory.java
@@ -75,14 +75,8 @@
     if (ownerGroup != null) {
       detail.setOwnerGroup(GroupReference.forGroup(ownerGroup));
     }
-    switch (group.getType()) {
-      case INTERNAL:
-        detail.setMembers(loadMembers());
-        detail.setIncludes(loadIncludes());
-        break;
-      case SYSTEM:
-        break;
-    }
+    detail.setMembers(loadMembers());
+    detail.setIncludes(loadIncludes());
     detail.setAccounts(aic.create());
     detail.setCanModify(control.isOwner());
     return detail;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java
index b6f7b48..df5b2c1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.AccountGroupMember;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gwtorm.server.OrmException;
@@ -66,7 +67,7 @@
   private Set<Account> listAccounts(final AccountGroup.UUID groupUUID,
       final Project.NameKey project, final Set<AccountGroup.UUID> seen)
       throws NoSuchGroupException, OrmException, NoSuchProjectException, IOException {
-    if (AccountGroup.PROJECT_OWNERS.equals(groupUUID)) {
+    if (SystemGroupBackend.PROJECT_OWNERS.equals(groupUUID)) {
       return getProjectOwners(project, seen);
     } else {
       AccountGroup group = groupCache.get(groupUUID);
@@ -81,7 +82,7 @@
   private Set<Account> getProjectOwners(final Project.NameKey project,
       final Set<AccountGroup.UUID> seen) throws NoSuchProjectException,
       NoSuchGroupException, OrmException, IOException {
-    seen.add(AccountGroup.PROJECT_OWNERS);
+    seen.add(SystemGroupBackend.PROJECT_OWNERS);
     if (project == null) {
       return Collections.emptySet();
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
index b020e22..7ef0b0c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
@@ -119,11 +119,12 @@
     GroupMembership membership = user.getEffectiveGroups();
     Set<AccountGroup.UUID> direct = user.state().getInternalGroups();
     Set<AccountGroup.UUID> r = Sets.newHashSet(direct);
-    List<AccountGroup.UUID> q = Lists.newArrayList(r);
+    r.remove(null);
 
+    List<AccountGroup.UUID> q = Lists.newArrayList(r);
     for (AccountGroup.UUID g : membership.intersection(
         includeCache.allExternalMembers())) {
-      if (r.add(g)) {
+      if (g != null && r.add(g)) {
         q.add(g);
       }
     }
@@ -131,7 +132,7 @@
     while (!q.isEmpty()) {
       AccountGroup.UUID id = q.remove(q.size() - 1);
       for (AccountGroup.UUID g : includeCache.memberIn(id)) {
-        if (r.add(g)) {
+        if (g != null && r.add(g)) {
           q.add(g);
           memberOf.put(g, true);
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
index 92e9b86..ca1bf32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PerformCreateGroup.java
@@ -30,6 +30,7 @@
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.lib.PersonIdent;
 
@@ -41,7 +42,7 @@
 public class PerformCreateGroup {
 
   public interface Factory {
-    PerformCreateGroup create();
+    PerformCreateGroup create(CreateGroupArgs createGroupArgs);
   }
 
   private final ReviewDb db;
@@ -50,89 +51,75 @@
   private final IdentifiedUser currentUser;
   private final PersonIdent serverIdent;
   private final GroupCache groupCache;
+  private final CreateGroupArgs createGroupArgs;
 
   @Inject
-  PerformCreateGroup(final ReviewDb db, final AccountCache accountCache,
-      final GroupIncludeCache groupIncludeCache,
-      final IdentifiedUser currentUser,
-      @GerritPersonIdent final PersonIdent serverIdent,
-      final GroupCache groupCache) {
+  PerformCreateGroup(ReviewDb db, AccountCache accountCache,
+      GroupIncludeCache groupIncludeCache, IdentifiedUser currentUser,
+      @GerritPersonIdent PersonIdent serverIdent, GroupCache groupCache,
+      @Assisted CreateGroupArgs createGroupArgs) {
     this.db = db;
     this.accountCache = accountCache;
     this.groupIncludeCache = groupIncludeCache;
     this.currentUser = currentUser;
     this.serverIdent = serverIdent;
     this.groupCache = groupCache;
+    this.createGroupArgs = createGroupArgs;
   }
 
   /**
    * Creates a new group.
-   *
-   * @param groupName the name for the new group
-   * @param groupDescription the description of the new group, <code>null</code>
-   *        if no description
-   * @param visibleToAll <code>true</code> to make the group visible to all
-   *        registered users, if <code>false</code> the group is only visible to
-   *        the group owners and Gerrit administrators
-   * @param ownerGroupId the group that should own the new group, if
-   *        <code>null</code> the new group will own itself
-   * @param initialMembers initial members to be added to the new group
-   * @param initialGroups initial groups to include in the new group
-   * @return the id of the new group
+
+   * @return the new group
    * @throws OrmException is thrown in case of any data store read or write
    *         error
    * @throws NameAlreadyUsedException is thrown in case a group with the given
    *         name already exists
    * @throws PermissionDeniedException user cannot create a group.
    */
-  public AccountGroup createGroup(final String groupName,
-      final String groupDescription, final boolean visibleToAll,
-      final AccountGroup.Id ownerGroupId,
-      final Collection<? extends Account.Id> initialMembers,
-      final Collection<? extends AccountGroup.UUID> initialGroups)
-      throws OrmException, NameAlreadyUsedException, PermissionDeniedException {
+  public AccountGroup createGroup() throws OrmException,
+      NameAlreadyUsedException, PermissionDeniedException {
     if (!currentUser.getCapabilities().canCreateGroup()) {
       throw new PermissionDeniedException(String.format(
         "%s does not have \"Create Group\" capability.",
         currentUser.getUserName()));
     }
-
-    final AccountGroup.Id groupId =
-        new AccountGroup.Id(db.nextAccountGroupId());
-    final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
-    final AccountGroup.UUID uuid = GroupUUID.make(groupName,
+    AccountGroup.Id groupId = new AccountGroup.Id(db.nextAccountGroupId());
+    AccountGroup.UUID uuid = GroupUUID.make(
+        createGroupArgs.getGroupName(),
         currentUser.newCommitterIdent(
             serverIdent.getWhen(),
             serverIdent.getTimeZone()));
-    final AccountGroup group = new AccountGroup(nameKey, groupId, uuid);
-    group.setVisibleToAll(visibleToAll);
-    if (ownerGroupId != null) {
-      AccountGroup ownerGroup = groupCache.get(ownerGroupId);
+    AccountGroup group =
+        new AccountGroup(createGroupArgs.getGroup(), groupId, uuid);
+    group.setVisibleToAll(createGroupArgs.visibleToAll);
+    if (createGroupArgs.ownerGroupId != null) {
+      AccountGroup ownerGroup = groupCache.get(createGroupArgs.ownerGroupId);
       if (ownerGroup != null) {
         group.setOwnerGroupUUID(ownerGroup.getGroupUUID());
       }
     }
-    if (groupDescription != null) {
-      group.setDescription(groupDescription);
+    if (createGroupArgs.groupDescription != null) {
+      group.setDescription(createGroupArgs.groupDescription);
     }
-    final AccountGroupName gn = new AccountGroupName(group);
+    AccountGroupName gn = new AccountGroupName(group);
     // first insert the group name to validate that the group name hasn't
     // already been used to create another group
     try {
       db.accountGroupNames().insert(Collections.singleton(gn));
     } catch (OrmDuplicateKeyException e) {
-      throw new NameAlreadyUsedException(groupName);
+      throw new NameAlreadyUsedException(createGroupArgs.getGroupName());
     }
     db.accountGroups().insert(Collections.singleton(group));
 
-    addMembers(groupId, initialMembers);
+    addMembers(groupId, createGroupArgs.initialMembers);
 
-    if (initialGroups != null) {
-      addGroups(groupId, initialGroups);
+    if (createGroupArgs.initialGroups != null) {
+      addGroups(groupId, createGroupArgs.initialGroups);
       groupIncludeCache.evictMembersOf(uuid);
     }
 
-    groupCache.onCreateGroup(nameKey);
+    groupCache.onCreateGroup(createGroupArgs.getGroup());
 
     return group;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
index a860fda..f1b5151 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
@@ -43,7 +43,7 @@
   }
 
   @Override
-  public Object apply(AccountResource rsrc, Input input)
+  public Response<String> apply(AccountResource rsrc, Input input)
       throws ResourceNotFoundException, OrmException {
     Account a = dbProvider.get().accounts().get(rsrc.getUser().getAccountId());
     if (a == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutEmail.java
index b79d8dc4..ba12bbf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutEmail.java
@@ -15,12 +15,13 @@
 package com.google.gerrit.server.account;
 
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.server.account.CreateEmail.Input;
 
 public class PutEmail implements RestModifyView<AccountResource.Email, Input> {
   @Override
-  public Object apply(AccountResource.Email rsrc, Input input)
+  public Response<?> apply(AccountResource.Email rsrc, Input input)
       throws ResourceConflictException {
     throw new ResourceConflictException("email exists");
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
index 0601b8d..f7061e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.PutHttpPassword.Input;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -94,21 +95,24 @@
       }
       newPassword = input.httpPassword;
     }
+    return apply(rsrc.getUser(), newPassword);
+  }
 
-    if (rsrc.getUser().getUserName() == null) {
+  public Response<String> apply(IdentifiedUser user, String newPassword)
+      throws ResourceNotFoundException, ResourceConflictException, OrmException {
+    if (user.getUserName() == null) {
       throw new ResourceConflictException("username must be set");
     }
 
     AccountExternalId id = dbProvider.get().accountExternalIds()
         .get(new AccountExternalId.Key(
-            SCHEME_USERNAME,
-            rsrc.getUser().getUserName()));
+            SCHEME_USERNAME, user.getUserName()));
     if (id == null) {
       throw new ResourceNotFoundException();
     }
     id.setPassword(newPassword);
     dbProvider.get().accountExternalIds().update(Collections.singleton(id));
-    accountCache.evict(rsrc.getUser().getAccountId());
+    accountCache.evict(user.getAccountId());
 
     return Strings.isNullOrEmpty(newPassword)
         ? Response.<String>none()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
index 50497c7..87629d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.account;
 
+import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_GERRIT;
+
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
@@ -23,9 +25,12 @@
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Account.FieldName;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.PutName.Input;
+import com.google.gerrit.server.auth.ldap.LdapRealm;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -60,19 +65,26 @@
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("not allowed to change name");
     }
+    return apply(rsrc.getUser(), input);
+  }
 
-    if (!realm.allowsEdit(FieldName.FULL_NAME)) {
-      throw new MethodNotAllowedException("realm does not allow editing name");
-    }
-
+  public Response<String> apply(IdentifiedUser user, Input input)
+      throws MethodNotAllowedException, ResourceNotFoundException, OrmException {
     if (input == null) {
       input = new Input();
     }
-
-    Account a = dbProvider.get().accounts().get(rsrc.getUser().getAccountId());
+    ReviewDb db = dbProvider.get();
+    Account a = db.accounts().get(user.getAccountId());
     if (a == null) {
       throw new ResourceNotFoundException("account not found");
     }
+
+    if (!realm.allowsEdit(FieldName.FULL_NAME)
+        && !(realm instanceof LdapRealm && db.accountExternalIds().get(
+            new AccountExternalId.Key(SCHEME_GERRIT, a.getUserName())) == null)) {
+      throw new MethodNotAllowedException("realm does not allow editing name");
+    }
+
     a.setFullName(input.name);
     dbProvider.get().accounts().update(Collections.singleton(a));
     byIdCache.evict(a.getId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
index d81f361..8fc2e6c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.PutPreferred.Input;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -52,15 +53,19 @@
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new AuthException("not allowed to set preferred email address");
     }
+    return apply(rsrc.getUser(), rsrc.getEmail());
+  }
 
-    Account a = dbProvider.get().accounts().get(rsrc.getUser().getAccountId());
+  public Response<String> apply(IdentifiedUser user, String email)
+      throws ResourceNotFoundException, OrmException {
+    Account a = dbProvider.get().accounts().get(user.getAccountId());
     if (a == null) {
       throw new ResourceNotFoundException("account not found");
     }
-    if (rsrc.getEmail().equals(a.getPreferredEmail())) {
+    if (email.equals(a.getPreferredEmail())) {
       return Response.ok("");
     }
-    a.setPreferredEmail(rsrc.getEmail());
+    a.setPreferredEmail(email);
     dbProvider.get().accounts().update(Collections.singleton(a));
     byIdCache.evict(a.getId());
     return Response.created("");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
index db9bc2d..146f241 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
@@ -44,7 +45,11 @@
     Boolean skipDeleted;
     Boolean skipUncommented;
     Boolean syntaxHighlighting;
+    Boolean hideTopMenu;
+    Boolean hideLineNumbers;
+    Boolean renderEntireFile;
     Integer tabSize;
+    Theme theme;
   }
 
   private final Provider<CurrentUser> self;
@@ -116,9 +121,21 @@
       if (input.syntaxHighlighting != null) {
         p.setSyntaxHighlighting(input.syntaxHighlighting);
       }
+      if (input.hideTopMenu != null) {
+        p.setHideTopMenu(input.hideTopMenu);
+      }
+      if (input.hideLineNumbers != null) {
+        p.setHideLineNumbers(input.hideLineNumbers);
+      }
+      if (input.renderEntireFile != null) {
+        p.setRenderEntireFile(input.renderEntireFile);
+      }
       if (input.tabSize != null) {
         p.setTabSize(input.tabSize);
       }
+      if (input.theme != null) {
+        p.setTheme(input.theme);
+      }
 
       db.accountDiffPreferences().upsert(Collections.singleton(p));
       db.commit();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index 8e40b2e..d0418eb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.CommentVisibilityStrategy;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DateFormat;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DiffView;
+import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.ChangeScreen;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadCommand;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.TimeFormat;
@@ -47,8 +48,10 @@
     Boolean reversePatchSetOrder;
     Boolean showUsernameInReviewCategory;
     Boolean relativeDateInChangeTable;
+    Boolean sizeBarInChangeTable;
     CommentVisibilityStrategy commentVisibilityStrategy;
     DiffView diffView;
+    ChangeScreen changeScreen;
   }
 
   private final Provider<CurrentUser> self;
@@ -121,12 +124,18 @@
       if (i.relativeDateInChangeTable != null) {
         p.setRelativeDateInChangeTable(i.relativeDateInChangeTable);
       }
+      if (i.sizeBarInChangeTable != null) {
+        p.setSizeBarInChangeTable(i.sizeBarInChangeTable);
+      }
       if (i.commentVisibilityStrategy != null) {
         p.setCommentVisibilityStrategy(i.commentVisibilityStrategy);
       }
       if (i.diffView != null) {
         p.setDiffView(i.diffView);
       }
+      if (i.changeScreen != null) {
+        p.setChangeScreen(i.changeScreen);
+      }
 
       db.accounts().update(Collections.singleton(a));
       db.commit();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
index 5578c3f..69acd1e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -55,16 +56,20 @@
         && !self.get().getCapabilities().canAdministrateServer()) {
       throw new ResourceNotFoundException();
     }
+    return parse(rsrc.getUser(), id);
+  }
 
+  public AccountResource.SshKey parse(IdentifiedUser user, IdString id)
+      throws ResourceNotFoundException, OrmException {
     try {
       int seq = Integer.parseInt(id.get(), 10);
       AccountSshKey sshKey =
           dbProvider.get().accountSshKeys()
-              .get(new AccountSshKey.Id(rsrc.getUser().getAccountId(), seq));
+              .get(new AccountSshKey.Id(user.getAccountId(), seq));
       if (sshKey == null) {
         throw new ResourceNotFoundException(id);
       }
-      return new AccountResource.SshKey(rsrc.getUser(), sshKey);
+      return new AccountResource.SshKey(user, sshKey);
     } catch (NumberFormatException e) {
       throw new ResourceNotFoundException(id);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
index 0e335d0..b8984ab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/StarredChanges.java
@@ -42,7 +42,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.UnsupportedEncodingException;
 import java.util.Collections;
 
 class StarredChanges implements
@@ -65,7 +64,7 @@
 
   @Override
   public AccountResource.StarredChange parse(AccountResource parent, IdString id)
-      throws ResourceNotFoundException, OrmException, UnsupportedEncodingException {
+      throws ResourceNotFoundException, OrmException {
     IdentifiedUser user = parent.getUser();
     try {
       user.asyncStarredChanges();
@@ -107,9 +106,6 @@
           .setChange(changes.parse(TopLevelResource.INSTANCE, id));
     } catch (ResourceNotFoundException e) {
       throw new UnprocessableEntityException(String.format("change %s not found", id.get()));
-    } catch (UnsupportedEncodingException e) {
-      log.error("cannot resolve change", e);
-      throw new UnprocessableEntityException("internal server error");
     } catch (OrmException e) {
       log.error("cannot resolve change", e);
       throw new UnprocessableEntityException("internal server error");
@@ -161,7 +157,7 @@
 
     @Override
     public Response<?> apply(AccountResource.StarredChange rsrc, EmptyInput in)
-        throws AuthException, OrmException {
+        throws AuthException {
       if (self.get() != rsrc.getUser()) {
         throw new AuthException("not allowed update starred changes");
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/GerritApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/GerritApiImpl.java
new file mode 100644
index 0000000..33314d3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/GerritApiImpl.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api;
+
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.Changes;
+import com.google.gerrit.extensions.api.projects.Projects;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+class GerritApiImpl implements GerritApi {
+  private final Provider<Changes> changes;
+  private final Provider<Projects> projects;
+
+  @Inject
+  GerritApiImpl(Provider<Changes> changes,
+      Provider<Projects> projects) {
+    this.changes = changes;
+    this.projects = projects;
+  }
+
+  @Override
+  public Changes changes() {
+    return changes.get();
+  }
+
+  @Override
+  public Projects projects() {
+    return projects.get();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/Module.java
similarity index 61%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/api/Module.java
index 2e209d1..1919ff5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/Module.java
@@ -12,16 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.project;
+package com.google.gerrit.server.api;
 
-public class ProjectInfo {
-  public String id;
-  public String name;
-  public String parent;
-  public String description;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.inject.AbstractModule;
 
+public class Module extends AbstractModule {
   @Override
-  public String toString() {
-    return name;
+  protected void configure() {
+    bind(GerritApi.class).to(GerritApiImpl.class);
+
+    install(new com.google.gerrit.server.api.changes.Module());
+    install(new com.google.gerrit.server.api.projects.Module());
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
new file mode 100644
index 0000000..0bcfd65
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -0,0 +1,183 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.changes;
+
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.api.changes.AbandonInput;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.Changes;
+import com.google.gerrit.extensions.api.changes.RestoreInput;
+import com.google.gerrit.extensions.api.changes.RevertInput;
+import com.google.gerrit.extensions.api.changes.RevisionApi;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.change.Abandon;
+import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.PostReviewers;
+import com.google.gerrit.server.change.Restore;
+import com.google.gerrit.server.change.Revert;
+import com.google.gerrit.server.change.Revisions;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+class ChangeApiImpl implements ChangeApi {
+  interface Factory {
+    ChangeApiImpl create(ChangeResource change);
+  }
+
+  private final Changes changeApi;
+  private final Revisions revisions;
+  private final RevisionApiImpl.Factory revisionApi;
+  private final ChangeResource change;
+  private final Provider<Abandon> abandon;
+  private final Provider<Revert> revert;
+  private final Provider<Restore> restore;
+  private final Provider<PostReviewers> postReviewers;
+  private final ChangeJson changeJson;
+
+  @Inject
+  ChangeApiImpl(Changes changeApi,
+      Revisions revisions,
+      RevisionApiImpl.Factory revisionApi,
+      Provider<Abandon> abandon,
+      Provider<Revert> revert,
+      Provider<Restore> restore,
+      Provider<PostReviewers> postReviewers,
+      ChangeJson changeJson,
+      @Assisted ChangeResource change) {
+    this.changeApi = changeApi;
+    this.revert = revert;
+    this.revisions = revisions;
+    this.revisionApi = revisionApi;
+    this.abandon = abandon;
+    this.restore = restore;
+    this.postReviewers = postReviewers;
+    this.changeJson = changeJson;
+    this.change = change;
+  }
+
+  @Override
+  public String id() {
+    return Integer.toString(change.getChange().getId().get());
+  }
+
+  @Override
+  public RevisionApi current() throws RestApiException {
+    return revision("current");
+  }
+
+  @Override
+  public RevisionApi revision(int id) throws RestApiException {
+    return revision(String.valueOf(id));
+  }
+
+  @Override
+  public RevisionApi revision(String id) throws RestApiException {
+    try {
+      return revisionApi.create(
+          revisions.parse(change, IdString.fromDecoded(id)));
+    } catch (OrmException e) {
+      throw new RestApiException("Cannot parse revision", e);
+    }
+  }
+
+  @Override
+  public void abandon() throws RestApiException {
+    abandon(new AbandonInput());
+  }
+
+  @Override
+  public void abandon(AbandonInput in) throws RestApiException {
+    try {
+      abandon.get().apply(change, in);
+    } catch (OrmException | IOException e) {
+      throw new RestApiException("Cannot abandon change", e);
+    }
+  }
+
+  @Override
+  public void restore() throws RestApiException {
+    restore(new RestoreInput());
+  }
+
+  @Override
+  public void restore(RestoreInput in) throws RestApiException {
+    try {
+      restore.get().apply(change, in);
+    } catch (OrmException | IOException e) {
+      throw new RestApiException("Cannot restore change", e);
+    }
+  }
+
+  @Override
+  public ChangeApi revert() throws RestApiException {
+    return revert(new RevertInput());
+  }
+
+  @Override
+  public ChangeApi revert(RevertInput in) throws RestApiException {
+    try {
+      return changeApi.id(revert.get().apply(change, in)._number);
+    } catch (OrmException | EmailException | IOException e) {
+      throw new RestApiException("Cannot revert change", e);
+    }
+  }
+
+  @Override
+  public void addReviewer(String reviewer) throws RestApiException {
+    AddReviewerInput in = new AddReviewerInput();
+    in.reviewer = reviewer;
+    addReviewer(in);
+  }
+
+  @Override
+  public void addReviewer(AddReviewerInput in) throws RestApiException {
+    try {
+      postReviewers.get().apply(change, in);
+    } catch (OrmException | EmailException | IOException e) {
+      throw new RestApiException("Cannot add change reviewer", e);
+    }
+  }
+
+  @Override
+  public ChangeInfo get(EnumSet<ListChangesOption> s)
+      throws RestApiException {
+    try {
+      return new ChangeInfoMapper(s).map(
+          changeJson.addOptions(s).format(change));
+    } catch (OrmException e) {
+      throw new RestApiException("Cannot retrieve change", e);
+    }
+  }
+
+  @Override
+  public ChangeInfo get() throws RestApiException {
+    return get(EnumSet.allOf(ListChangesOption.class));
+  }
+
+  @Override
+  public ChangeInfo info() throws RestApiException {
+    return get(EnumSet.noneOf(ListChangesOption.class));
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeInfoMapper.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeInfoMapper.java
new file mode 100644
index 0000000..9d813fc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeInfoMapper.java
@@ -0,0 +1,162 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.changes;
+
+import static com.google.gerrit.extensions.common.ListChangesOption.ALL_REVISIONS;
+import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_ACTIONS;
+import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_REVISION;
+import static com.google.gerrit.extensions.common.ListChangesOption.DETAILED_LABELS;
+import static com.google.gerrit.extensions.common.ListChangesOption.LABELS;
+import static com.google.gerrit.extensions.common.ListChangesOption.MESSAGES;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ApprovalInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.common.ChangeStatus;
+import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Status;
+import com.google.gerrit.server.change.ChangeJson;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+class ChangeInfoMapper {
+  private final static ImmutableMap<Change.Status, ChangeStatus> MAP =
+      Maps.immutableEnumMap(ImmutableMap.of(
+          Status.DRAFT, ChangeStatus.DRAFT,
+          Status.NEW, ChangeStatus.NEW,
+          Status.SUBMITTED, ChangeStatus.SUBMITTED,
+          Status.MERGED, ChangeStatus.MERGED,
+          Status.ABANDONED, ChangeStatus.ABANDONED));
+
+  private final EnumSet<ListChangesOption> s;
+
+  ChangeInfoMapper(EnumSet<ListChangesOption> s) {
+    this.s = s;
+  }
+
+  ChangeInfo map(ChangeJson.ChangeInfo i) {
+    ChangeInfo o = new ChangeInfo();
+    mapCommon(i, o);
+    if (has(LABELS) || has(DETAILED_LABELS)) {
+      mapLabels(i, o);
+    }
+    if (has(MESSAGES)) {
+      mapMessages(i, o);
+    }
+    if (has(ALL_REVISIONS) || has(CURRENT_REVISION)) {
+      o.revisions = i.revisions;
+    }
+    if (has(CURRENT_ACTIONS)) {
+      o.actions = i.actions;
+    }
+    return o;
+  }
+
+  private void mapCommon(ChangeJson.ChangeInfo i, ChangeInfo o) {
+    o.id = i.id;
+    o.project = i.project;
+    o.branch = i.branch;
+    o.topic = i.topic;
+    o.changeId = i.changeId;
+    o.subject = i.subject;
+    o.status = MAP.get(i.status);
+    o.created = i.created;
+    o.updated = i.updated;
+    o.starred = i.starred;
+    o.reviewed = i.reviewed;
+    o.mergeable = i.mergeable;
+    o.insertions = i.insertions;
+    o.deletions = i.deletions;
+    o.owner = fromAcountInfo(i.owner);
+    o.currentRevision = i.currentRevision;
+  }
+
+  private void mapMessages(ChangeJson.ChangeInfo i, ChangeInfo o) {
+    List<ChangeMessageInfo> r =
+        Lists.newArrayListWithCapacity(i.messages.size());
+    for (ChangeJson.ChangeMessageInfo m : i.messages) {
+      ChangeMessageInfo cmi = new ChangeMessageInfo();
+      cmi.id = m.id;
+      cmi.author = fromAcountInfo(m.author);
+      cmi.date = m.date;
+      cmi.message = m.message;
+      cmi._revisionNumber = m._revisionNumber;
+      r.add(cmi);
+    }
+    o.messages = r;
+  }
+
+  private void mapLabels(ChangeJson.ChangeInfo i, ChangeInfo o) {
+    Map<String, LabelInfo> r = Maps.newLinkedHashMap();
+    for (Map.Entry<String, ChangeJson.LabelInfo> e : i.labels.entrySet()) {
+      ChangeJson.LabelInfo li = e.getValue();
+      LabelInfo lo = new LabelInfo();
+      lo.approved = fromAcountInfo(li.approved);
+      lo.rejected = fromAcountInfo(li.rejected);
+      lo.recommended = fromAcountInfo(li.recommended);
+      lo.disliked = fromAcountInfo(li.disliked);
+      lo.value = li.value;
+      lo.optional = li.optional;
+      lo.blocking = li.blocking;
+      lo.values = li.values;
+      if (li.all != null) {
+        lo.all = Lists.newArrayListWithExpectedSize(li.all.size());
+        for (ChangeJson.ApprovalInfo ai : li.all) {
+          lo.all.add(fromApprovalInfo(ai));
+        }
+      }
+      r.put(e.getKey(), lo);
+    }
+    o.labels = r;
+  }
+
+  private boolean has(ListChangesOption o) {
+    return s.contains(o);
+  }
+
+  private static ApprovalInfo fromApprovalInfo(ChangeJson.ApprovalInfo ai) {
+    ApprovalInfo ao = new ApprovalInfo();
+    ao.value = ai.value;
+    ao.date = ai.date;
+    fromAccount(ai, ao);
+    return ao;
+  }
+
+  private static AccountInfo fromAcountInfo(
+      com.google.gerrit.server.account.AccountInfo i) {
+    if (i == null) {
+      return null;
+    }
+    AccountInfo ai = new AccountInfo();
+    fromAccount(i, ai);
+    return ai;
+  }
+
+  private static void fromAccount(
+      com.google.gerrit.server.account.AccountInfo i, AccountInfo ai) {
+    ai._accountId = i._accountId;
+    ai.email = i.email;
+    ai.name = i.name;
+    ai.username = i.username;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
new file mode 100644
index 0000000..fdd0817
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
@@ -0,0 +1,63 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.changes;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.Changes;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.server.change.ChangesCollection;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+class ChangesImpl implements Changes {
+  private final ChangesCollection changes;
+  private final ChangeApiImpl.Factory api;
+
+  @Inject
+  ChangesImpl(ChangesCollection changes, ChangeApiImpl.Factory api) {
+    this.changes = changes;
+    this.api = api;
+  }
+
+  @Override
+  public ChangeApi id(int id) throws RestApiException {
+    return id(String.valueOf(id));
+  }
+
+  @Override
+  public ChangeApi id(String project, String branch, String id)
+      throws RestApiException {
+    return id(Joiner.on('~').join(ImmutableList.of(
+        Url.encode(project),
+        Url.encode(branch),
+        Url.encode(id))));
+  }
+
+  @Override
+  public ChangeApi id(String id) throws RestApiException {
+    try {
+      return api.create(changes.parse(
+          TopLevelResource.INSTANCE,
+          IdString.fromUrl(id)));
+    } catch (OrmException e) {
+      throw new RestApiException("Cannot parse change", e);
+    }
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
similarity index 62%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
index 2e209d1..dbf5f27 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/Module.java
@@ -12,16 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.project;
+package com.google.gerrit.server.api.changes;
 
-public class ProjectInfo {
-  public String id;
-  public String name;
-  public String parent;
-  public String description;
+import com.google.gerrit.extensions.api.changes.Changes;
+import com.google.gerrit.server.config.FactoryModule;
 
+public class Module extends FactoryModule {
   @Override
-  public String toString() {
-    return name;
+  protected void configure() {
+    bind(Changes.class).to(ChangesImpl.class);
+
+    factory(ChangeApiImpl.Factory.class);
+    factory(RevisionApiImpl.Factory.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
new file mode 100644
index 0000000..a9253c3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -0,0 +1,132 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.changes;
+
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.Changes;
+import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.RevisionApi;
+import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.change.CherryPick;
+import com.google.gerrit.server.change.DeleteDraftPatchSet;
+import com.google.gerrit.server.change.PostReview;
+import com.google.gerrit.server.change.Publish;
+import com.google.gerrit.server.change.Rebase;
+import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.change.Submit;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+
+import java.io.IOException;
+
+class RevisionApiImpl implements RevisionApi {
+  interface Factory {
+    RevisionApiImpl create(RevisionResource r);
+  }
+
+  private final Changes changes;
+  private final Provider<CherryPick> cherryPick;
+  private final Provider<DeleteDraftPatchSet> deleteDraft;
+  private final Provider<Rebase> rebase;
+  private final Provider<PostReview> review;
+  private final Provider<Submit> submit;
+  private final Provider<Publish> publish;
+  private final RevisionResource revision;
+
+  @Inject
+  RevisionApiImpl(Changes changes,
+      Provider<CherryPick> cherryPick,
+      Provider<DeleteDraftPatchSet> deleteDraft,
+      Provider<Rebase> rebase,
+      Provider<PostReview> review,
+      Provider<Submit> submit,
+      Provider<Publish> publish,
+      @Assisted RevisionResource r) {
+    this.changes = changes;
+    this.cherryPick = cherryPick;
+    this.deleteDraft = deleteDraft;
+    this.rebase = rebase;
+    this.review = review;
+    this.submit = submit;
+    this.publish = publish;
+    this.revision = r;
+  }
+
+  @Override
+  public void review(ReviewInput in) throws RestApiException {
+    try {
+      review.get().apply(revision, in);
+    } catch (OrmException | IOException e) {
+      throw new RestApiException("Cannot post review", e);
+    }
+  }
+
+  @Override
+  public void submit() throws RestApiException {
+    SubmitInput in = new SubmitInput();
+    in.waitForMerge = true;
+    submit(in);
+  }
+
+  @Override
+  public void submit(SubmitInput in) throws RestApiException {
+    try {
+      submit.get().apply(revision, in);
+    } catch (OrmException | IOException e) {
+      throw new RestApiException("Cannot submit change", e);
+    }
+  }
+
+  @Override
+  public void publish() throws RestApiException {
+    try {
+      publish.get().apply(revision, new Publish.Input());
+    } catch (OrmException | IOException e) {
+      throw new RestApiException("Cannot publish draft patch set", e);
+    }
+  }
+
+  @Override
+  public void delete() throws RestApiException {
+    try {
+      deleteDraft.get().apply(revision, null);
+    } catch (OrmException | IOException e) {
+      throw new RestApiException("Cannot delete draft ps", e);
+    }
+  }
+
+  @Override
+  public ChangeApi rebase() throws RestApiException {
+    try {
+      return changes.id(rebase.get().apply(revision, null)._number);
+    } catch (OrmException | EmailException e) {
+      throw new RestApiException("Cannot rebase ps", e);
+    }
+  }
+
+  @Override
+  public ChangeApi cherryPick(CherryPickInput in) throws RestApiException {
+    try {
+      return changes.id(cherryPick.get().apply(revision, in)._number);
+    } catch (OrmException | EmailException | IOException e) {
+      throw new RestApiException("Cannot cherry pick", e);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/BranchApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/BranchApiImpl.java
new file mode 100644
index 0000000..39166c3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/BranchApiImpl.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.projects;
+
+import com.google.gerrit.extensions.api.projects.BranchApi;
+import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.project.CreateBranch;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.io.IOException;
+
+public class BranchApiImpl implements BranchApi {
+  interface Factory {
+    BranchApiImpl create(ProjectResource project, String ref);
+  }
+
+  private final CreateBranch.Factory createBranchFactory;
+  private final String ref;
+  private final ProjectResource project;
+
+  @Inject
+  BranchApiImpl(
+      CreateBranch.Factory createBranchFactory,
+      @Assisted ProjectResource project,
+      @Assisted String ref) {
+    this.createBranchFactory = createBranchFactory;
+    this.project = project;
+    this.ref = ref;
+  }
+
+  @Override
+  public BranchApi create(BranchInput in) throws RestApiException {
+    try {
+      CreateBranch.Input input = new CreateBranch.Input();
+      input.ref = ref;
+      input.revision = in.revision;
+      createBranchFactory.create(ref).apply(project, input);
+      return this;
+    } catch (IOException e) {
+      throw new RestApiException("Cannot create branch", e);
+    }
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java
similarity index 62%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java
index 2e209d1..0b7e258 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/Module.java
@@ -12,16 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.project;
+package com.google.gerrit.server.api.projects;
 
-public class ProjectInfo {
-  public String id;
-  public String name;
-  public String parent;
-  public String description;
+import com.google.gerrit.extensions.api.projects.Projects;
+import com.google.gerrit.server.config.FactoryModule;
 
+public class Module extends FactoryModule {
   @Override
-  public String toString() {
-    return name;
+  protected void configure() {
+    bind(Projects.class).to(ProjectsImpl.class);
+
+    factory(BranchApiImpl.Factory.class);
+    factory(ProjectApiImpl.Factory.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
new file mode 100644
index 0000000..14b05b6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.projects;
+
+import com.google.gerrit.extensions.api.projects.BranchApi;
+import com.google.gerrit.extensions.api.projects.ProjectApi;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+public class ProjectApiImpl implements ProjectApi {
+  interface Factory {
+    ProjectApiImpl create(ProjectResource project);
+  }
+
+  private final ProjectResource project;
+  private final BranchApiImpl.Factory branchApi;
+
+  @Inject
+  ProjectApiImpl(
+      BranchApiImpl.Factory branchApiFactory,
+      @Assisted ProjectResource project) {
+    this.project = project;
+    this.branchApi = branchApiFactory;
+  }
+
+  @Override
+  public BranchApi branch(String ref) {
+    return branchApi.create(project, ref);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
new file mode 100644
index 0000000..bd5e2ac
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.projects;
+
+import com.google.gerrit.extensions.api.projects.ProjectApi;
+import com.google.gerrit.extensions.api.projects.Projects;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.server.project.ProjectsCollection;
+import com.google.inject.Inject;
+
+import java.io.IOException;
+
+class ProjectsImpl implements Projects {
+  private final ProjectsCollection projects;
+  private final ProjectApiImpl.Factory api;
+
+  @Inject
+  ProjectsImpl(ProjectsCollection projects, ProjectApiImpl.Factory api) {
+    this.projects = projects;
+    this.api = api;
+  }
+
+  @Override
+  public ProjectApi name(String name) throws RestApiException {
+    try {
+      return api.create(projects.parse(name));
+    } catch (IOException | UnprocessableEntityException e) {
+      throw new RestApiException("Cannot retrieve project");
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/NoSuchUserException.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/NoSuchUserException.java
new file mode 100644
index 0000000..acb90b9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/NoSuchUserException.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.auth;
+
+import com.google.gerrit.server.account.AccountException;
+
+/** The user does not exist on the authentication server */
+public class NoSuchUserException extends AccountException {
+  private static final long serialVersionUID = 1L;
+
+  public NoSuchUserException(String username) {
+    super(String.format("No such user: %s", username));
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 63ef2e6..afefe6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.data.ParameterizedString;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.AccountException;
+import com.google.gerrit.server.auth.NoSuchUserException;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.util.ssl.BlindSSLSocketFactory;
@@ -178,7 +179,7 @@
 
     switch (res.size()) {
       case 0:
-        throw new AccountException("No such user:" + username);
+        throw new NoSuchUserException(username);
 
       case 1:
         return res.get(0);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index ac47cb5..7cde019 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -59,7 +59,7 @@
 import javax.security.auth.login.LoginException;
 
 @Singleton
-class LdapRealm implements Realm {
+public class LdapRealm implements Realm {
   static final Logger log = LoggerFactory.getLogger(LdapRealm.class);
   static final String LDAP = "com.sun.jndi.ldap.LdapCtxFactory";
   static final String USERNAME = "username";
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
index c98ddf9..b99648b 100644
--- 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
@@ -52,6 +52,7 @@
     }
   }
 
+  @SuppressWarnings("unchecked")
   public void onRemoval(RemovalNotification<K, V> notification) {
     for (CacheRemovalListener<K, V> l : listeners) {
       l.onRemoval(pluginName, cacheName, notification);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index b406af0..33d161c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -17,19 +17,16 @@
 import com.google.common.base.Strings;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.api.changes.AbandonInput;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.Abandon.Input;
 import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.AbandonedSender;
@@ -46,7 +43,7 @@
 import java.io.IOException;
 import java.util.Collections;
 
-public class Abandon implements RestModifyView<ChangeResource, Input>,
+public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
     UiAction<ChangeResource> {
   private static final Logger log = LoggerFactory.getLogger(Abandon.class);
 
@@ -56,11 +53,6 @@
   private final ChangeJson json;
   private final ChangeIndexer indexer;
 
-  public static class Input {
-    @DefaultInput
-    public String message;
-  }
-
   @Inject
   Abandon(ChangeHooks hooks,
       AbandonedSender.Factory abandonedSenderFactory,
@@ -75,9 +67,9 @@
   }
 
   @Override
-  public Object apply(ChangeResource req, Input input)
-      throws BadRequestException, AuthException,
-      ResourceConflictException, Exception {
+  public ChangeInfo apply(ChangeResource req, AbandonInput input)
+      throws AuthException, ResourceConflictException, OrmException,
+      IOException {
     ChangeControl control = req.getControl();
     IdentifiedUser caller = (IdentifiedUser) control.getCurrentUser();
     Change change = req.getChange();
@@ -112,13 +104,13 @@
       }
       message = newMessage(input, caller, change);
       db.changeMessages().insert(Collections.singleton(message));
-      new ApprovalsUtil(db).syncChangeStatus(change);
       db.commit();
     } finally {
       db.rollback();
     }
 
-    CheckedFuture<?, IOException> indexFuture = indexer.indexAsync(change);
+    CheckedFuture<?, IOException> indexFuture =
+        indexer.indexAsync(change.getId());
     try {
       ReplyToChangeSender cm = abandonedSenderFactory.create(change);
       cm.setFrom(caller.getAccountId());
@@ -147,7 +139,7 @@
           && resource.getControl().canAbandon());
   }
 
-  private ChangeMessage newMessage(Input input, IdentifiedUser caller,
+  private ChangeMessage newMessage(AbandonInput input, IdentifiedUser caller,
       Change change) throws OrmException {
     StringBuilder msg = new StringBuilder();
     msg.append("Abandoned");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java
new file mode 100644
index 0000000..e7d05df
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ArchiveFormat.java
@@ -0,0 +1,84 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.change;
+
+import com.google.common.collect.Maps;
+
+import org.eclipse.jgit.api.ArchiveCommand;
+import org.eclipse.jgit.archive.TarFormat;
+import org.eclipse.jgit.archive.Tbz2Format;
+import org.eclipse.jgit.archive.TgzFormat;
+import org.eclipse.jgit.archive.TxzFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Map;
+
+enum ArchiveFormat {
+  TGZ("application/x-gzip", new TgzFormat()),
+  TAR("application/x-tar", new TarFormat()),
+  TBZ2("application/x-bzip2", new Tbz2Format()),
+  TXZ("application/x-xz", new TxzFormat());
+  // Zip is not supported because it may be interpreted by a Java plugin as a
+  // valid JAR file, whose code would have access to cookies on the domain.
+
+  static final Logger log = LoggerFactory.getLogger(ArchiveFormat.class);
+
+  private final ArchiveCommand.Format<?> format;
+  private final String mimeType;
+
+  private ArchiveFormat(String mimeType, ArchiveCommand.Format<?> format) {
+    this.format = format;
+    this.mimeType = mimeType;
+    ArchiveCommand.registerFormat(name(), format);
+  }
+
+  String getShortName() {
+    return name().toLowerCase();
+  }
+
+  String getMimeType() {
+    return mimeType;
+  }
+
+  String getDefaultSuffix() {
+    return getSuffixes().iterator().next();
+  }
+
+  Iterable<String> getSuffixes() {
+    return format.suffixes();
+  }
+
+  static Map<String, ArchiveFormat> init() {
+    String[] formats = new String[values().length];
+    for (int i = 0; i < values().length; i++) {
+      formats[i] = values()[i].name();
+    }
+
+    Map<String, ArchiveFormat> exts = Maps.newLinkedHashMap();
+    for (String name : formats) {
+      try {
+        ArchiveFormat format = valueOf(name.toUpperCase());
+        for (String ext : format.getSuffixes()) {
+          exts.put(ext, format);
+        }
+      } catch (IllegalArgumentException e) {
+        log.warn("Invalid archive.format {}", name);
+      }
+    }
+    return Collections.unmodifiableMap(exts);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index 77ad83b..ff94f35 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -28,10 +28,9 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.RefControl;
 import com.google.gwtorm.server.OrmException;
@@ -57,11 +56,11 @@
       LoggerFactory.getLogger(ChangeInserter.class);
 
   private final Provider<ReviewDb> dbProvider;
+  private final ChangeUpdate.Factory updateFactory;
   private final GitReferenceUpdated gitRefUpdated;
   private final ChangeHooks hooks;
   private final ApprovalsUtil approvalsUtil;
-  private final TrackingFooters trackingFooters;
-  private final ChangeIndexer indexer;
+  private final MergeabilityChecker mergeabilityChecker;
   private final CreateChangeSender.Factory createChangeSenderFactory;
 
   private final RefControl refControl;
@@ -78,22 +77,23 @@
 
   @Inject
   ChangeInserter(Provider<ReviewDb> dbProvider,
+      ChangeUpdate.Factory updateFactory,
+      Provider<ApprovalsUtil> approvals,
       PatchSetInfoFactory patchSetInfoFactory,
       GitReferenceUpdated gitRefUpdated,
       ChangeHooks hooks,
       ApprovalsUtil approvalsUtil,
-      TrackingFooters trackingFooters,
-      ChangeIndexer indexer,
+      MergeabilityChecker mergeabilityChecker,
       CreateChangeSender.Factory createChangeSenderFactory,
       @Assisted RefControl refControl,
       @Assisted Change change,
       @Assisted RevCommit commit) {
     this.dbProvider = dbProvider;
+    this.updateFactory = updateFactory;
     this.gitRefUpdated = gitRefUpdated;
     this.hooks = hooks;
     this.approvalsUtil = approvalsUtil;
-    this.trackingFooters = trackingFooters;
-    this.indexer = indexer;
+    this.mergeabilityChecker = mergeabilityChecker;
     this.createChangeSenderFactory = createChangeSenderFactory;
     this.refControl = refControl;
     this.change = change;
@@ -158,24 +158,33 @@
 
   public Change insert() throws OrmException, IOException {
     ReviewDb db = dbProvider.get();
+    ChangeUpdate update = updateFactory.create(
+        refControl.getProjectControl().controlFor(change),
+        change.getCreatedOn());
     db.changes().beginTransaction(change.getId());
     try {
       ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
       db.patchSets().insert(Collections.singleton(patchSet));
       db.changes().insert(Collections.singleton(change));
-      ChangeUtil.updateTrackingIds(db, change, trackingFooters, commit.getFooterLines());
       LabelTypes labelTypes = refControl.getProjectControl().getLabelTypes();
-      approvalsUtil.addReviewers(db, labelTypes, change, patchSet, patchSetInfo,
-          reviewers, Collections.<Account.Id> emptySet());
+      approvalsUtil.addReviewers(db, update, labelTypes, change,
+          patchSet, patchSetInfo, reviewers, Collections.<Account.Id> emptySet());
+      if (messageIsForChange()) {
+        insertMessage(db);
+      }
       db.commit();
     } finally {
       db.rollback();
     }
-    if (changeMessage != null) {
-      db.changeMessages().insert(Collections.singleton(changeMessage));
-    }
 
-    CheckedFuture<?, IOException> indexFuture = indexer.indexAsync(change);
+    update.commit();
+    CheckedFuture<?, IOException> f = mergeabilityChecker.newCheck()
+        .addChange(change)
+        .reindex()
+        .runAsync();
+    if (!messageIsForChange()) {
+      insertMessage(db);
+    }
     gitRefUpdated.fire(change.getProject(), patchSet.getRefName(),
         ObjectId.zeroId(), commit);
 
@@ -196,7 +205,18 @@
         log.error("Cannot send email for new change " + change.getId(), err);
       }
     }
-    indexFuture.checkedGet();
+    f.checkedGet();
     return change;
   }
+
+  private boolean messageIsForChange() {
+    return changeMessage != null
+        && changeMessage.getKey().getParentKey().equals(change.getKey());
+  }
+
+  private void insertMessage(ReviewDb db) throws OrmException {
+    if (changeMessage != null) {
+      db.changeMessages().insert(Collections.singleton(changeMessage));
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index faa3181..ad6b804 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -14,23 +14,24 @@
 
 package com.google.gerrit.server.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_ACTIONS;
-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.DETAILED_ACCOUNTS;
-import static com.google.gerrit.common.changes.ListChangesOption.DETAILED_LABELS;
-import static com.google.gerrit.common.changes.ListChangesOption.DOWNLOAD_COMMANDS;
-import static com.google.gerrit.common.changes.ListChangesOption.DRAFT_COMMENTS;
-import static com.google.gerrit.common.changes.ListChangesOption.LABELS;
-import static com.google.gerrit.common.changes.ListChangesOption.MESSAGES;
-import static com.google.gerrit.common.changes.ListChangesOption.REVIEWED;
+import static com.google.gerrit.extensions.common.ListChangesOption.ALL_COMMITS;
+import static com.google.gerrit.extensions.common.ListChangesOption.ALL_FILES;
+import static com.google.gerrit.extensions.common.ListChangesOption.ALL_REVISIONS;
+import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_ACTIONS;
+import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_COMMIT;
+import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_FILES;
+import static com.google.gerrit.extensions.common.ListChangesOption.CURRENT_REVISION;
+import static com.google.gerrit.extensions.common.ListChangesOption.DETAILED_ACCOUNTS;
+import static com.google.gerrit.extensions.common.ListChangesOption.DETAILED_LABELS;
+import static com.google.gerrit.extensions.common.ListChangesOption.DOWNLOAD_COMMANDS;
+import static com.google.gerrit.extensions.common.ListChangesOption.DRAFT_COMMENTS;
+import static com.google.gerrit.extensions.common.ListChangesOption.LABELS;
+import static com.google.gerrit.extensions.common.ListChangesOption.MESSAGES;
+import static com.google.gerrit.extensions.common.ListChangesOption.REVIEWED;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
+import com.google.common.base.Optional;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
@@ -46,13 +47,18 @@
 import com.google.common.collect.SetMultimap;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Table;
-import com.google.gerrit.common.changes.ListChangesOption;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.LabelValue;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.extensions.common.ActionInfo;
+import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.FetchInfo;
+import com.google.gerrit.extensions.common.GitPerson;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -74,16 +80,17 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.actions.ActionInfo;
 import com.google.gerrit.server.extensions.webui.UiActions;
 import com.google.gerrit.server.git.LabelNormalizer;
 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.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
@@ -130,12 +137,14 @@
   private final AnonymousUser anonymous;
   private final IdentifiedUser.GenericFactory userFactory;
   private final ProjectControl.GenericFactory projectControlFactory;
+  private final ChangeData.Factory changeDataFactory;
   private final PatchSetInfoFactory patchSetInfoFactory;
+  private final ChangesCollection changes;
   private final FileInfoJson fileInfoJson;
   private final AccountInfo.Loader.Factory accountLoaderFactory;
   private final DynamicMap<DownloadScheme> downloadSchemes;
   private final DynamicMap<DownloadCommand> downloadCommands;
-  private final DynamicMap<RestView<ChangeResource>> changes;
+  private final DynamicMap<RestView<ChangeResource>> changeViews;
   private final Revisions revisions;
 
   private EnumSet<ListChangesOption> options;
@@ -152,12 +161,14 @@
       AnonymousUser au,
       IdentifiedUser.GenericFactory uf,
       ProjectControl.GenericFactory pcf,
+      ChangeData.Factory cdf,
       PatchSetInfoFactory psi,
+      ChangesCollection changes,
       FileInfoJson fileInfoJson,
       AccountInfo.Loader.Factory ailf,
       DynamicMap<DownloadScheme> downloadSchemes,
       DynamicMap<DownloadCommand> downloadCommands,
-      DynamicMap<RestView<ChangeResource>> changes,
+      DynamicMap<RestView<ChangeResource>> changeViews,
       Revisions revisions) {
     this.db = db;
     this.labelNormalizer = ln;
@@ -165,12 +176,14 @@
     this.anonymous = au;
     this.userFactory = uf;
     this.projectControlFactory = pcf;
+    this.changeDataFactory = cdf;
     this.patchSetInfoFactory = psi;
+    this.changes = changes;
     this.fileInfoJson = fileInfoJson;
     this.accountLoaderFactory = ailf;
     this.downloadSchemes = downloadSchemes;
     this.downloadCommands = downloadCommands;
-    this.changes = changes;
+    this.changeViews = changeViews;
     this.revisions = revisions;
 
     options = EnumSet.noneOf(ListChangesOption.class);
@@ -196,42 +209,51 @@
   }
 
   public ChangeInfo format(ChangeResource rsrc) throws OrmException {
-    return format(new ChangeData(rsrc.getControl()));
+    return format(changeDataFactory.create(db.get(), rsrc.getControl()));
   }
 
   public ChangeInfo format(Change change) throws OrmException {
-    return format(new ChangeData(change));
+    return format(changeDataFactory.create(db.get(), change));
   }
 
   public ChangeInfo format(Change.Id id) throws OrmException {
-    return format(new ChangeData(id));
+    return format(changeDataFactory.create(db.get(), id));
   }
 
   public ChangeInfo format(ChangeData cd) throws OrmException {
-    List<ChangeData> tmp = ImmutableList.of(cd);
-    return formatList2(ImmutableList.of(tmp)).get(0).get(0);
+    return format(cd, Optional.<PatchSet.Id> absent());
+  }
+
+  private ChangeInfo format(ChangeData cd, Optional<PatchSet.Id> limitToPsId)
+      throws OrmException {
+    accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
+    if (has(REVIEWED)) {
+      ensureReviewedLoaded(Collections.singleton(cd));
+    }
+    ChangeInfo res = toChangeInfo(cd, limitToPsId);
+    accountLoader.fill();
+    return res;
   }
 
   public ChangeInfo format(RevisionResource rsrc) throws OrmException {
-    ChangeData cd = new ChangeData(rsrc.getControl());
-    cd.limitToPatchSets(ImmutableList.of(rsrc.getPatchSet().getId()));
-    return format(cd);
+    ChangeData cd = changeDataFactory.create(db.get(), rsrc.getControl());
+    return format(cd, Optional.of(rsrc.getPatchSet().getId()));
   }
 
   public List<List<ChangeInfo>> formatList2(List<List<ChangeData>> in)
       throws OrmException {
     accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
     Iterable<ChangeData> all = Iterables.concat(in);
-    ChangeData.ensureChangeLoaded(db, all);
+    ChangeData.ensureChangeLoaded(all);
     if (has(ALL_REVISIONS)) {
-      ChangeData.ensureAllPatchSetsLoaded(db, all);
+      ChangeData.ensureAllPatchSetsLoaded(all);
     } else {
-      ChangeData.ensureCurrentPatchSetLoaded(db, all);
+      ChangeData.ensureCurrentPatchSetLoaded(all);
     }
     if (has(REVIEWED)) {
       ensureReviewedLoaded(all);
     }
-    ChangeData.ensureCurrentApprovalsLoaded(db, all);
+    ChangeData.ensureCurrentApprovalsLoaded(all);
 
     List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
     Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
@@ -252,7 +274,7 @@
     for (ChangeData cd : changes) {
       ChangeInfo i = out.get(cd.getId());
       if (i == null) {
-        i = toChangeInfo(cd);
+        i = toChangeInfo(cd, Optional.<PatchSet.Id> absent());
         out.put(cd.getId(), i);
       }
       info.add(i);
@@ -260,14 +282,20 @@
     return info;
   }
 
-  private ChangeInfo toChangeInfo(ChangeData cd) throws OrmException {
+  private ChangeInfo toChangeInfo(ChangeData cd,
+      Optional<PatchSet.Id> limitToPsId) throws OrmException {
     ChangeInfo out = new ChangeInfo();
-    Change in = cd.change(db);
+    Change in = cd.change();
     out.project = in.getProject().get();
     out.branch = in.getDest().getShortName();
     out.topic = in.getTopic();
     out.changeId = in.getKey().get();
     out.mergeable = in.getStatus() != Change.Status.MERGED ? in.isMergeable() : null;
+    ChangedLines changedLines = cd.changedLines();
+    if (changedLines != null) {
+      out.insertions = changedLines.insertions;
+      out.deletions = changedLines.deletions;
+    }
     out.subject = in.getSubject();
     out.status = in.getStatus();
     out.owner = accountLoader.get(in.getOwner());
@@ -283,26 +311,28 @@
         && reviewed.contains(cd.getId()) ? true : null;
     out.labels = labelsFor(cd, has(LABELS), has(DETAILED_LABELS));
 
-    Collection<PatchSet.Id> limited = cd.getLimitedPatchSets();
     if (out.labels != null && has(DETAILED_LABELS)) {
       // If limited to specific patch sets but not the current patch set, don't
       // list permitted labels, since users can't vote on those patch sets.
-      if (limited == null || limited.contains(in.currentPatchSetId())) {
-        out.permitted_labels = permittedLabels(cd);
+      if (!limitToPsId.isPresent()
+          || limitToPsId.get().equals(in.currentPatchSetId())) {
+        out.permittedLabels = permittedLabels(cd);
       }
-      out.removable_reviewers = removableReviewers(cd, out.labels.values());
+      out.removableReviewers = removableReviewers(cd, out.labels.values());
     }
-    if (options.contains(MESSAGES)) {
+    if (has(MESSAGES)) {
       out.messages = messages(cd);
     }
     out.finish();
 
-    if (has(ALL_REVISIONS) || has(CURRENT_REVISION) || limited != null) {
-      out.revisions = revisions(cd);
+    if (has(ALL_REVISIONS)
+        || has(CURRENT_REVISION)
+        || limitToPsId.isPresent()) {
+      out.revisions = revisions(cd, limitToPsId);
       if (out.revisions != null) {
-        for (String commit : out.revisions.keySet()) {
-          if (out.revisions.get(commit).isCurrent) {
-            out.current_revision = commit;
+        for (Map.Entry<String, RevisionInfo> entry : out.revisions.entrySet()) {
+          if (entry.getValue().isCurrent) {
+            out.currentRevision = entry.getKey();
             break;
           }
         }
@@ -312,8 +342,8 @@
     if (has(CURRENT_ACTIONS) && userProvider.get().isIdentifiedUser()) {
       out.actions = Maps.newTreeMap();
       for (UiAction.Description d : UiActions.from(
-          changes,
-          new ChangeResource(control(cd)),
+          changeViews,
+          changes.parse(control(cd)),
           userProvider)) {
         out.actions.put(d.getId(), new ActionInfo(d));
       }
@@ -323,22 +353,20 @@
   }
 
   private ChangeControl control(ChangeData cd) throws OrmException {
-    ChangeControl ctrl = cd.changeControl();
-    if (ctrl != null && ctrl.getCurrentUser() == userProvider.get()) {
-      return ctrl;
-    } else if (lastControl != null
+    if (lastControl != null
         && cd.getId().equals(lastControl.getChange().getId())) {
       return lastControl;
     }
-
+    ChangeControl ctrl;
     try {
-      Change change = cd.change(db);
-      if (change == null) {
-        return null;
+      if (cd.hasChangeControl()) {
+        ctrl = cd.changeControl().forUser(userProvider.get());
+      } else {
+        ctrl = projectControls.get(cd.change().getProject())
+            .controlFor(cd.change());
       }
-      ctrl = projectControls.get(change.getProject()).controlFor(change);
-    } catch (ExecutionException e) {
-      return null;
+    } catch (NoSuchChangeException | ExecutionException e) {
+      throw new OrmException(e);
     }
     lastControl = ctrl;
     return ctrl;
@@ -352,7 +380,7 @@
     if (ctl == null) {
       return ImmutableList.of();
     }
-    PatchSet ps = cd.currentPatchSet(db);
+    PatchSet ps = cd.currentPatchSet();
     if (ps == null) {
       return ImmutableList.of();
     }
@@ -372,7 +400,7 @@
     }
 
     LabelTypes labelTypes = ctl.getLabelTypes();
-    if (cd.getChange().getStatus().isOpen()) {
+    if (cd.change().getStatus().isOpen()) {
       return labelsForOpenChange(cd, labelTypes, standard, detailed);
     } else {
       return labelsForClosedChange(cd, labelTypes, standard, detailed);
@@ -392,7 +420,7 @@
         continue;
       }
       if (standard) {
-        for (PatchSetApproval psa : cd.currentApprovals(db)) {
+        for (PatchSetApproval psa : cd.currentApprovals()) {
           if (type.matches(psa)) {
             short val = psa.getValue();
             Account.Id accountId = psa.getAccountId();
@@ -480,18 +508,19 @@
     // All users ever added, even if they can't vote on one or all labels.
     Set<Account.Id> allUsers = Sets.newHashSet();
     ListMultimap<PatchSet.Id, PatchSetApproval> allApprovals =
-        cd.allApprovalsMap(db);
+        cd.approvals();
     for (PatchSetApproval psa : allApprovals.values()) {
       allUsers.add(psa.getAccountId());
     }
 
-    List<PatchSetApproval> currentList = labelNormalizer.normalize(
-        baseCtrl, allApprovals.get(baseCtrl.getChange().currentPatchSetId()));
+    List<PatchSetApproval> currentList =
+        allApprovals.get(baseCtrl.getChange().currentPatchSetId());
     // Most recent, normalized vote on each label for the current patch set by
     // each user (may be 0).
     Table<Account.Id, String, PatchSetApproval> current = HashBasedTable.create(
         allUsers.size(), baseCtrl.getLabelTypes().getLabelTypes().size());
-    for (PatchSetApproval psa : currentList) {
+    for (PatchSetApproval psa :
+        labelNormalizer.normalize(baseCtrl, currentList).getNormalized()) {
       current.put(psa.getAccountId(), psa.getLabel(), psa);
     }
 
@@ -526,13 +555,13 @@
       LabelTypes labelTypes, boolean standard, boolean detailed)
       throws OrmException {
     Set<Account.Id> allUsers = Sets.newHashSet();
-    for (PatchSetApproval psa : cd.allApprovals(db)) {
+    for (PatchSetApproval psa : cd.approvals().values()) {
       allUsers.add(psa.getAccountId());
     }
 
     Set<String> labelNames = Sets.newHashSet();
     Multimap<Account.Id, PatchSetApproval> current = HashMultimap.create();
-    for (PatchSetApproval a : cd.currentApprovals(db)) {
+    for (PatchSetApproval a : cd.currentApprovals()) {
       LabelType type = labelTypes.byLabel(a.getLabelId());
       if (type != null && a.getValue() != 0) {
         labelNames.add(type.getName());
@@ -560,10 +589,10 @@
           Maps.newHashMapWithExpectedSize(labels.size());
 
       if (detailed) {
-        for (String name : labels.keySet()) {
+        for (Map.Entry<String, LabelInfo> entry : labels.entrySet()) {
           ApprovalInfo ai = approvalInfo(accountId, 0, null);
-          byLabel.put(name, ai);
-          labels.get(name).addApproval(ai);
+          byLabel.put(entry.getKey(), ai);
+          entry.getValue().addApproval(ai);
         }
       }
       for (PatchSetApproval psa : current.get(accountId)) {
@@ -723,8 +752,8 @@
         List<ResultSet<ChangeMessage>> m =
             Lists.newArrayListWithCapacity(batch.size());
         for (ChangeData cd : batch) {
-          PatchSet.Id ps = cd.change(db).currentPatchSetId();
-          if (ps != null && cd.change(db).getStatus().isOpen()) {
+          PatchSet.Id ps = cd.change().currentPatchSetId();
+          if (ps != null && cd.change().getStatus().isOpen()) {
             m.add(db.get().changeMessages().byPatchSet(ps));
           } else {
             m.add(NO_MESSAGES);
@@ -749,7 +778,7 @@
       }
     });
 
-    Account.Id changeOwnerId = cd.change(db).getOwner();
+    Account.Id changeOwnerId = cd.change().getOwner();
     for (ChangeMessage cm : msgs) {
       if (self.equals(cm.getAuthor())) {
         return true;
@@ -760,18 +789,33 @@
     return false;
   }
 
-  private Map<String, RevisionInfo> revisions(ChangeData cd) throws OrmException {
+  private Map<String, RevisionInfo> revisions(ChangeData cd,
+      Optional<PatchSet.Id> limitToPsId) throws OrmException {
     ChangeControl ctl = control(cd);
     if (ctl == null) {
       return null;
     }
 
     Collection<PatchSet> src;
-    if (cd.getLimitedPatchSets() != null || has(ALL_REVISIONS)) {
-      src = cd.patches(db);
+    if (has(ALL_REVISIONS)) {
+      src = cd.patches();
     } else {
-      src = Collections.singletonList(cd.currentPatchSet(db));
+      PatchSet ps;
+      if (limitToPsId.isPresent()) {
+        ps = cd.patch(limitToPsId.get());
+        if (ps == null) {
+          throw new OrmException("missing patch set " + limitToPsId.get());
+        }
+      } else {
+        ps = cd.currentPatchSet();
+        if (ps == null) {
+          throw new OrmException(
+              "missing current patch set for change " + cd.getId());
+        }
+      }
+      src = Collections.singletonList(ps);
     }
+
     Map<String, RevisionInfo> res = Maps.newLinkedHashMap();
     for (PatchSet in : src) {
       if (ctl.isPatchVisible(in, db.get())) {
@@ -784,7 +828,7 @@
   private RevisionInfo toRevisionInfo(ChangeData cd, PatchSet in)
       throws OrmException {
     RevisionInfo out = new RevisionInfo();
-    out.isCurrent = in.getId().equals(cd.change(db).currentPatchSetId());
+    out.isCurrent = in.getId().equals(cd.change().currentPatchSetId());
     out._number = in.getId().get();
     out.draft = in.isDraft() ? true : null;
     out.fetch = makeFetchMap(cd, in);
@@ -799,7 +843,7 @@
 
     if (has(ALL_FILES) || (out.isCurrent && has(CURRENT_FILES))) {
       try {
-        out.files = fileInfoJson.toFileInfoMap(cd.change(db), in);
+        out.files = fileInfoJson.toFileInfoMap(cd.change(), in);
         out.files.remove(Patch.COMMIT_MSG);
       } catch (PatchListNotAvailableException e) {
         log.warn("Cannot load PatchList " + in.getId(), e);
@@ -812,7 +856,7 @@
       out.actions = Maps.newTreeMap();
       for (UiAction.Description d : UiActions.from(
           revisions,
-          new RevisionResource(new ChangeResource(control(cd)), in),
+          new RevisionResource(changes.parse(control(cd)), in),
           userProvider)) {
         out.actions.put(d.getId(), new ActionInfo(d));
       }
@@ -881,7 +925,7 @@
           DownloadCommand command = e2.getProvider().get();
           String c = command.getCommand(scheme, projectName, refName);
           if (c != null) {
-            fetchInfo.addCommand(commandName, c);
+            addCommand(fetchInfo, commandName, c);
           }
         }
       }
@@ -890,6 +934,13 @@
     return r;
   }
 
+  private void addCommand(FetchInfo fetchInfo, String commandName, String c) {
+    if (fetchInfo.commands == null) {
+      fetchInfo.commands = Maps.newTreeMap();
+    }
+    fetchInfo.commands.put(commandName, c);
+  }
+
   private static GitPerson toGitPerson(UserIdentity committer) {
     GitPerson p = new GitPerson();
     p.name = committer.getName();
@@ -900,33 +951,35 @@
   }
 
   public static class ChangeInfo {
-    final String kind = "gerritcodereview#change";
-    String id;
-    String project;
-    String branch;
-    String topic;
+    public final String kind = "gerritcodereview#change";
+    public String id;
+    public String project;
+    public String branch;
+    public String topic;
     public String changeId;
     public String subject;
-    Change.Status status;
-    Timestamp created;
-    Timestamp updated;
-    Boolean starred;
-    Boolean reviewed;
-    Boolean mergeable;
+    public Change.Status status;
+    public Timestamp created;
+    public Timestamp updated;
+    public Boolean starred;
+    public Boolean reviewed;
+    public Boolean mergeable;
+    public Integer insertions;
+    public Integer deletions;
 
     public String _sortkey;
     public int _number;
 
-    AccountInfo owner;
+    public AccountInfo owner;
 
-    Map<String, ActionInfo> actions;
-    Map<String, LabelInfo> labels;
-    Map<String, Collection<String>> permitted_labels;
-    Collection<AccountInfo> removable_reviewers;
-    Collection<ChangeMessageInfo> messages;
+    public Map<String, ActionInfo> actions;
+    public Map<String, LabelInfo> labels;
+    public Map<String, Collection<String>> permittedLabels;
+    public Collection<AccountInfo> removableReviewers;
+    public Collection<ChangeMessageInfo> messages;
 
-    String current_revision;
-    Map<String, RevisionInfo> revisions;
+    public String currentRevision;
+    public Map<String, RevisionInfo> revisions;
     public Boolean _moreChanges;
 
     void finish() {
@@ -937,52 +990,6 @@
     }
   }
 
-  static class RevisionInfo {
-    private transient boolean isCurrent;
-    Boolean draft;
-    Boolean hasDraftComments;
-    int _number;
-    Map<String, FetchInfo> fetch;
-    CommitInfo commit;
-    Map<String, FileInfoJson.FileInfo> files;
-    Map<String, ActionInfo> actions;
-  }
-
-  static class FetchInfo {
-    String url;
-    String ref;
-    Map<String, String> commands;
-
-    FetchInfo(String url, String ref) {
-      this.url = url;
-      this.ref = ref;
-    }
-
-    void addCommand(String name, String command) {
-      if (commands == null) {
-        commands = Maps.newTreeMap();
-      }
-      commands.put(name, command);
-    }
-  }
-
-  static class GitPerson {
-    String name;
-    String email;
-    Timestamp date;
-    int tz;
-  }
-
-  public static class CommitInfo {
-    final String kind = "gerritcodereview#commit";
-    String commit;
-    List<CommitInfo> parents;
-    GitPerson author;
-    GitPerson committer;
-    String subject;
-    String message;
-  }
-
   public static class LabelInfo {
     transient SubmitRecord.Label.Status _status;
 
@@ -1006,20 +1013,20 @@
     }
   }
 
-  static class ApprovalInfo extends AccountInfo {
-    Integer value;
-    Timestamp date;
+  public static class ApprovalInfo extends AccountInfo {
+    public Integer value;
+    public Timestamp date;
 
     ApprovalInfo(Account.Id id) {
       super(id);
     }
   }
 
-  static class ChangeMessageInfo {
-    String id;
-    AccountInfo author;
-    Timestamp date;
-    String message;
-    Integer _revisionNumber;
+  public static class ChangeMessageInfo {
+    public String id;
+    public AccountInfo author;
+    public Timestamp date;
+    public String message;
+    public Integer _revisionNumber;
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKind.java
similarity index 64%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKind.java
index c48f968..c6e088f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKind.java
@@ -12,14 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.server.change;
 
-public class GroupInfo {
-  public String id;
-  public String name;
-  public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+/** Operation performed by a change relative to its parent. */
+public enum ChangeKind {
+  /** Nontrivial content changes. */
+  REWORK,
+
+  /** Conflict-free merge between the new parent and the prior patch set. */
+  TRIVIAL_REBASE,
+
+  /** Same tree and same parents. */
+  NO_CODE_CHANGE;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
new file mode 100644
index 0000000..f6e5773
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
@@ -0,0 +1,195 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Objects;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Cache of {@link ChangeKind} per commit.
+ * <p>
+ * This is immutable conditioned on the merge strategy (unless the JGit strategy
+ * implementation changes, which might invalidate old entries).
+ */
+public class ChangeKindCache {
+  private static final Logger log =
+      LoggerFactory.getLogger(ChangeKindCache.class);
+
+  private static final String ID_CACHE = "change_kind";
+
+  public static Module module() {
+    return new CacheModule() {
+      @Override
+      protected void configure() {
+        cache(ID_CACHE,
+            Key.class,
+            ChangeKind.class)
+          .maximumWeight(0)
+          .loader(Loader.class);
+      }
+    };
+  }
+
+  public static class Key implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final ObjectId prior;
+    private final ObjectId next;
+    private final String strategyName;
+    private transient Repository repo;
+
+    private Key(ObjectId prior, ObjectId next, String strategyName,
+        Repository repo) {
+      this.prior = prior.copy();
+      this.next = next.copy();
+      this.strategyName = strategyName;
+      this.repo = repo;
+    }
+
+    public ObjectId getPrior() {
+      return prior;
+    }
+
+    public ObjectId getNext() {
+      return next;
+    }
+
+    public String getStrategyName() {
+      return strategyName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof Key) {
+        Key k = (Key) o;
+        return Objects.equal(prior, k.prior)
+            && Objects.equal(next, k.next)
+            && Objects.equal(strategyName, k.strategyName);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(prior, next, strategyName);
+    }
+  }
+
+  @Singleton
+  private static class Loader extends CacheLoader<Key, ChangeKind> {
+    @Override
+    public ChangeKind load(Key key) throws IOException {
+      RevWalk walk = new RevWalk(key.repo);
+      try {
+        RevCommit prior = walk.parseCommit(key.prior);
+        walk.parseBody(prior);
+        RevCommit next = walk.parseCommit(key.next);
+        walk.parseBody(next);
+
+        if (!next.getFullMessage().equals(prior.getFullMessage())) {
+          if (next.getTree() == prior.getTree() && isSameParents(prior, next)) {
+            return ChangeKind.NO_CODE_CHANGE;
+          } else {
+            return ChangeKind.REWORK;
+          }
+        }
+
+        if (prior.getParentCount() != 1 || next.getParentCount() != 1) {
+          // Trivial rebases done by machine only work well on 1 parent.
+          return ChangeKind.REWORK;
+        }
+
+        if (next.getTree() == prior.getTree() &&
+           isSameParents(prior, next)) {
+          return ChangeKind.TRIVIAL_REBASE;
+        }
+
+        // A trivial rebase can be detected by looking for the next commit
+        // having the same tree as would exist when the prior commit is
+        // cherry-picked onto the next commit's new first parent.
+        ThreeWayMerger merger = MergeUtil.newThreeWayMerger(
+            key.repo, MergeUtil.createDryRunInserter(), key.strategyName);
+        merger.setBase(prior.getParent(0));
+        if (merger.merge(next.getParent(0), prior)
+            && merger.getResultTreeId().equals(next.getTree())) {
+          return ChangeKind.TRIVIAL_REBASE;
+        } else {
+          return ChangeKind.REWORK;
+        }
+      } finally {
+        key.repo = null;
+        walk.release();
+      }
+    }
+
+    private static boolean isSameParents(RevCommit prior, RevCommit next) {
+      if (prior.getParentCount() != next.getParentCount()) {
+        return false;
+      } else if (prior.getParentCount() == 0) {
+        return true;
+      }
+      return prior.getParent(0).equals(next.getParent(0));
+    }
+  }
+
+  private final LoadingCache<Key, ChangeKind> cache;
+  private final boolean useRecursiveMerge;
+
+  @Inject
+  ChangeKindCache(
+      @GerritServerConfig Config serverConfig,
+      @Named(ID_CACHE) LoadingCache<Key, ChangeKind> cache) {
+    this.cache = cache;
+    this.useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
+  }
+
+  public ChangeKind getChangeKind(ProjectState project, Repository repo,
+      ObjectId prior, ObjectId next) {
+    checkNotNull(next, "next");
+    String strategyName = MergeUtil.mergeStrategyName(
+        project.isUseContentMerge(), useRecursiveMerge);
+    try {
+      return cache.get(new Key(prior, next, strategyName, repo));
+    } catch (ExecutionException e) {
+      log.warn("Cannot check trivial rebase of new patch set " + next.name()
+          + " in " + project.getProject().getName(), e);
+      return ChangeKind.REWORK;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
index b0562a9..fb8397f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.TypeLiteral;
@@ -35,7 +36,7 @@
 
   private final ChangeControl control;
 
-  public ChangeResource(ChangeControl control) {
+  ChangeResource(ChangeControl control) {
     this.control = control;
   }
 
@@ -51,6 +52,10 @@
     return getControl().getChange();
   }
 
+  public ChangeNotes getNotes() {
+    return getControl().getNotes();
+  }
+
   @Override
   public String getETag() {
     CurrentUser user = control.getCurrentUser();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
index e93a0d8..f7038549 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -31,7 +31,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import java.io.UnsupportedEncodingException;
 import java.util.Collections;
 import java.util.List;
 
@@ -69,8 +68,7 @@
 
   @Override
   public ChangeResource parse(TopLevelResource root, IdString id)
-      throws ResourceNotFoundException, OrmException,
-      UnsupportedEncodingException {
+      throws ResourceNotFoundException, OrmException {
     List<Change> changes = findChanges(id.encoded());
     if (changes.size() != 1) {
       throw new ResourceNotFoundException(id);
@@ -85,6 +83,16 @@
     return new ChangeResource(control);
   }
 
+  public ChangeResource parse(Change.Id id)
+      throws ResourceNotFoundException, OrmException {
+    return parse(TopLevelResource.INSTANCE,
+        IdString.fromUrl(Integer.toString(id.get())));
+  }
+
+  public ChangeResource parse(ChangeControl control) throws OrmException {
+    return new ChangeResource(control);
+  }
+
   private List<Change> findChanges(String id)
       throws OrmException, ResourceNotFoundException {
     // Try legacy id
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
index 18f45bb..fae8971 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -14,33 +14,35 @@
 
 package com.google.gerrit.server.change;
 
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.api.changes.CherryPickInput;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.CherryPick.Input;
+import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gerrit.server.git.MergeException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.RefControl;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-class CherryPick implements RestModifyView<RevisionResource, Input>,
+import java.io.IOException;
+
+public class CherryPick implements RestModifyView<RevisionResource, CherryPickInput>,
     UiAction<RevisionResource> {
   private final Provider<ReviewDb> dbProvider;
   private final Provider<CherryPickChange> cherryPickChange;
   private final ChangeJson json;
 
-  static class Input {
-    String message;
-    String destination;
-  }
-
   @Inject
   CherryPick(Provider<ReviewDb> dbProvider,
       Provider<CherryPickChange> cherryPickChange,
@@ -51,9 +53,9 @@
   }
 
   @Override
-  public Object apply(RevisionResource revision, Input input)
+  public ChangeInfo apply(RevisionResource revision, CherryPickInput input)
       throws AuthException, BadRequestException, ResourceConflictException,
-      Exception {
+      ResourceNotFoundException, OrmException, IOException, EmailException {
     final ChangeControl control = revision.getControl();
 
     if (input.message == null || input.message.trim().isEmpty()) {
@@ -89,6 +91,8 @@
       throw new BadRequestException(e.getMessage());
     } catch (MergeException  e) {
       throw new ResourceConflictException(e.getMessage());
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage());
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index a5c7c69..3915449 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectState;
@@ -195,8 +196,10 @@
       RefControl refControl, IdentifiedUser uploader)
       throws InvalidChangeOperationException, IOException, OrmException,
       NoSuchChangeException {
+    final ChangeControl changeControl =
+        refControl.getProjectControl().controlFor(change);
     final PatchSetInserter inserter = patchSetInserterFactory
-        .create(git, revWalk, refControl, currentUser, change, cherryPickCommit);
+        .create(git, revWalk, changeControl, cherryPickCommit);
     final PatchSet.Id newPatchSetId = inserter.getPatchSetId();
     final PatchSet current = db.patchSets().get(change.currentPatchSetId());
     inserter
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
index 229e072..afd0b85 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraft.java
@@ -16,9 +16,7 @@
 
 import com.google.common.base.Strings;
 import com.google.gerrit.common.changes.Side;
-import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.Url;
@@ -44,7 +42,7 @@
 
   @Override
   public Response<CommentInfo> apply(RevisionResource rsrc, Input in)
-      throws AuthException, BadRequestException, ResourceConflictException, OrmException {
+      throws BadRequestException, OrmException {
     if (Strings.isNullOrEmpty(in.path)) {
       throw new BadRequestException("path must be non-empty");
     } else if (in.message == null || in.message.trim().isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
index 0d5898e..588c372 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraft.java
@@ -36,7 +36,8 @@
   }
 
   @Override
-  public Object apply(DraftResource rsrc, Input input) throws OrmException {
+  public Response<CommentInfo> apply(DraftResource rsrc, Input input)
+      throws OrmException {
     db.get().patchComments().delete(Collections.singleton(rsrc.getComment()));
     return Response.none();
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
index 767d5ee..59b52a1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
@@ -24,15 +24,15 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.change.DeleteDraftChange.Input;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import org.eclipse.jgit.lib.Config;
+
 import java.io.IOException;
 
 public class DeleteDraftChange implements
@@ -41,24 +41,21 @@
   }
 
   protected final Provider<ReviewDb> dbProvider;
-  private final GitRepositoryManager gitManager;
-  private final GitReferenceUpdated gitRefUpdated;
-  private final ChangeIndexer indexer;
+  private final ChangeUtil changeUtil;
+  private final boolean allowDrafts;
 
   @Inject
   public DeleteDraftChange(Provider<ReviewDb> dbProvider,
-      GitRepositoryManager gitManager,
-      GitReferenceUpdated gitRefUpdated,
       PatchSetInfoFactory patchSetInfoFactory,
-      ChangeIndexer indexer) {
+      ChangeUtil changeUtil,
+      @GerritServerConfig Config cfg) {
     this.dbProvider = dbProvider;
-    this.gitManager = gitManager;
-    this.gitRefUpdated = gitRefUpdated;
-    this.indexer = indexer;
+    this.changeUtil = changeUtil;
+    this.allowDrafts = cfg.getBoolean("change", "allowDrafts", true);
   }
 
   @Override
-  public Object apply(ChangeResource rsrc, Input input)
+  public Response<?> apply(ChangeResource rsrc, Input input)
       throws ResourceConflictException, AuthException,
       ResourceNotFoundException, OrmException, IOException {
     if (rsrc.getChange().getStatus() != Status.DRAFT) {
@@ -69,9 +66,12 @@
       throw new AuthException("Not permitted to delete this draft change");
     }
 
+    if (!allowDrafts) {
+      throw new ResourceConflictException("Draft workflow is disabled.");
+    }
+
     try {
-      ChangeUtil.deleteDraftChange(rsrc.getChange().getId(),
-          gitManager, gitRefUpdated, dbProvider.get(), indexer);
+      changeUtil.deleteDraftChange(rsrc.getChange().getId());
     } catch (NoSuchChangeException e) {
       throw new ResourceNotFoundException(e.getMessage());
     }
@@ -85,7 +85,8 @@
       return new UiAction.Description()
         .setTitle(String.format("Delete draft change %d",
             rsrc.getChange().getChangeId()))
-        .setVisible(rsrc.getChange().getStatus() == Status.DRAFT
+        .setVisible(allowDrafts
+            && rsrc.getChange().getStatus() == Status.DRAFT
             && rsrc.getControl().canDeleteDraft(dbProvider.get()));
     } catch (OrmException e) {
       throw new IllegalStateException(e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
index f4980f9..7b9b3d9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
@@ -26,9 +26,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.change.DeleteDraftPatchSet.Input;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -37,6 +35,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import org.eclipse.jgit.lib.Config;
+
 import java.io.IOException;
 
 public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Input>,
@@ -45,28 +45,25 @@
   }
 
   protected final Provider<ReviewDb> dbProvider;
-  private final GitRepositoryManager gitManager;
-  private final GitReferenceUpdated gitRefUpdated;
   private final PatchSetInfoFactory patchSetInfoFactory;
-  private final ChangeIndexer indexer;
+  private final ChangeUtil changeUtil;
+  private final boolean allowDrafts;
 
   @Inject
   public DeleteDraftPatchSet(Provider<ReviewDb> dbProvider,
-      GitRepositoryManager gitManager,
-      GitReferenceUpdated gitRefUpdated,
       PatchSetInfoFactory patchSetInfoFactory,
-      ChangeIndexer indexer) {
+      ChangeUtil changeUtil,
+      @GerritServerConfig Config cfg) {
     this.dbProvider = dbProvider;
-    this.gitManager = gitManager;
-    this.gitRefUpdated = gitRefUpdated;
     this.patchSetInfoFactory = patchSetInfoFactory;
-    this.indexer = indexer;
+    this.changeUtil = changeUtil;
+    this.allowDrafts = cfg.getBoolean("change", "allowDrafts", true);
   }
 
   @Override
-  public Object apply(RevisionResource rsrc, Input input)
-      throws ResourceNotFoundException, AuthException, OrmException,
-      IOException, ResourceConflictException {
+  public Response<?> apply(RevisionResource rsrc, Input input)
+      throws AuthException, ResourceNotFoundException,
+      ResourceConflictException, OrmException, IOException {
     PatchSet patchSet = rsrc.getPatchSet();
     PatchSet.Id patchSetId = patchSet.getId();
     Change change = rsrc.getChange();
@@ -75,6 +72,10 @@
       throw new ResourceConflictException("Patch set is not a draft.");
     }
 
+    if (!allowDrafts) {
+      throw new ResourceConflictException("Draft workflow is disabled.");
+    }
+
     if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
       throw new AuthException("Not permitted to delete this draft patch set");
     }
@@ -93,7 +94,8 @@
       return new UiAction.Description()
         .setTitle(String.format("Delete draft revision %d",
             rsrc.getPatchSet().getPatchSetId()))
-        .setVisible(rsrc.getPatchSet().isDraft()
+        .setVisible(allowDrafts
+            && rsrc.getPatchSet().isDraft()
             && rsrc.getControl().canDeleteDraft(dbProvider.get())
             && psCount > 1);
     } catch (OrmException e) {
@@ -104,8 +106,7 @@
   private void deleteDraftPatchSet(PatchSet patchSet, Change change)
       throws ResourceNotFoundException, OrmException, IOException {
     try {
-      ChangeUtil.deleteOnlyDraftPatchSet(patchSet,
-          change, gitManager, gitRefUpdated, dbProvider.get());
+      changeUtil.deleteOnlyDraftPatchSet(patchSet, change);
     } catch (NoSuchChangeException e) {
       throw new ResourceNotFoundException(e.getMessage());
     }
@@ -133,8 +134,7 @@
   private void deleteDraftChange(PatchSet.Id patchSetId)
       throws OrmException, IOException, ResourceNotFoundException {
     try {
-      ChangeUtil.deleteDraftChange(patchSetId,
-          gitManager, gitRefUpdated, dbProvider.get(), indexer);
+      changeUtil.deleteDraftChange(patchSetId);
     } catch (NoSuchChangeException e) {
       throw new ResourceNotFoundException(e.getMessage());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
index c58fc6c..b4dcdd7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -23,17 +23,23 @@
 import com.google.gerrit.extensions.restapi.RestModifyView;
 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.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.DeleteReviewer.Input;
 import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
 
 public class DeleteReviewer implements RestModifyView<ReviewerResource, Input> {
@@ -41,27 +47,50 @@
   }
 
   private final Provider<ReviewDb> dbProvider;
+  private final ChangeUpdate.Factory updateFactory;
+  private final ApprovalsUtil approvalsUtil;
   private final ChangeIndexer indexer;
+  private final IdentifiedUser.GenericFactory userFactory;
 
   @Inject
-  DeleteReviewer(Provider<ReviewDb> dbProvider, ChangeIndexer indexer) {
+  DeleteReviewer(Provider<ReviewDb> dbProvider,
+      ChangeUpdate.Factory updateFactory,
+      ApprovalsUtil approvalsUtil,
+      ChangeIndexer indexer,
+      IdentifiedUser.GenericFactory userFactory) {
     this.dbProvider = dbProvider;
+    this.updateFactory = updateFactory;
+    this.approvalsUtil = approvalsUtil;
     this.indexer = indexer;
+    this.userFactory = userFactory;
   }
 
   @Override
-  public Object apply(ReviewerResource rsrc, Input input)
+  public Response<?> apply(ReviewerResource rsrc, Input input)
       throws AuthException, ResourceNotFoundException, OrmException,
       IOException {
     ChangeControl control = rsrc.getControl();
     Change.Id changeId = rsrc.getChange().getId();
     ReviewDb db = dbProvider.get();
+    ChangeUpdate update = updateFactory.create(rsrc.getControl());
+
+    StringBuilder msg = new StringBuilder();
     db.changes().beginTransaction(changeId);
     try {
       List<PatchSetApproval> del = Lists.newArrayList();
       for (PatchSetApproval a : approvals(db, rsrc)) {
         if (control.canRemoveReviewer(a)) {
           del.add(a);
+          if (a.getPatchSetId().equals(control.getChange().currentPatchSetId())
+              && a.getValue() != 0) {
+            if (msg.length() == 0) {
+              msg.append("Removed the following approvals:\n\n");
+            }
+            msg.append("* ")
+                .append(a.getLabel()).append(formatLabelValue(a.getValue()))
+                .append(" by ").append(userFactory.create(a.getAccountId()).getNameEmail())
+                .append("\n");
+          }
         } else {
           throw new AuthException("delete not permitted");
         }
@@ -71,19 +100,40 @@
       }
       ChangeUtil.bumpRowVersionNotLastUpdatedOn(rsrc.getChange().getId(), db);
       db.patchSetApprovals().delete(del);
+      update.removeReviewer(rsrc.getUser().getAccountId());
+
+      if (msg.length() > 0) {
+        ChangeMessage changeMessage =
+            new ChangeMessage(new ChangeMessage.Key(rsrc.getChange().getId(),
+                ChangeUtil.messageUUID(db)),
+                ((IdentifiedUser) control.getCurrentUser()).getAccountId(),
+                TimeUtil.nowTs(), rsrc.getChange().currentPatchSetId());
+        changeMessage.setMessage(msg.toString());
+        db.changeMessages().insert(Collections.singleton(changeMessage));
+      }
+
       db.commit();
     } finally {
       db.rollback();
     }
-    indexer.index(rsrc.getChange());
+    update.commit();
+    indexer.index(db, rsrc.getChange());
     return Response.none();
   }
 
+  private static String formatLabelValue(short value) {
+    if (value > 0) {
+      return "+" + value;
+    } else {
+      return Short.toString(value);
+    }
+  }
+
   private Iterable<PatchSetApproval> approvals(ReviewDb db,
       ReviewerResource rsrc) throws OrmException {
     final Account.Id user = rsrc.getUser().getAccountId();
     return Iterables.filter(
-        db.patchSetApprovals().byChange(rsrc.getChange().getId()),
+        approvalsUtil.byChange(db, rsrc.getNotes()).values(),
         new Predicate<PatchSetApproval>() {
           @Override
           public boolean apply(PatchSetApproval input) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java
index a634b7c..3477d45 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java
@@ -23,37 +23,26 @@
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gerrit.server.change.EditMessage.Input;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.mail.CommitMessageEditedSender;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
 
 import java.io.IOException;
 
 class EditMessage implements RestModifyView<RevisionResource, Input>,
     UiAction<RevisionResource> {
-
-  private final Provider<ReviewDb> dbProvider;
-  private final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory;
-  private final GitRepositoryManager gitManager;
+  private final ChangeUtil changeUtil;
   private final PersonIdent myIdent;
-  private final PatchSetInserter.Factory patchSetInserterFactory;
   private final ChangeJson json;
 
   static class Input {
@@ -62,55 +51,34 @@
   }
 
   @Inject
-  EditMessage(final Provider<ReviewDb> dbProvider,
-      final CommitMessageEditedSender.Factory commitMessageEditedSenderFactory,
-      final GitRepositoryManager gitManager,
-      final PatchSetInserter.Factory patchSetInserterFactory,
-      @GerritPersonIdent final PersonIdent myIdent,
+  EditMessage(ChangeUtil changeUtil,
+      @GerritPersonIdent PersonIdent myIdent,
       ChangeJson json) {
-    this.dbProvider = dbProvider;
-    this.commitMessageEditedSenderFactory = commitMessageEditedSenderFactory;
-    this.gitManager = gitManager;
+    this.changeUtil = changeUtil;
     this.myIdent = myIdent;
-    this.patchSetInserterFactory = patchSetInserterFactory;
     this.json = json;
   }
 
   @Override
   public ChangeInfo apply(RevisionResource rsrc, Input input)
-      throws BadRequestException, ResourceConflictException, EmailException,
-      OrmException, ResourceNotFoundException, IOException {
+      throws BadRequestException, ResourceConflictException,
+      ResourceNotFoundException, EmailException, OrmException, IOException {
     if (Strings.isNullOrEmpty(input.message)) {
       throw new BadRequestException("message must be non-empty");
     }
-
-    final Repository git;
     try {
-      git = gitManager.openRepository(rsrc.getChange().getProject());
-    } catch (RepositoryNotFoundException e) {
-      throw new ResourceNotFoundException(e.getMessage());
-    }
-
-    try {
-      return json.format(ChangeUtil.editCommitMessage(
+      return json.format(changeUtil.editCommitMessage(
+          rsrc.getControl(),
           rsrc.getPatchSet().getId(),
-          rsrc.getControl().getRefControl(),
-          (IdentifiedUser) rsrc.getControl().getCurrentUser(),
-          input.message, dbProvider.get(),
-          commitMessageEditedSenderFactory, git, myIdent,
-          patchSetInserterFactory));
+          input.message,
+          myIdent));
     } catch (InvalidChangeOperationException e) {
       throw new BadRequestException(e.getMessage());
-    } catch (MissingObjectException e) {
-      throw new ResourceConflictException(e.getMessage());
-    } catch (IncorrectObjectTypeException e) {
-      throw new ResourceConflictException(e.getMessage());
-    } catch (PatchSetInfoNotAvailableException e) {
-      throw new ResourceConflictException(e.getMessage());
     } catch (NoSuchChangeException e) {
       throw new ResourceNotFoundException();
-    } finally {
-      git.close();
+    } catch (MissingObjectException | IncorrectObjectTypeException
+        | PatchSetInfoNotAvailableException e) {
+      throw new ResourceConflictException(e.getMessage());
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
index e37eede..bc38039 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -22,7 +23,6 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.change.PostReview.NotifyHandling;
 import com.google.gerrit.server.git.EmailReviewCommentsExecutor;
 import com.google.gerrit.server.git.WorkQueue.Executor;
 import com.google.gerrit.server.mail.CommentSender;
@@ -62,7 +62,7 @@
   private final SchemaFactory<ReviewDb> schemaFactory;
   private final ThreadLocalRequestContext requestContext;
 
-  private final PostReview.NotifyHandling notify;
+  private final NotifyHandling notify;
   private final Change change;
   private final PatchSet patchSet;
   private final Account.Id authorId;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
index 65b96b3..c6b3b5d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -16,10 +16,11 @@
 
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListEntry;
@@ -55,7 +56,7 @@
 
     Map<String, FileInfo> files = Maps.newTreeMap();
     for (PatchListEntry e : list.getPatches()) {
-      FileInfoJson.FileInfo d = new FileInfoJson.FileInfo();
+      FileInfo d = new FileInfo();
       d.status = e.getChangeType() != Patch.ChangeType.MODIFIED
           ? e.getChangeType().getCode() : null;
       d.oldPath = e.getOldName();
@@ -66,7 +67,7 @@
         d.linesDeleted = e.getDeletions() > 0 ? e.getDeletions() : null;
       }
 
-      FileInfoJson.FileInfo o = files.put(e.getNewName(), d);
+      FileInfo o = 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
@@ -85,12 +86,4 @@
     }
     return files;
   }
-
-  static class FileInfo {
-    Character status;
-    Boolean binary;
-    String oldPath;
-    Integer linesInserted;
-    Integer linesDeleted;
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index 98e2ee9..1c40426 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.common.FileInfo;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -33,7 +34,6 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.FileInfoJson.FileInfo;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
@@ -117,13 +117,12 @@
     }
 
     @Override
-    public Object apply(RevisionResource resource)
-        throws ResourceNotFoundException, OrmException,
-        PatchListNotAvailableException, BadRequestException, AuthException {
+    public Response<?> apply(RevisionResource resource) throws AuthException,
+        BadRequestException, ResourceNotFoundException, OrmException {
       if (base != null && reviewed) {
         throw new BadRequestException("cannot combine base and reviewed");
       } else if (reviewed) {
-        return reviewed(resource);
+        return Response.ok(reviewed(resource));
       }
 
       PatchSet basePatchSet = null;
@@ -132,17 +131,21 @@
             resource.getChangeResource(), IdString.fromDecoded(base));
         basePatchSet = baseResource.getPatchSet();
       }
-      Response<Map<String, FileInfo>> r = Response.ok(fileInfoJson.toFileInfoMap(
-          resource.getChange(),
-          resource.getPatchSet(),
-          basePatchSet));
-      if (resource.isCacheable()) {
-        r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+      try {
+        Response<Map<String, FileInfo>> r = Response.ok(fileInfoJson.toFileInfoMap(
+            resource.getChange(),
+            resource.getPatchSet(),
+            basePatchSet));
+        if (resource.isCacheable()) {
+          r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+        }
+        return r;
+      } catch (PatchListNotAvailableException e) {
+        throw new ResourceNotFoundException(e.getMessage());
       }
-      return r;
     }
 
-    private Object reviewed(RevisionResource resource)
+    private List<String> reviewed(RevisionResource resource)
         throws AuthException, OrmException {
       CurrentUser user = self.get();
       if (!(user.isIdentifiedUser())) {
@@ -159,9 +162,7 @@
           if (!o.isEmpty()) {
             try {
               r = copy(Sets.newHashSet(o), old, resource, userId);
-            } catch (IOException e) {
-              log.warn("Cannot copy patch review flags", e);
-            } catch (PatchListNotAvailableException e) {
+            } catch (IOException | PatchListNotAvailableException e) {
               log.warn("Cannot copy patch review flags", e);
             }
             break;
@@ -226,12 +227,12 @@
 
           int op = -1;
           if (oldList.getOldId() != null) {
-            op = tw.addTree(rw.parseCommit(oldList.getOldId()).getTree());
+            op = tw.addTree(rw.parseTree(oldList.getOldId()));
           }
 
           int cp = -1;
           if (curList.getOldId() != null) {
-            cp = tw.addTree(rw.parseCommit(curList.getOldId()).getTree());
+            cp = tw.addTree(rw.parseTree(curList.getOldId()));
           }
 
           while (tw.next()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
new file mode 100644
index 0000000..9a4ab21
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
@@ -0,0 +1,111 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.api.ArchiveCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+
+class GetArchive implements RestReadView<RevisionResource> {
+  private static final Map<String, ArchiveFormat> formats = ArchiveFormat.init();
+  private final GitRepositoryManager repoManager;
+
+  @Option(name = "--format")
+  private String format;
+
+  @Inject
+  GetArchive(GitRepositoryManager repoManager) {
+    this.repoManager = repoManager;
+  }
+
+  @Override
+  public BinaryResult apply(RevisionResource rsrc)
+      throws BadRequestException, IOException {
+    if (Strings.isNullOrEmpty(format)) {
+      throw new BadRequestException("format is not specified");
+    }
+    final ArchiveFormat f = formats.get("." + format);
+    if (f == null) {
+      throw new BadRequestException("unknown archive format");
+    }
+    boolean close = true;
+    final Repository repo = repoManager
+        .openRepository(rsrc.getControl().getProject().getNameKey());
+    try {
+      final RevWalk rw = new RevWalk(repo);
+      try {
+        final RevCommit commit =
+            rw.parseCommit(ObjectId.fromString(rsrc.getPatchSet()
+                .getRevision().get()));
+        BinaryResult bin = new BinaryResult() {
+          @Override
+          public void writeTo(OutputStream out) throws IOException {
+            try {
+              new ArchiveCommand(repo)
+                  .setFormat(f.name())
+                  .setTree(commit.getTree())
+                  .setOutputStream(out).call();
+            } catch (GitAPIException e) {
+              throw new IOException(e);
+            }
+          }
+
+          @Override
+          public void close() throws IOException {
+            rw.release();
+            repo.close();
+          }
+        };
+
+        bin.disableGzip()
+            .setContentType(f.getMimeType())
+            .setAttachmentName(name(f, rw, commit));
+
+        close = false;
+        return bin;
+      } finally {
+        if (close) {
+          rw.release();
+        }
+      }
+    } finally {
+      if (close) {
+        repo.close();
+      }
+    }
+  }
+
+  private static String name(ArchiveFormat format, RevWalk rw, RevCommit commit)
+      throws IOException {
+    return String.format("%s%s",
+        rw.getObjectReader().abbreviate(commit,7).name(),
+        format.getDefaultSuffix());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
index 7213a94..c50a6ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetChange.java
@@ -14,10 +14,11 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.extensions.restapi.CacheControl;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
@@ -28,7 +29,7 @@
 public class GetChange implements RestReadView<ChangeResource> {
   private final ChangeJson json;
 
-  @Option(name = "-o", multiValued = true, usage = "Output options")
+  @Option(name = "-o", usage = "Output options")
   void addOption(ListChangesOption o) {
     json.addOption(o);
   }
@@ -44,15 +45,15 @@
   }
 
   @Override
-  public Object apply(ChangeResource rsrc) throws OrmException {
+  public Response<ChangeInfo> apply(ChangeResource rsrc) throws OrmException {
     return cache(json.format(rsrc));
   }
 
-  Object apply(RevisionResource rsrc) throws OrmException {
+  Response<ChangeInfo> apply(RevisionResource rsrc) throws OrmException {
     return cache(json.format(rsrc));
   }
 
-  private Object cache(Object res) {
+  private Response<ChangeInfo> cache(ChangeInfo res) {
     return Response.ok(res)
         .caching(CacheControl.PRIVATE(0, TimeUnit.SECONDS).setMustRevalidate());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java
index 68b0435..3606eed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetComment.java
@@ -29,7 +29,7 @@
   }
 
   @Override
-  public Object apply(CommentResource rsrc) throws OrmException {
+  public CommentInfo apply(CommentResource rsrc) throws OrmException {
     AccountInfo.Loader accountLoader = accountLoaderFactory.create(true);
     CommentInfo ci = new CommentInfo(rsrc.getComment(), accountLoader);
     accountLoader.fill();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
index 7d29449..679466a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
@@ -14,10 +14,11 @@
 
 package com.google.gerrit.server.change;
 
+import com.google.gerrit.extensions.common.CommitInfo;
 import com.google.gerrit.extensions.restapi.CacheControl;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.change.ChangeJson.CommitInfo;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -34,11 +35,16 @@
 
   @Override
   public Response<CommitInfo> apply(RevisionResource resource)
-      throws OrmException, PatchSetInfoNotAvailableException {
-    Response<CommitInfo> r = Response.ok(json.toCommit(resource.getPatchSet()));
-    if (resource.isCacheable()) {
-      r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+      throws ResourceNotFoundException, OrmException {
+    try {
+      Response<CommitInfo> r =
+          Response.ok(json.toCommit(resource.getPatchSet()));
+      if (resource.isCacheable()) {
+        r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+      }
+      return r;
+    } catch (PatchSetInfoNotAvailableException e) {
+      throw new ResourceNotFoundException(e.getMessage());
     }
-    return r;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
index f0d2297..86a3da6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 
-import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -42,30 +41,35 @@
   @Override
   public BinaryResult apply(FileResource rsrc)
       throws ResourceNotFoundException, IOException {
-    Project.NameKey project =
-        rsrc.getRevision().getControl().getProject().getNameKey();
+    return apply(rsrc.getRevision().getControl().getProject().getNameKey(),
+        rsrc.getRevision().getPatchSet().getRevision().get(),
+        rsrc.getPatchKey().get());
+  }
+
+  public BinaryResult apply(Project.NameKey project, String revstr, String path)
+      throws ResourceNotFoundException, IOException {
     Repository repo = repoManager.openRepository(project);
     try {
       RevWalk rw = new RevWalk(repo);
       try {
         RevCommit commit =
-            rw.parseCommit(ObjectId.fromString(rsrc.getRevision().getPatchSet()
-                .getRevision().get()));
+            rw.parseCommit(repo.resolve(revstr));
         TreeWalk tw =
-            TreeWalk.forPath(rw.getObjectReader(), rsrc.getPatchKey().get(),
+            TreeWalk.forPath(rw.getObjectReader(), path,
                 commit.getTree().getId());
         if (tw == null) {
           throw new ResourceNotFoundException();
         }
         try {
           final ObjectLoader object = repo.open(tw.getObjectId(0));
-          return new BinaryResult() {
+          @SuppressWarnings("resource")
+          BinaryResult result = new BinaryResult() {
             @Override
             public void writeTo(OutputStream os) throws IOException {
               object.copyTo(os);
             }
-          }.setContentLength(object.getSize())
-           .base64();
+          };
+          return result.setContentLength(object.getSize()).base64();
         } finally {
           tw.release();
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
index 936edd6..514c12a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDetail.java
@@ -14,8 +14,10 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
@@ -24,7 +26,7 @@
 public class GetDetail implements RestReadView<ChangeResource> {
   private final GetChange delegate;
 
-  @Option(name = "-o", multiValued = true, usage = "Output options")
+  @Option(name = "-o", usage = "Output options")
   void addOption(ListChangesOption o) {
     delegate.addOption(o);
   }
@@ -44,7 +46,7 @@
   }
 
   @Override
-  public Object apply(ChangeResource rsrc) throws OrmException {
+  public Response<ChangeInfo> apply(ChangeResource rsrc) throws OrmException {
     return delegate.apply(rsrc);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
index 753a70b..7267f7d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.common.data.PatchScript.FileMode;
 import com.google.gerrit.extensions.restapi.CacheControl;
 import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -38,6 +39,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+
 import org.eclipse.jgit.diff.Edit;
 import org.eclipse.jgit.diff.ReplaceEdit;
 import org.kohsuke.args4j.CmdLineException;
@@ -76,8 +78,8 @@
   }
 
   @Override
-  public Object apply(FileResource resource)
-      throws OrmException, NoSuchChangeException, LargeObjectException, ResourceNotFoundException {
+  public Response<Result> apply(FileResource resource)
+      throws ResourceConflictException, ResourceNotFoundException, OrmException {
     PatchSet.Id basePatchSet = null;
     if (base != null) {
       RevisionResource baseResource = revisions.get().parse(
@@ -89,74 +91,82 @@
     prefs.setContext(context);
     prefs.setIntralineDifference(intraline);
 
-    PatchScript ps = patchScriptFactoryFactory.create(
-        resource.getRevision().getControl(),
-        resource.getPatchKey().getFileName(),
-        basePatchSet,
-        resource.getPatchKey().getParentKey(),
-        prefs)
-          .call();
+    try {
+      PatchScript ps = patchScriptFactoryFactory.create(
+          resource.getRevision().getControl(),
+          resource.getPatchKey().getFileName(),
+          basePatchSet,
+          resource.getPatchKey().getParentKey(),
+          prefs)
+            .call();
 
-    Content content = new Content(ps);
-    for (Edit edit : ps.getEdits()) {
-      if (edit.getType() == Edit.Type.EMPTY) {
-        continue;
+      Content content = new Content(ps);
+      for (Edit edit : ps.getEdits()) {
+        if (edit.getType() == Edit.Type.EMPTY) {
+          continue;
+        }
+        content.addCommon(edit.getBeginA());
+
+        checkState(content.nextA == edit.getBeginA(),
+            "nextA = %d; want %d", content.nextA, edit.getBeginA());
+        checkState(content.nextB == edit.getBeginB(),
+            "nextB = %d; want %d", content.nextB, edit.getBeginB());
+        switch (edit.getType()) {
+          case DELETE:
+          case INSERT:
+          case REPLACE:
+            List<Edit> internalEdit = edit instanceof ReplaceEdit
+              ? ((ReplaceEdit) edit).getInternalEdits()
+              : null;
+            content.addDiff(edit.getEndA(), edit.getEndB(), internalEdit);
+            break;
+          case EMPTY:
+          default:
+            throw new IllegalStateException();
+        }
       }
-      content.addCommon(edit.getBeginA());
+      content.addCommon(ps.getA().size());
 
-      checkState(content.nextA == edit.getBeginA(),
-          "nextA = %d; want %d", content.nextA, edit.getBeginA());
-      checkState(content.nextB == edit.getBeginB(),
-          "nextB = %d; want %d", content.nextB, edit.getBeginB());
-      switch (edit.getType()) {
-        case DELETE:
-        case INSERT:
-        case REPLACE:
-          List<Edit> internalEdit = edit instanceof ReplaceEdit
-            ? ((ReplaceEdit) edit).getInternalEdits()
-            : null;
-          content.addDiff(edit.getEndA(), edit.getEndB(), internalEdit);
-          break;
-        case EMPTY:
-        default:
-          throw new IllegalStateException();
+      Result result = new Result();
+      if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
+        result.metaA = new FileMeta();
+        result.metaA.name = Objects.firstNonNull(ps.getOldName(), ps.getNewName());
+        result.metaA.setContentType(ps.getFileModeA(), ps.getMimeTypeA());
+        result.metaA.lines = ps.getA().size();
       }
-    }
-    content.addCommon(ps.getA().size());
 
-    Result result = new Result();
-    if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
-      result.metaA = new FileMeta();
-      result.metaA.name = Objects.firstNonNull(ps.getOldName(), ps.getNewName());
-      result.metaA.setContentType(ps.getFileModeA(), ps.getMimeTypeA());
-    }
-
-    if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
-      result.metaB = new FileMeta();
-      result.metaB.name = ps.getNewName();
-      result.metaB.setContentType(ps.getFileModeB(), ps.getMimeTypeB());
-    }
-
-    if (intraline) {
-      if (ps.hasIntralineTimeout()) {
-        result.intralineStatus = IntraLineStatus.TIMEOUT;
-      } else if (ps.hasIntralineFailure()) {
-        result.intralineStatus = IntraLineStatus.FAILURE;
-      } else {
-        result.intralineStatus = IntraLineStatus.OK;
+      if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
+        result.metaB = new FileMeta();
+        result.metaB.name = ps.getNewName();
+        result.metaB.setContentType(ps.getFileModeB(), ps.getMimeTypeB());
+        result.metaB.lines = ps.getB().size();
       }
-    }
 
-    result.changeType = ps.getChangeType();
-    if (ps.getPatchHeader().size() > 0) {
-      result.diffHeader = ps.getPatchHeader();
+      if (intraline) {
+        if (ps.hasIntralineTimeout()) {
+          result.intralineStatus = IntraLineStatus.TIMEOUT;
+        } else if (ps.hasIntralineFailure()) {
+          result.intralineStatus = IntraLineStatus.FAILURE;
+        } else {
+          result.intralineStatus = IntraLineStatus.OK;
+        }
+      }
+
+      result.changeType = ps.getChangeType();
+      if (ps.getPatchHeader().size() > 0) {
+        result.diffHeader = ps.getPatchHeader();
+      }
+      result.content = content.lines;
+      Response<Result> r = Response.ok(result);
+      if (resource.isCacheable()) {
+        r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+      }
+      return r;
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage());
+    } catch (LargeObjectException e) {
+      throw new ResourceConflictException(e.getMessage());
     }
-    result.content = content.lines;
-    Response<Result> r = Response.ok(result);
-    if (resource.isCacheable()) {
-      r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
-    }
-    return r;
   }
 
   static class Result {
@@ -171,7 +181,7 @@
   static class FileMeta {
     String name;
     String contentType;
-    String url;
+    Integer lines;
 
     void setContentType(FileMode fileMode, String mimeType) {
       switch (fileMode) {
@@ -193,13 +203,14 @@
   enum IntraLineStatus {
     OK,
     TIMEOUT,
-    FAILURE;
+    FAILURE
   }
 
   private static class Content {
     final List<ContentEntry> lines;
     final SparseFileContent fileA;
     final SparseFileContent fileB;
+    final boolean ignoreWS;
 
     int nextA;
     int nextB;
@@ -208,6 +219,7 @@
       lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
       fileA = ps.getA();
       fileB = ps.getB();
+      ignoreWS = ps.isIgnoreWhitespace();
     }
 
     void addCommon(int end) {
@@ -215,21 +227,39 @@
       if (nextA >= end) {
         return;
       }
-      nextB += end - nextA;
 
       while (nextA < end) {
-        if (fileA.contains(nextA)) {
-          ContentEntry e = entry();
-          e.ab = Lists.newArrayListWithCapacity(end - nextA);
-          for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++) {
-            e.ab.add(fileA.get(i));
-          }
-        } else {
-          int endRegion = Math.min(end,
-              (nextA == 0) ? fileA.first() : fileA.next(nextA - 1));
-          ContentEntry e = entry();
-          e.skip = endRegion - nextA;
+        if (!fileA.contains(nextA)) {
+          int endRegion = Math.min(
+              end,
+              nextA == 0 ? fileA.first() : fileA.next(nextA - 1));
+          int len = endRegion - nextA;
+          entry().skip = len;
           nextA = endRegion;
+          nextB += len;
+          continue;
+        }
+
+        ContentEntry e = null;
+        for (int i = nextA;
+            i == nextA && i < end;
+            i = fileA.next(i), nextA++, nextB++) {
+          if (ignoreWS && fileB.contains(nextB)) {
+            if (e == null || e.common == null) {
+              e = entry();
+              e.a = Lists.newArrayListWithCapacity(end - nextA);
+              e.b = Lists.newArrayListWithCapacity(end - nextA);
+              e.common = true;
+            }
+            e.a.add(fileA.get(nextA));
+            e.b.add(fileB.get(nextB));
+          } else {
+            if (e == null || e.common != null) {
+              e = entry();
+              e.ab = Lists.newArrayListWithCapacity(end - nextA);
+            }
+            e.ab.add(fileA.get(nextA));
+          }
         }
       }
     }
@@ -298,13 +328,16 @@
     // Lines of b.
     List<String> b;
 
-    // A list of changed sections of the of the corresponding line list.
+    // A list of changed sections of the corresponding line list.
     // Each entry is a character <offset, length> pair. The offset is from the
     // beginning of the first line in the list. Also, the offset includes an
     // implied trailing newline character for each line.
     List<List<Integer>> editA;
     List<List<Integer>> editB;
 
+    // a and b are actually common with this whitespace ignore setting.
+    Boolean common;
+
     // Number of lines to skip on both sides.
     Integer skip;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java
index 6b36048..c8a2d43 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDraft.java
@@ -14,15 +14,11 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 
 class GetDraft implements RestReadView<DraftResource> {
   @Override
-  public Object apply(DraftResource rsrc) throws AuthException,
-      BadRequestException, ResourceConflictException, Exception {
+  public CommentInfo apply(DraftResource rsrc) {
     return new CommentInfo(rsrc.getComment(), null);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
index 4c1994b..4a8be84 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
@@ -14,11 +14,10 @@
 
 package com.google.gerrit.server.change;
 
-import static com.google.common.base.Charsets.UTF_8;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -57,84 +56,80 @@
 
   @Override
   public BinaryResult apply(RevisionResource rsrc)
-      throws ResourceNotFoundException, ResourceConflictException {
+      throws ResourceConflictException, IOException {
     Project.NameKey project = rsrc.getControl().getProject().getNameKey();
+    final Repository repo = repoManager.openRepository(project);
     boolean close = true;
     try {
-      final Repository repo = repoManager.openRepository(project);
+      final RevWalk rw = new RevWalk(repo);
       try {
-        final RevWalk rw = new RevWalk(repo);
-        try {
-          final RevCommit commit =
-              rw.parseCommit(ObjectId.fromString(rsrc.getPatchSet()
-                  .getRevision().get()));
-          RevCommit[] parents = commit.getParents();
-          if (parents.length > 1) {
-            throw new ResourceConflictException(
-                "Revision has more than 1 parent.");
-          } else if (parents.length == 0) {
-            throw new ResourceConflictException("Revision has no parent.");
-          }
-          final RevCommit base = parents[0];
-          rw.parseBody(base);
-
-          BinaryResult bin = new BinaryResult() {
-            @Override
-            public void writeTo(OutputStream out) throws IOException {
-              if (zip) {
-                ZipOutputStream zos = new ZipOutputStream(out);
-                ZipEntry e = new ZipEntry(fileName(rw, commit));
-                e.setTime(commit.getCommitTime() * 1000L);
-                zos.putNextEntry(e);
-                format(zos);
-                zos.closeEntry();
-                zos.finish();
-              } else {
-                format(out);
-              }
-            }
-
-            private void format(OutputStream out) throws IOException {
-              out.write(formatEmailHeader(commit).getBytes(UTF_8));
-              DiffFormatter fmt = new DiffFormatter(out);
-              fmt.setRepository(repo);
-              fmt.format(base.getTree(), commit.getTree());
-              fmt.flush();
-            }
-
-            @Override
-            public void close() throws IOException {
-              rw.release();
-              repo.close();
-            }
-          };
-
-          if (zip) {
-            bin.disableGzip()
-               .setContentType("application/zip")
-               .setAttachmentName(fileName(rw, commit) + ".zip");
-          } else {
-            bin.base64()
-               .setContentType("application/mbox")
-               .setAttachmentName(download
-                   ? fileName(rw, commit) + ".base64"
-                   : null);
-          }
-
-          close = false;
-          return bin;
-        } finally {
-          if (close) {
-            rw.release();
-          }
+        final RevCommit commit =
+            rw.parseCommit(ObjectId.fromString(rsrc.getPatchSet()
+                .getRevision().get()));
+        RevCommit[] parents = commit.getParents();
+        if (parents.length > 1) {
+          throw new ResourceConflictException(
+              "Revision has more than 1 parent.");
+        } else if (parents.length == 0) {
+          throw new ResourceConflictException("Revision has no parent.");
         }
+        final RevCommit base = parents[0];
+        rw.parseBody(base);
+
+        BinaryResult bin = new BinaryResult() {
+          @Override
+          public void writeTo(OutputStream out) throws IOException {
+            if (zip) {
+              ZipOutputStream zos = new ZipOutputStream(out);
+              ZipEntry e = new ZipEntry(fileName(rw, commit));
+              e.setTime(commit.getCommitTime() * 1000L);
+              zos.putNextEntry(e);
+              format(zos);
+              zos.closeEntry();
+              zos.finish();
+            } else {
+              format(out);
+            }
+          }
+
+          private void format(OutputStream out) throws IOException {
+            out.write(formatEmailHeader(commit).getBytes(UTF_8));
+            DiffFormatter fmt = new DiffFormatter(out);
+            fmt.setRepository(repo);
+            fmt.format(base.getTree(), commit.getTree());
+            fmt.flush();
+          }
+
+          @Override
+          public void close() throws IOException {
+            rw.release();
+            repo.close();
+          }
+        };
+
+        if (zip) {
+          bin.disableGzip()
+             .setContentType("application/zip")
+             .setAttachmentName(fileName(rw, commit) + ".zip");
+        } else {
+          bin.base64()
+             .setContentType("application/mbox")
+             .setAttachmentName(download
+                 ? fileName(rw, commit) + ".base64"
+                 : null);
+        }
+
+        close = false;
+        return bin;
       } finally {
         if (close) {
-          repo.close();
+          rw.release();
         }
       }
-    } catch (IOException e) {
-      throw new ResourceNotFoundException();
+    } finally {
+      if (close) {
+        repo.close();
+      }
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
index b8441f3..81debde 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
@@ -20,13 +20,13 @@
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.GitPerson;
 import com.google.gerrit.extensions.restapi.RestReadView;
 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.server.ReviewDb;
-import com.google.gerrit.server.change.ChangeJson.CommitInfo;
-import com.google.gerrit.server.change.ChangeJson.GitPerson;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectControl;
@@ -70,7 +70,7 @@
   }
 
   @Override
-  public Object apply(RevisionResource rsrc)
+  public RelatedInfo apply(RevisionResource rsrc)
       throws RepositoryNotFoundException, IOException, OrmException {
     Repository git = gitMgr.openRepository(rsrc.getChange().getProject());
     try {
@@ -201,6 +201,7 @@
           c.add(seenCommit);
           q.addFirst(ps.getRevision().get());
           if (added.add(ps.getId().getParentKey())) {
+            rw.parseBody(c);
             graph.add(new ChangeAndCommit(change, ps, c));
           }
         }
@@ -278,21 +279,24 @@
     return p;
   }
 
-  static class RelatedInfo {
-    List<ChangeAndCommit> changes;
+  public static class RelatedInfo {
+    public List<ChangeAndCommit> changes;
   }
 
-  static class ChangeAndCommit {
-    String changeId;
-    CommitInfo commit;
-    Integer _changeNumber;
-    Integer _revisionNumber;
+  public static class ChangeAndCommit {
+    public String changeId;
+    public CommitInfo commit;
+    public Integer _changeNumber;
+    public Integer _revisionNumber;
+    public Integer _currentRevisionNumber;
 
     ChangeAndCommit(@Nullable Change change, @Nullable PatchSet ps, RevCommit c) {
       if (change != null) {
         changeId = change.getKey().get();
         _changeNumber = change.getChangeId();
         _revisionNumber = ps != null ? ps.getPatchSetId() : null;
+        PatchSet.Id curr = change.currentPatchSetId();
+        _currentRevisionNumber = curr != null ? curr.get() : null;
       }
 
       commit = new CommitInfo();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java
index 3676a1e..b646dd6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java
@@ -14,8 +14,10 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
@@ -30,7 +32,7 @@
   }
 
   @Override
-  public Object apply(RevisionResource rsrc) throws OrmException {
+  public Response<ChangeInfo> apply(RevisionResource rsrc) throws OrmException {
     return delegate.apply(rsrc);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReviewer.java
index 8c41be8..fac4618 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReviewer.java
@@ -15,9 +15,12 @@
 package com.google.gerrit.server.change;
 
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.change.ReviewerJson.ReviewerInfo;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
+import java.util.List;
+
 public class GetReviewer implements RestReadView<ReviewerResource> {
   private final ReviewerJson json;
 
@@ -27,7 +30,7 @@
   }
 
   @Override
-  public Object apply(ReviewerResource rsrc) throws OrmException {
+  public List<ReviewerInfo> apply(ReviewerResource rsrc) throws OrmException {
     return json.format(rsrc);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java
index 96a5c76..53f71fd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetTopic.java
@@ -16,11 +16,10 @@
 
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gwtorm.server.OrmException;
 
 class GetTopic implements RestReadView<ChangeResource> {
   @Override
-  public Object apply(ChangeResource rsrc) throws OrmException {
+  public String apply(ChangeResource rsrc) {
     return Strings.nullToEmpty(rsrc.getChange().getTopic());
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java
index 26e7846..8df6957 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedIn.java
@@ -47,8 +47,8 @@
   }
 
   @Override
-  public Object apply(ChangeResource rsrc) throws OrmException, IOException,
-      BadRequestException, ResourceConflictException {
+  public IncludedInInfo apply(ChangeResource rsrc) throws BadRequestException,
+      ResourceConflictException, OrmException, IOException {
     ChangeControl ctl = rsrc.getControl();
     PatchSet ps =
         db.patchSets().get(ctl.getChange().currentPatchSetId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
index b02f6f0..201ee14 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -14,12 +14,17 @@
 
 package com.google.gerrit.server.change;
 
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.IncludedInDetail;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
@@ -28,13 +33,10 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -47,18 +49,40 @@
 
   public static IncludedInDetail resolve(final Repository repo,
       final RevWalk rw, final RevCommit commit) throws IOException {
+    return new IncludedInResolver(repo, rw, commit).resolve();
+  }
 
-    Set<Ref> tags =
-        new HashSet<Ref>(repo.getRefDatabase().getRefs(Constants.R_TAGS)
-            .values());
-    Set<Ref> branches =
-        new HashSet<Ref>(repo.getRefDatabase().getRefs(Constants.R_HEADS)
-            .values());
-    Set<Ref> allTagsAndBranches = new HashSet<Ref>();
+  public static boolean includedInOne(final Repository repo, final RevWalk rw,
+      final RevCommit commit, final Collection<Ref> refs) throws IOException {
+    return new IncludedInResolver(repo, rw, commit).includedInOne(refs);
+  }
+
+  private final Repository repo;
+  private final RevWalk rw;
+  private final RevCommit target;
+
+  private final RevFlag containsTarget;
+  private Multimap<RevCommit, String> commitToRef;
+  private List<RevCommit> tipsByCommitTime;
+
+  private IncludedInResolver(final Repository repo, final RevWalk rw,
+      final RevCommit target) {
+    this.repo = repo;
+    this.rw = rw;
+    this.target = target;
+    this.containsTarget = rw.newFlag("CONTAINS_TARGET");
+  }
+
+  private IncludedInDetail resolve() throws IOException {
+    RefDatabase refDb = repo.getRefDatabase();
+    Collection<Ref> tags = refDb.getRefs(Constants.R_TAGS).values();
+    Collection<Ref> branches = refDb.getRefs(Constants.R_HEADS).values();
+    List<Ref> allTagsAndBranches = Lists.newArrayListWithCapacity(
+        tags.size() + branches.size());
     allTagsAndBranches.addAll(tags);
     allTagsAndBranches.addAll(branches);
-    Set<Ref> allMatchingTagsAndBranches =
-        includedIn(repo, rw, commit, allTagsAndBranches);
+    parseCommits(allTagsAndBranches);
+    Set<String> allMatchingTagsAndBranches = includedIn(tipsByCommitTime, 0);
 
     IncludedInDetail detail = new IncludedInDetail();
     detail
@@ -68,58 +92,85 @@
     return detail;
   }
 
+  private boolean includedInOne(final Collection<Ref> refs) throws IOException {
+    parseCommits(refs);
+    List<RevCommit> before = Lists.newLinkedList();
+    List<RevCommit> after = Lists.newLinkedList();
+    partition(before, after);
+    // It is highly likely that the target is reachable from the "after" set
+    // Within the "before" set we are trying to handle cases arising from clock skew
+    return !includedIn(after, 1).isEmpty() || !includedIn(before, 1).isEmpty();
+  }
+
   /**
    * Resolves which tip refs include the target commit.
    */
-  private static Set<Ref> includedIn(final Repository repo, final RevWalk rw,
-      final RevCommit target, final Set<Ref> tipRefs) throws IOException,
-      MissingObjectException, IncorrectObjectTypeException {
-
-    Set<Ref> result = new HashSet<Ref>();
-
-    Map<RevCommit, Set<Ref>> tipsAndCommits = parseCommits(repo, rw, tipRefs);
-
-    List<RevCommit> tips = new ArrayList<RevCommit>(tipsAndCommits.keySet());
-    Collections.sort(tips, new Comparator<RevCommit>() {
-      @Override
-      public int compare(RevCommit c1, RevCommit c2) {
-        return c1.getCommitTime() - c2.getCommitTime();
-      }
-    });
-
-    Set<RevCommit> targetReachableFrom = new HashSet<RevCommit>();
-    targetReachableFrom.add(target);
-
+  private Set<String> includedIn(final Collection<RevCommit> tips, int limit)
+      throws IOException, MissingObjectException, IncorrectObjectTypeException {
+    Set<String> result = Sets.newHashSet();
     for (RevCommit tip : tips) {
       boolean commitFound = false;
-      rw.resetRetain(RevFlag.UNINTERESTING);
+      rw.resetRetain(RevFlag.UNINTERESTING, containsTarget);
       rw.markStart(tip);
       for (RevCommit commit : rw) {
-        if (targetReachableFrom.contains(commit)) {
+        if (commit.equals(target) || commit.has(containsTarget)) {
           commitFound = true;
-          targetReachableFrom.add(tip);
-          result.addAll(tipsAndCommits.get(tip));
+          tip.add(containsTarget);
+          result.addAll(commitToRef.get(tip));
           break;
         }
       }
       if (!commitFound) {
         rw.markUninteresting(tip);
+      } else if (0 < limit && limit < result.size()) {
+        break;
       }
     }
-
     return result;
   }
 
   /**
+   * Partition the reference tips into two sets:
+   * <ul>
+   * <li> before = commits with time <  target.getCommitTime()
+   * <li> after  = commits with time >= target.getCommitTime()
+   * </ul>
+   *
+   * Each of the before/after lists is sorted by the the commit time.
+   *
+   * @param before
+   * @param after
+   */
+  private void partition(final List<RevCommit> before,
+      final List<RevCommit> after) {
+    int insertionPoint = Collections.binarySearch(tipsByCommitTime, target,
+        new Comparator<RevCommit>() {
+      @Override
+      public int compare(RevCommit c1, RevCommit c2) {
+        return c1.getCommitTime() - c2.getCommitTime();
+      }
+    });
+    if (insertionPoint < 0) {
+      insertionPoint = - (insertionPoint + 1);
+    }
+    if (0 < insertionPoint) {
+      before.addAll(tipsByCommitTime.subList(0, insertionPoint));
+    }
+    if (insertionPoint < tipsByCommitTime.size()) {
+      after.addAll(tipsByCommitTime.subList(insertionPoint, tipsByCommitTime.size()));
+    }
+  }
+
+  /**
    * Returns the short names of refs which are as well in the matchingRefs list
    * as well as in the allRef list.
    */
-  private static List<String> getMatchingRefNames(Set<Ref> matchingRefs,
-      Set<Ref> allRefs) {
-    List<String> refNames = new ArrayList<String>();
-    for (Ref matchingRef : matchingRefs) {
-      if (allRefs.contains(matchingRef)) {
-        refNames.add(Repository.shortenRefName(matchingRef.getName()));
+  private static List<String> getMatchingRefNames(Set<String> matchingRefs,
+      Collection<Ref> allRefs) {
+    List<String> refNames = Lists.newArrayListWithCapacity(matchingRefs.size());
+    for (Ref r : allRefs) {
+      if (matchingRefs.contains(r.getName())) {
+        refNames.add(Repository.shortenRefName(r.getName()));
       }
     }
     return refNames;
@@ -128,9 +179,11 @@
   /**
    * Parse commit of ref and store the relation between ref and commit.
    */
-  private static Map<RevCommit, Set<Ref>> parseCommits(final Repository repo,
-      final RevWalk rw, final Set<Ref> refs) throws IOException {
-    Map<RevCommit, Set<Ref>> result = new HashMap<RevCommit, Set<Ref>>();
+  private void parseCommits(final Collection<Ref> refs) throws IOException {
+    if (commitToRef != null) {
+      return;
+    }
+    commitToRef = LinkedListMultimap.create();
     for (Ref ref : refs) {
       final RevCommit commit;
       try {
@@ -147,13 +200,18 @@
             + " points to dangling object " + ref.getObjectId());
         continue;
       }
-      Set<Ref> relatedRefs = result.get(commit);
-      if (relatedRefs == null) {
-        relatedRefs = new HashSet<Ref>();
-        result.put(commit, relatedRefs);
-      }
-      relatedRefs.add(ref);
+      commitToRef.put(commit, ref.getName());
     }
-    return result;
+    tipsByCommitTime = Lists.newArrayList(commitToRef.keySet());
+    sortOlderFirst(tipsByCommitTime);
+  }
+
+  private void sortOlderFirst(final List<RevCommit> tips) {
+    Collections.sort(tips, new Comparator<RevCommit>() {
+      @Override
+      public int compare(RevCommit c1, RevCommit c2) {
+        return c1.getCommitTime() - c2.getCommitTime();
+      }
+    });
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
index 6ecc544..f773f2f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
@@ -18,9 +18,11 @@
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.Index.Input;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import java.io.IOException;
 
@@ -29,16 +31,19 @@
   public static class Input {
   }
 
+  private final Provider<ReviewDb> db;
   private final ChangeIndexer indexer;
 
   @Inject
-  Index(ChangeIndexer indexer) {
+  Index(Provider<ReviewDb> db,
+      ChangeIndexer indexer) {
+    this.db = db;
     this.indexer = indexer;
   }
 
   @Override
-  public Object apply(ChangeResource rsrc, Input input) throws IOException {
-    indexer.index(rsrc.getChange());
+  public Response<?> apply(ChangeResource rsrc, Input input) throws IOException {
+    indexer.index(db.get(), rsrc.getChange());
     return Response.none();
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
index 97c7694..cb03724 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListDrafts.java
@@ -19,9 +19,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.changes.Side;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -58,8 +55,8 @@
   }
 
   @Override
-  public Object apply(RevisionResource rsrc) throws AuthException,
-      BadRequestException, ResourceConflictException, Exception {
+  public Map<String, List<CommentInfo>> apply(RevisionResource rsrc)
+      throws OrmException {
     Map<String, List<CommentInfo>> out = Maps.newTreeMap();
     AccountInfo.Loader accountLoader =
         includeAuthorInfo() ? accountLoaderFactory.create(true) : null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java
index 68cc1b4..25d4e2c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java
@@ -15,41 +15,41 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.collect.Maps;
-import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.change.ReviewerJson.ReviewerInfo;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import java.util.List;
 import java.util.Map;
 
 class ListReviewers implements RestReadView<ChangeResource> {
   private final Provider<ReviewDb> dbProvider;
+  private final ApprovalsUtil approvalsUtil;
   private final ReviewerJson json;
   private final ReviewerResource.Factory resourceFactory;
 
   @Inject
   ListReviewers(Provider<ReviewDb> dbProvider,
+      ApprovalsUtil approvalsUtil,
       ReviewerResource.Factory resourceFactory,
       ReviewerJson json) {
     this.dbProvider = dbProvider;
+    this.approvalsUtil = approvalsUtil;
     this.resourceFactory = resourceFactory;
     this.json = json;
   }
 
   @Override
-  public Object apply(ChangeResource rsrc) throws BadRequestException,
-      OrmException {
+  public List<ReviewerInfo> apply(ChangeResource rsrc) throws OrmException {
     Map<Account.Id, ReviewerResource> reviewers = Maps.newLinkedHashMap();
     ReviewDb db = dbProvider.get();
-    Change.Id changeId = rsrc.getChange().getId();
-    for (PatchSetApproval patchSetApproval
-         : db.patchSetApprovals().byChange(changeId)) {
-      Account.Id accountId = patchSetApproval.getAccountId();
+    for (Account.Id accountId
+        : approvalsUtil.getReviewers(db, rsrc.getNotes()).values()) {
       if (!reviewers.containsKey(accountId)) {
         reviewers.put(accountId, resourceFactory.create(rsrc, accountId));
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCheckQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCheckQueue.java
new file mode 100644
index 0000000..6598238
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCheckQueue.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.collect.Sets;
+import com.google.gerrit.reviewdb.client.Change;
+
+import java.util.Collection;
+import java.util.Set;
+
+import javax.inject.Singleton;
+
+@Singleton
+class MergeabilityCheckQueue {
+  private final Set<Change.Id> pending = Sets.newHashSet();
+  private final Set<Change.Id> forcePending = Sets.newHashSet();
+
+  synchronized Set<Change> addAll(Collection<Change> changes, boolean force) {
+    Set<Change> r = Sets.newLinkedHashSetWithExpectedSize(changes.size());
+    for (Change c : changes) {
+      if (force ? forcePending.add(c.getId()) : pending.add(c.getId())) {
+        r.add(c);
+      }
+    }
+    return r;
+  }
+
+  synchronized void updatingMergeabilityFlag(Change change, boolean force) {
+    if (force) {
+      forcePending.remove(change.getId());
+    }
+    pending.remove(change.getId());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
new file mode 100644
index 0000000..0734d12
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
@@ -0,0 +1,362 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.common.base.Function;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AsyncFunction;
+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.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
+import com.google.gerrit.server.change.Mergeable.MergeableInfo;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.git.WorkQueue.Executor;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+public class MergeabilityChecker implements GitReferenceUpdatedListener {
+  private static final Logger log = LoggerFactory
+      .getLogger(MergeabilityChecker.class);
+
+  private static final Function<Exception, IOException> MAPPER =
+      new Function<Exception, IOException>() {
+    @Override
+    public IOException apply(Exception in) {
+      if (in instanceof IOException) {
+        return (IOException) in;
+      } else if (in instanceof ExecutionException
+          && in.getCause() instanceof IOException) {
+        return (IOException) in.getCause();
+      } else {
+        return new IOException(in);
+      }
+    }
+  };
+
+  public class Check {
+    private List<Change> changes;
+    private List<Branch.NameKey> branches;
+    private List<Project.NameKey> projects;
+    private boolean force;
+    private boolean reindex;
+    private boolean interactive;
+
+    private Check() {
+      changes = Lists.newArrayListWithExpectedSize(1);
+      branches = Lists.newArrayListWithExpectedSize(1);
+      projects = Lists.newArrayListWithExpectedSize(1);
+      interactive = true;
+    }
+
+    public Check addChange(Change change) {
+      changes.add(change);
+      return this;
+    }
+
+    public Check addBranch(Branch.NameKey branch) {
+      branches.add(branch);
+      interactive = false;
+      return this;
+    }
+
+    public Check addProject(Project.NameKey project) {
+      projects.add(project);
+      interactive = false;
+      return this;
+    }
+
+    /** Force reindexing regardless of whether mergeable flag was modified. */
+    public Check reindex() {
+      reindex = true;
+      return this;
+    }
+
+    /** Force mergeability check even if change is not stale. */
+    private Check force() {
+      force = true;
+      return this;
+    }
+
+    private ListeningExecutorService getExecutor() {
+      return interactive ? interactiveExecutor : backgroundExecutor;
+    }
+
+    public CheckedFuture<?, IOException> runAsync() {
+      final ListeningExecutorService executor = getExecutor();
+      ListenableFuture<List<Change>> getChanges;
+      if (branches.isEmpty() && projects.isEmpty()) {
+        getChanges = Futures.immediateFuture(changes);
+      } else {
+        getChanges = executor.submit(
+            new Callable<List<Change>>() {
+              @Override
+              public List<Change> call() throws OrmException {
+                return getChanges();
+              }
+            });
+      }
+
+      return Futures.makeChecked(Futures.transform(getChanges,
+          new AsyncFunction<List<Change>, List<Object>>() {
+            @Override
+            public ListenableFuture<List<Object>> apply(List<Change> changes) {
+              List<ListenableFuture<?>> result =
+                  Lists.newArrayListWithCapacity(changes.size());
+              for (final Change c : changes) {
+                ListenableFuture<Boolean> b =
+                    executor.submit(new Task(c, force));
+                if (reindex) {
+                  result.add(Futures.transform(
+                      b, new AsyncFunction<Boolean, Object>() {
+                        @SuppressWarnings("unchecked")
+                        @Override
+                        public ListenableFuture<Object> apply(
+                            Boolean indexUpdated) throws Exception {
+                          if (!indexUpdated) {
+                            return (ListenableFuture<Object>)
+                                indexer.indexAsync(c.getId());
+                          }
+                          return Futures.immediateFuture(null);
+                        }
+                      }));
+                } else {
+                  result.add(b);
+                }
+              }
+              return Futures.allAsList(result);
+            }
+          }), MAPPER);
+    }
+
+    public void run() throws IOException {
+      try {
+        runAsync().checkedGet();
+      } catch (Exception e) {
+        Throwables.propagateIfPossible(e, IOException.class);
+        throw MAPPER.apply(e);
+      }
+    }
+
+    private List<Change> getChanges() throws OrmException {
+      ReviewDb db = schemaFactory.open();
+      try {
+        List<Change> results = Lists.newArrayList();
+        results.addAll(changes);
+        for (Project.NameKey p : projects) {
+          Iterables.addAll(results, db.changes().byProjectOpenAll(p));
+        }
+        for (Branch.NameKey b : branches) {
+          Iterables.addAll(results, db.changes().byBranchOpenAll(b));
+        }
+        return results;
+      } catch (OrmException e) {
+        log.error("Failed to fetch changes for mergeability check", e);
+        throw e;
+      } finally {
+        db.close();
+      }
+    }
+  }
+
+  private final ThreadLocalRequestContext tl;
+  private final SchemaFactory<ReviewDb> schemaFactory;
+  private final IdentifiedUser.GenericFactory identifiedUserFactory;
+  private final ChangeControl.GenericFactory changeControlFactory;
+  private final Provider<Mergeable> mergeable;
+  private final ChangeIndexer indexer;
+  private final ListeningExecutorService backgroundExecutor;
+  private final ListeningExecutorService interactiveExecutor;
+  private final MergeabilityCheckQueue mergeabilityCheckQueue;
+  private final MetaDataUpdate.Server metaDataUpdateFactory;
+
+  @Inject
+  public MergeabilityChecker(ThreadLocalRequestContext tl,
+      SchemaFactory<ReviewDb> schemaFactory,
+      IdentifiedUser.GenericFactory identifiedUserFactory,
+      ChangeControl.GenericFactory changeControlFactory,
+      Provider<Mergeable> mergeable, ChangeIndexer indexer,
+      @MergeabilityChecksExecutor(Priority.BACKGROUND)
+        Executor backgroundExecutor,
+      @MergeabilityChecksExecutor(Priority.INTERACTIVE)
+        Executor interactiveExecutor,
+      MergeabilityCheckQueue mergeabilityCheckQueue,
+      MetaDataUpdate.Server metaDataUpdateFactory) {
+    this.tl = tl;
+    this.schemaFactory = schemaFactory;
+    this.identifiedUserFactory = identifiedUserFactory;
+    this.changeControlFactory = changeControlFactory;
+    this.mergeable = mergeable;
+    this.indexer = indexer;
+    this.backgroundExecutor =
+        MoreExecutors.listeningDecorator(backgroundExecutor);
+    this.interactiveExecutor =
+        MoreExecutors.listeningDecorator(interactiveExecutor);
+    this.mergeabilityCheckQueue = mergeabilityCheckQueue;
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+  }
+
+  public Check newCheck() {
+    return new Check();
+  }
+
+  @Override
+  public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
+    String ref = event.getRefName();
+    if (ref.startsWith(Constants.R_HEADS) || ref.equals(RefNames.REFS_CONFIG)) {
+      Branch.NameKey branch = new Branch.NameKey(
+          new Project.NameKey(event.getProjectName()), ref);
+      newCheck().addBranch(branch).runAsync();
+    }
+    if (ref.equals(RefNames.REFS_CONFIG)) {
+      Project.NameKey p = new Project.NameKey(event.getProjectName());
+      try {
+        ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
+        ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
+        if (recheckMerges(oldCfg, newCfg)) {
+          newCheck().addProject(p).force().runAsync();
+        }
+      } catch (ConfigInvalidException | IOException e) {
+        String msg = "Failed to update mergeability flags for project " + p.get()
+            + " on update of " + RefNames.REFS_CONFIG;
+        log.error(msg, e);
+        throw new RuntimeException(msg, e);
+      }
+    }
+  }
+
+  private boolean recheckMerges(ProjectConfig oldCfg, ProjectConfig newCfg) {
+    if (oldCfg == null || newCfg == null) {
+      return true;
+    }
+    return !oldCfg.getProject().getSubmitType().equals(newCfg.getProject().getSubmitType())
+        || oldCfg.getProject().getUseContentMerge() != newCfg.getProject().getUseContentMerge()
+        || (oldCfg.getRulesId() == null
+            ? newCfg.getRulesId() != null
+            : !oldCfg.getRulesId().equals(newCfg.getRulesId()));
+  }
+
+  private ProjectConfig parseConfig(Project.NameKey p, String idStr)
+      throws IOException, ConfigInvalidException, RepositoryNotFoundException {
+    ObjectId id = ObjectId.fromString(idStr);
+    if (ObjectId.zeroId().equals(id)) {
+      return null;
+    }
+    return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
+  }
+
+  private class Task implements Callable<Boolean> {
+    private final Change change;
+    private final boolean force;
+
+    private ReviewDb reviewDb;
+
+    Task(Change change, boolean force) {
+      this.change = change;
+      this.force = force;
+    }
+
+    @Override
+    public Boolean call() throws Exception {
+      mergeabilityCheckQueue.updatingMergeabilityFlag(change, force);
+
+      RequestContext context = new RequestContext() {
+        @Override
+        public CurrentUser getCurrentUser() {
+          return identifiedUserFactory.create(change.getOwner());
+        }
+
+        @Override
+        public Provider<ReviewDb> getReviewDbProvider() {
+          return new Provider<ReviewDb>() {
+            @Override
+            public ReviewDb get() {
+              if (reviewDb == null) {
+                try {
+                  reviewDb = schemaFactory.open();
+                } catch (OrmException e) {
+                  throw new ProvisionException("Cannot open ReviewDb", e);
+                }
+              }
+              return reviewDb;
+            }
+          };
+        }
+      };
+      RequestContext old = tl.setContext(context);
+      ReviewDb db = context.getReviewDbProvider().get();
+      try {
+        PatchSet ps = db.patchSets().get(change.currentPatchSetId());
+        Mergeable m = mergeable.get();
+        m.setForce(force);
+
+        ChangeControl control =
+            changeControlFactory.controlFor(change.getId(), context.getCurrentUser());
+        MergeableInfo info = m.apply(
+            new RevisionResource(new ChangeResource(control), ps));
+        return change.isMergeable() != info.mergeable;
+      } catch (ResourceConflictException e) {
+        // change is closed
+        return false;
+      } catch (Exception e) {
+        String msg = "Failed to update mergeability flags for project "
+            + change.getDest().getParentKey() + " on update of "
+            + change.getDest().get();
+        log.error(msg, e);
+        throw e;
+      } finally {
+        tl.setContext(old);
+        if (reviewDb != null) {
+          reviewDb.close();
+          reviewDb = null;
+        }
+      }
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutor.java
new file mode 100644
index 0000000..632e6ac
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutor.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.gerrit.server.git.WorkQueue.Executor;
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Marker on the global {@link Executor} used by
+ * {@link MergeabilityChecker}.
+ */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface MergeabilityChecksExecutor {
+  public enum Priority {
+    BACKGROUND, INTERACTIVE;
+  }
+
+  Priority value();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java
new file mode 100644
index 0000000..96c83d3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecksExecutorModule.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.Config;
+
+/** Module providing the {@link MergeabilityChecksExecutor}. */
+public class MergeabilityChecksExecutorModule extends AbstractModule {
+  @Override
+  protected void configure() {
+  }
+
+  @Provides
+  @Singleton
+  @MergeabilityChecksExecutor(Priority.BACKGROUND)
+  public WorkQueue.Executor createMergeabilityChecksExecutor(
+      @GerritServerConfig Config config,
+      WorkQueue queues) {
+    int poolSize = config.getInt("changeMerge", null, "threadPoolSize", 1);
+    return queues.createQueue(poolSize, "MergeabilityChecks");
+  }
+
+  @Provides
+  @Singleton
+  @MergeabilityChecksExecutor(Priority.INTERACTIVE)
+  public WorkQueue.Executor createMergeabilityChecksExecutor(
+      @GerritServerConfig Config config,
+      WorkQueue queues,
+      @MergeabilityChecksExecutor(Priority.BACKGROUND)
+        WorkQueue.Executor backgroundExecutor) {
+    int poolSize =
+        config.getInt("changeMerge", null, "interactiveThreadPoolSize", 1);
+    if (poolSize <= 0) {
+      return backgroundExecutor;
+    }
+    return queues.createQueue(poolSize, "InteractiveMergeabilityChecks");
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
index 595e1c8..fb8c4e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
@@ -27,15 +27,16 @@
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeException;
-import com.google.gerrit.server.git.SubmitStrategyFactory;
+import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
+import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -51,7 +52,6 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
@@ -67,22 +67,30 @@
   private final GitRepositoryManager gitManager;
   private final SubmitStrategyFactory submitStrategyFactory;
   private final Provider<ReviewDb> db;
+  private final ChangeIndexer indexer;
+
+  private boolean force;
 
   @Inject
   Mergeable(TestSubmitType.Get submitType,
       GitRepositoryManager gitManager,
       SubmitStrategyFactory submitStrategyFactory,
-      Provider<ReviewDb> db) {
+      Provider<ReviewDb> db,
+      ChangeIndexer indexer) {
     this.submitType = submitType;
     this.gitManager = gitManager;
     this.submitStrategyFactory = submitStrategyFactory;
     this.db = db;
+    this.indexer = indexer;
+  }
+
+  public void setForce(boolean force) {
+    this.force = force;
   }
 
   @Override
-  public MergeableInfo apply(RevisionResource resource)
-      throws ResourceConflictException, BadRequestException, AuthException,
-      OrmException, RepositoryNotFoundException, IOException {
+  public MergeableInfo apply(RevisionResource resource) throws AuthException,
+      ResourceConflictException, BadRequestException, OrmException, IOException {
     Change change = resource.getChange();
     PatchSet ps = resource.getPatchSet();
     MergeableInfo result = new MergeableInfo();
@@ -101,7 +109,7 @@
     try {
       Map<String, Ref> refs = git.getRefDatabase().getRefs(RefDatabase.ALL);
       Ref ref = refs.get(change.getDest().get());
-      if (isStale(change, ref)) {
+      if (force || isStale(change, ref)) {
         result.mergeable =
             refresh(change, ps, result.submitType, git, refs, ref);
       }
@@ -123,11 +131,11 @@
   }
 
   private boolean refresh(Change change,
-      PatchSet ps,
+      final PatchSet ps,
       Project.SubmitType type,
       Repository git,
       Map<String, Ref> refs,
-      Ref ref) throws IOException, OrmException {
+      final Ref ref) throws IOException, OrmException {
     RevWalk rw = new RevWalk(git) {
       @Override
       protected CodeReviewCommit createCommit(AnyObjectId id) {
@@ -150,7 +158,7 @@
       CodeReviewCommit rev = parse(rw, id);
       rev.add(canMerge);
 
-      boolean mergeable;
+      final boolean mergeable;
       if (ref == null || ref.getObjectId() == null) {
         mergeable = true; // Assume yes on new branch.
       } else {
@@ -169,20 +177,26 @@
             change.getDest()).dryRun(tip, rev);
       }
 
-      Change c = db.get().changes().get(change.getId());
+      Change c = db.get().changes().atomicUpdate(
+        change.getId(),
+        new AtomicUpdate<Change>() {
+          @Override
+          public Change update(Change c) {
+            if (c.getStatus().isOpen()
+                && ps.getId().equals(c.currentPatchSetId())) {
+              c.setMergeable(mergeable);
+              c.setLastSha1MergeTested(toRevId(ref));
+              return c;
+            } else {
+              return null;
+            }
+          }
+        });
       if (c != null) {
-        c.setMergeable(mergeable);
-        c.setLastSha1MergeTested(toRevId(ref));
-        db.get().changes().update(Collections.singleton(c));
+        indexer.index(db.get(), c);
       }
       return mergeable;
-    } catch (MergeException e) {
-      return false;
-    } catch (IOException e) {
-      log.error(String.format(
-          "Cannot merge test change %d", change.getId().get()), e);
-      return false;
-    } catch (NoSuchProjectException e) {
+    } catch (MergeException | IOException | NoSuchProjectException e) {
       log.error(String.format(
           "Cannot merge test change %d", change.getId().get()), e);
       return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 9ed2bee..6880ca2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -82,6 +82,7 @@
     get(REVISION_KIND, "submit_type").to(TestSubmitType.Get.class);
     post(REVISION_KIND, "test.submit_rule").to(TestSubmitRule.class);
     post(REVISION_KIND, "test.submit_type").to(TestSubmitType.class);
+    get(REVISION_KIND, "archive").to(GetArchive.class);
 
     child(REVISION_KIND, "drafts").to(Drafts.class);
     put(REVISION_KIND, "drafts").to(CreateDraft.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index 58b36b2..c059bf4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -17,32 +17,30 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.google.common.collect.Sets;
+import com.google.common.collect.SetMultimap;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
 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.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalCopier;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.events.CommitReceivedEvent;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.ReviewerState;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
-import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.project.RefControl;
 import com.google.gerrit.server.ssh.NoSshInfo;
 import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gerrit.server.util.TimeUtil;
@@ -54,8 +52,6 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.merge.ThreeWayMerger;
-import org.eclipse.jgit.revwalk.FooterLine;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
@@ -64,16 +60,14 @@
 
 import java.io.IOException;
 import java.util.Collections;
-import java.util.List;
-import java.util.Set;
 
 public class PatchSetInserter {
   private static final Logger log =
       LoggerFactory.getLogger(PatchSetInserter.class);
 
   public static interface Factory {
-    PatchSetInserter create(Repository git, RevWalk revWalk, RefControl refControl,
-        IdentifiedUser user, Change change, RevCommit commit);
+    PatchSetInserter create(Repository git, RevWalk revWalk, ChangeControl ctl,
+        RevCommit commit);
   }
 
   /**
@@ -82,29 +76,25 @@
    * validation.
    */
   public static enum ValidatePolicy {
-    GERRIT, RECEIVE_COMMITS, NONE;
-  }
-
-  public static enum ChangeKind {
-    REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE;
+    GERRIT, RECEIVE_COMMITS, NONE
   }
 
   private final ChangeHooks hooks;
-  private final TrackingFooters trackingFooters;
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final ReviewDb db;
-  private final IdentifiedUser user;
+  private final ChangeUpdate.Factory updateFactory;
   private final GitReferenceUpdated gitRefUpdated;
   private final CommitValidators.Factory commitValidatorsFactory;
-  private final ChangeIndexer indexer;
+  private final MergeabilityChecker mergeabilityChecker;
   private final ReplacePatchSetSender.Factory replacePatchSetFactory;
-  private final MergeUtil.Factory mergeUtilFactory;
+  private final ApprovalsUtil approvalsUtil;
+  private final ApprovalCopier approvalCopier;
 
   private final Repository git;
   private final RevWalk revWalk;
   private final RevCommit commit;
-  private final Change change;
-  private final RefControl refControl;
+  private final ChangeControl ctl;
+  private final IdentifiedUser user;
 
   private PatchSet patchSet;
   private ChangeMessage changeMessage;
@@ -118,47 +108,50 @@
 
   @Inject
   public PatchSetInserter(ChangeHooks hooks,
-      TrackingFooters trackingFooters,
       ReviewDb db,
+      ChangeUpdate.Factory updateFactory,
+      ApprovalsUtil approvalsUtil,
+      ApprovalCopier approvalCopier,
       PatchSetInfoFactory patchSetInfoFactory,
       GitReferenceUpdated gitRefUpdated,
       CommitValidators.Factory commitValidatorsFactory,
-      ChangeIndexer indexer,
+      MergeabilityChecker mergeabilityChecker,
       ReplacePatchSetSender.Factory replacePatchSetFactory,
-      MergeUtil.Factory mergeUtilFactory,
       @Assisted Repository git,
       @Assisted RevWalk revWalk,
-      @Assisted RefControl refControl,
-      @Assisted IdentifiedUser user,
-      @Assisted Change change,
+      @Assisted ChangeControl ctl,
       @Assisted RevCommit commit) {
+    checkArgument(ctl.getCurrentUser().isIdentifiedUser(),
+        "only IdentifiedUser may create patch set on change %s",
+        ctl.getChange().getId());
     this.hooks = hooks;
-    this.trackingFooters = trackingFooters;
     this.db = db;
+    this.updateFactory = updateFactory;
+    this.approvalsUtil = approvalsUtil;
+    this.approvalCopier = approvalCopier;
     this.patchSetInfoFactory = patchSetInfoFactory;
-    this.user = user;
     this.gitRefUpdated = gitRefUpdated;
     this.commitValidatorsFactory = commitValidatorsFactory;
-    this.indexer = indexer;
+    this.mergeabilityChecker = mergeabilityChecker;
     this.replacePatchSetFactory = replacePatchSetFactory;
-    this.mergeUtilFactory = mergeUtilFactory;
 
     this.git = git;
     this.revWalk = revWalk;
-    this.refControl = refControl;
-    this.change = change;
     this.commit = commit;
+    this.ctl = ctl;
+    this.user = (IdentifiedUser) ctl.getCurrentUser();
     this.runHooks = true;
     this.sendMail = true;
   }
 
   public PatchSetInserter setPatchSet(PatchSet patchSet) {
+    Change c = ctl.getChange();
     PatchSet.Id psid = patchSet.getId();
-    checkArgument(psid.getParentKey().equals(change.getId()),
-        "patch set %s not for change %s", psid, change.getId());
-    checkArgument(psid.get() > change.currentPatchSetId().get(),
+    checkArgument(psid.getParentKey().equals(c.getId()),
+        "patch set %s not for change %s", psid, c.getId());
+    checkArgument(psid.get() > c.currentPatchSetId().get(),
         "new patch set ID %s is not greater than current patch set ID %s",
-        psid.get(), change.currentPatchSetId().get());
+        psid.get(), c.currentPatchSetId().get());
     this.patchSet = patchSet;
     return this;
   }
@@ -170,13 +163,15 @@
 
   public PatchSetInserter setMessage(String message) throws OrmException {
     changeMessage = new ChangeMessage(
-        new ChangeMessage.Key(change.getId(), ChangeUtil.messageUUID(db)),
+        new ChangeMessage.Key(
+            ctl.getChange().getId(), ChangeUtil.messageUUID(db)),
         user.getAccountId(), TimeUtil.nowTs(), patchSet.getId());
     changeMessage.setMessage(message);
     return this;
   }
 
-  public PatchSetInserter setMessage(ChangeMessage changeMessage) throws OrmException {
+  public PatchSetInserter setMessage(ChangeMessage changeMessage)
+      throws OrmException {
     this.changeMessage = changeMessage;
     return this;
   }
@@ -221,6 +216,7 @@
     init();
     validate();
 
+    Change c = ctl.getChange();
     Change updatedChange;
     RefUpdate ru = git.updateRef(patchSet.getRefName());
     ru.setExpectedOldObjectId(ObjectId.zeroId());
@@ -229,36 +225,30 @@
     if (ru.update(revWalk) != RefUpdate.Result.NEW) {
       throw new IOException(String.format(
           "Failed to create ref %s in %s: %s", patchSet.getRefName(),
-          change.getDest().getParentKey().get(), ru.getResult()));
+          c.getDest().getParentKey().get(), ru.getResult()));
     }
-    gitRefUpdated.fire(change.getProject(), ru);
+    gitRefUpdated.fire(c.getProject(), ru);
 
-    final PatchSet.Id currentPatchSetId = change.currentPatchSetId();
+    final PatchSet.Id currentPatchSetId = c.currentPatchSetId();
 
-    db.changes().beginTransaction(change.getId());
+    ChangeUpdate update = updateFactory.create(ctl, patchSet.getCreatedOn());
+
+    db.changes().beginTransaction(c.getId());
     try {
-      if (!db.changes().get(change.getId()).getStatus().isOpen()) {
+      if (!db.changes().get(c.getId()).getStatus().isOpen()) {
         throw new InvalidChangeOperationException(String.format(
-            "Change %s is closed", change.getId()));
+            "Change %s is closed", c.getId()));
       }
 
       ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
       db.patchSets().insert(Collections.singleton(patchSet));
 
-      final List<PatchSetApproval> oldPatchSetApprovals =
-          db.patchSetApprovals().byChange(change.getId()).toList();
-      final Set<Account.Id> oldReviewers = Sets.newHashSet();
-      final Set<Account.Id> oldCC = Sets.newHashSet();
-      for (PatchSetApproval a : oldPatchSetApprovals) {
-        if (a.getValue() != 0) {
-          oldReviewers.add(a.getAccountId());
-        } else {
-          oldCC.add(a.getAccountId());
-        }
-      }
+      SetMultimap<ReviewerState, Account.Id> oldReviewers = sendMail
+          ? approvalsUtil.getReviewers(db, ctl.getNotes())
+          : null;
 
       updatedChange =
-          db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+          db.changes().atomicUpdate(c.getId(), new AtomicUpdate<Change>() {
             @Override
             public Change update(Change change) {
               if (change.getStatus().isClosed()) {
@@ -279,28 +269,21 @@
           });
       if (updatedChange == null) {
         throw new ChangeModifiedException(String.format(
-            "Change %s was modified", change.getId()));
+            "Change %s was modified", c.getId()));
+      }
+
+      if (messageIsForChange()) {
+        insertMessage(db);
       }
 
       if (copyLabels) {
-        PatchSet priorPatchSet = db.patchSets().get(currentPatchSetId);
-        ObjectId priorCommitId = ObjectId.fromString(priorPatchSet.getRevision().get());
-        RevCommit priorCommit = revWalk.parseCommit(priorCommitId);
-        ProjectState projectState =
-            refControl.getProjectControl().getProjectState();
-        ChangeKind changeKind =
-            getChangeKind(mergeUtilFactory, projectState, git, priorCommit, commit);
-
-        ApprovalsUtil.copyLabels(db, refControl.getProjectControl()
-            .getLabelTypes(), currentPatchSetId, patchSet, changeKind);
+        approvalCopier.copy(db, ctl, patchSet);
       }
-
-      final List<FooterLine> footerLines = commit.getFooterLines();
-      ChangeUtil.updateTrackingIds(db, updatedChange, trackingFooters, footerLines);
       db.commit();
+      update.commit();
 
-      if (changeMessage != null) {
-        db.changeMessages().insert(Collections.singleton(changeMessage));
+      if (!messageIsForChange()) {
+        insertMessage(db);
       }
 
       if (sendMail) {
@@ -311,23 +294,26 @@
           cm.setFrom(user.getAccountId());
           cm.setPatchSet(patchSet, info);
           cm.setChangeMessage(changeMessage);
-          cm.addReviewers(oldReviewers);
-          cm.addExtraCC(oldCC);
+          cm.addReviewers(oldReviewers.get(ReviewerState.REVIEWER));
+          cm.addExtraCC(oldReviewers.get(ReviewerState.CC));
           cm.send();
         } catch (Exception err) {
-          log.error("Cannot send email for new patch set on change " + updatedChange.getId(),
-              err);
+          log.error("Cannot send email for new patch set on change "
+              + updatedChange.getId(), err);
         }
       }
 
     } finally {
       db.rollback();
     }
-    CheckedFuture<?, IOException> e = indexer.indexAsync(updatedChange);
+    CheckedFuture<?, IOException> f = mergeabilityChecker.newCheck()
+        .addChange(updatedChange)
+        .reindex()
+        .runAsync();
     if (runHooks) {
       hooks.doPatchsetCreatedHook(updatedChange, patchSet, db);
     }
-    e.checkedGet();
+    f.checkedGet();
     return updatedChange;
   }
 
@@ -337,9 +323,9 @@
     }
     if (patchSet == null) {
       patchSet = new PatchSet(
-          ChangeUtil.nextPatchSetId(git, change.currentPatchSetId()));
+          ChangeUtil.nextPatchSetId(git, ctl.getChange().currentPatchSetId()));
       patchSet.setCreatedOn(TimeUtil.nowTs());
-      patchSet.setUploader(change.getOwner());
+      patchSet.setUploader(ctl.getChange().getOwner());
       patchSet.setRevision(new RevId(commit.name()));
     }
     patchSet.setDraft(draft);
@@ -349,7 +335,8 @@
   }
 
   private void validate() throws InvalidChangeOperationException {
-    CommitValidators cv = commitValidatorsFactory.create(refControl, sshInfo, git);
+    CommitValidators cv =
+        commitValidatorsFactory.create(ctl.getRefControl(), sshInfo, git);
 
     String refName = patchSet.getRefName();
     CommitReceivedEvent event = new CommitReceivedEvent(
@@ -357,7 +344,7 @@
             ObjectId.zeroId(),
             commit.getId(),
             refName.substring(0, refName.lastIndexOf('/') + 1) + "new"),
-        refControl.getProjectControl().getProject(), refControl.getRefName(),
+        ctl.getProjectControl().getProject(), ctl.getRefControl().getRefName(),
         commit, user);
 
     try {
@@ -376,54 +363,15 @@
     }
   }
 
-  public static ChangeKind getChangeKind(MergeUtil.Factory mergeUtilFactory, ProjectState project,
-      Repository git, RevCommit prior, RevCommit next) {
-    if (!next.getFullMessage().equals(prior.getFullMessage())) {
-      if (next.getTree() == prior.getTree() && isSameParents(prior, next)) {
-        return ChangeKind.NO_CODE_CHANGE;
-      } else {
-        return ChangeKind.REWORK;
-      }
-    }
-
-    if (prior.getParentCount() != 1 || next.getParentCount() != 1) {
-      // Trivial rebases done by machine only work well on 1 parent.
-      return ChangeKind.REWORK;
-    }
-
-    if (next.getTree() == prior.getTree() &&
-       isSameParents(prior, next)) {
-      return ChangeKind.TRIVIAL_REBASE;
-    }
-
-    // A trivial rebase can be detected by looking for the next commit
-    // having the same tree as would exist when the prior commit is
-    // cherry-picked onto the next commit's new first parent.
-    try {
-      MergeUtil mergeUtil = mergeUtilFactory.create(project);
-      ThreeWayMerger merger =
-          mergeUtil.newThreeWayMerger(git, mergeUtil.createDryRunInserter());
-      merger.setBase(prior.getParent(0));
-      if (merger.merge(next.getParent(0), prior)
-          && merger.getResultTreeId().equals(next.getTree())) {
-        return ChangeKind.TRIVIAL_REBASE;
-      } else {
-        return ChangeKind.REWORK;
-      }
-    } catch (IOException err) {
-      log.warn("Cannot check trivial rebase of new patch set " + next.name()
-          + " in " + project.getProject().getName(), err);
-      return ChangeKind.REWORK;
-    }
+  private boolean messageIsForChange() {
+    return changeMessage != null && changeMessage.getKey().getParentKey()
+        .equals(patchSet.getId().getParentKey());
   }
 
-  private static boolean isSameParents(RevCommit prior, RevCommit next) {
-    if (prior.getParentCount() != next.getParentCount()) {
-      return false;
-    } else if (prior.getParentCount() == 0) {
-      return true;
+  private void insertMessage(ReviewDb db) throws OrmException {
+    if (changeMessage != null) {
+      db.changeMessages().insert(Collections.singleton(changeMessage));
     }
-    return prior.getParent(0).equals(next.getParent(0));
   }
 
   public class ChangeModifiedException extends InvalidChangeOperationException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 2607de0..25c63b5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -23,14 +23,17 @@
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.changes.Side;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.Comment;
+import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
+import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.ReviewInput.Side;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.extensions.restapi.Url;
@@ -41,12 +44,14 @@
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountsCollection;
-import com.google.gerrit.server.change.PostReview.Input;
 import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.util.LabelVote;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -62,70 +67,17 @@
 import java.util.List;
 import java.util.Map;
 
-public class PostReview implements RestModifyView<RevisionResource, Input> {
+public class PostReview implements RestModifyView<RevisionResource, ReviewInput> {
   private static final Logger log = LoggerFactory.getLogger(PostReview.class);
 
-  public static class Input {
-    @DefaultInput
-    public String message;
-
-    public Map<String, Short> labels;
-    public Map<String, List<Comment>> comments;
-
-    /**
-     * If true require all labels to be within the user's permitted ranges based
-     * on access controls, attempting to use a label not granted to the user
-     * will fail the entire modify operation early. If false the operation will
-     * execute anyway, but the proposed labels given by the user will be
-     * modified to be the "best" value allowed by the access controls, or
-     * ignored if the label does not exist.
-     */
-    public boolean strictLabels = true;
-
-    /**
-     * How to process draft comments already in the database that were not also
-     * described in this input request.
-     */
-    public DraftHandling drafts = DraftHandling.DELETE;
-
-    /** Who to send email notifications to after review is stored. */
-    public NotifyHandling notify = NotifyHandling.ALL;
-
-    /**
-     * Account ID, name, email address or username of another user. The review
-     * will be posted/updated on behalf of this named user instead of the
-     * caller. Caller must have the labelAs-$NAME permission granted for each
-     * label that appears in {@link #labels}. This is in addition to the named
-     * user also needing to have permission to use the labels.
-     * <p>
-     * {@link #strictLabels} impacts how labels is processed for the named user,
-     * not the caller.
-     */
-    public String onBehalfOf;
-  }
-
-  public static enum DraftHandling {
-    DELETE, PUBLISH, KEEP;
-  }
-
-  public static enum NotifyHandling {
-    NONE, OWNER, OWNER_REVIEWERS, ALL;
-  }
-
-  public static class Comment {
-    public String id;
-    public Side side;
-    public int line;
-    public String inReplyTo;
-    public String message;
-    public CommentRange range;
-  }
-
   static class Output {
     Map<String, Short> labels;
   }
 
   private final Provider<ReviewDb> db;
+  private final ChangesCollection changes;
+  private final ChangeUpdate.Factory updateFactory;
+  private final ApprovalsUtil approvalsUtil;
   private final ChangeIndexer indexer;
   private final AccountsCollection accounts;
   private final EmailReviewComments.Factory email;
@@ -140,11 +92,17 @@
 
   @Inject
   PostReview(Provider<ReviewDb> db,
+      ChangesCollection changes,
+      ChangeUpdate.Factory updateFactory,
+      ApprovalsUtil approvalsUtil,
       ChangeIndexer indexer,
       AccountsCollection accounts,
       EmailReviewComments.Factory email,
       ChangeHooks hooks) {
     this.db = db;
+    this.changes = changes;
+    this.updateFactory = updateFactory;
+    this.approvalsUtil = approvalsUtil;
     this.indexer = indexer;
     this.accounts = accounts;
     this.email = email;
@@ -152,9 +110,9 @@
   }
 
   @Override
-  public Object apply(RevisionResource revision, Input input)
-      throws AuthException, BadRequestException, OrmException,
-      UnprocessableEntityException, IOException {
+  public Output apply(RevisionResource revision, ReviewInput input)
+      throws AuthException, BadRequestException, UnprocessableEntityException,
+      OrmException, IOException {
     if (input.onBehalfOf != null) {
       revision = onBehalfOf(revision, input);
     }
@@ -169,6 +127,7 @@
       input.notify = NotifyHandling.NONE;
     }
 
+    ChangeUpdate update = null;
     db.get().changes().beginTransaction(revision.getChange().getId());
     boolean dirty = false;
     try {
@@ -176,8 +135,9 @@
       ChangeUtil.updated(change);
       timestamp = change.getLastUpdatedOn();
 
+      update = updateFactory.create(revision.getControl(), timestamp);
       dirty |= insertComments(revision, input.comments, input.drafts);
-      dirty |= updateLabels(revision, input.labels);
+      dirty |= updateLabels(revision, update, input.labels);
       dirty |= insertMessage(revision, input.message);
       if (dirty) {
         db.get().changes().update(Collections.singleton(change));
@@ -186,10 +146,13 @@
     } finally {
       db.get().rollback();
     }
+    if (update != null) {
+      update.commit();
+    }
 
     CheckedFuture<?, IOException> indexWrite;
     if (dirty) {
-      indexWrite = indexer.indexAsync(change);
+      indexWrite = indexer.indexAsync(change.getId());
     } else {
       indexWrite = Futures.<Void, IOException> immediateCheckedFuture(null);
     }
@@ -212,7 +175,7 @@
     return output;
   }
 
-  private RevisionResource onBehalfOf(RevisionResource rev, Input in)
+  private RevisionResource onBehalfOf(RevisionResource rev, ReviewInput in)
       throws BadRequestException, AuthException, UnprocessableEntityException,
       OrmException {
     if (in.labels == null || in.labels.isEmpty()) {
@@ -248,7 +211,7 @@
     }
 
     ChangeControl target = caller.forUser(accounts.parse(in.onBehalfOf));
-    return new RevisionResource(new ChangeResource(target), rev.getPatchSet());
+    return new RevisionResource(changes.parse(target), rev.getPatchSet());
   }
 
   private void checkLabels(RevisionResource revision, boolean strict,
@@ -362,7 +325,7 @@
                   ChangeUtil.messageUUID(db.get())),
               c.line,
               rsrc.getAccountId(),
-              parent, TimeUtil.nowTs());
+              parent, timestamp);
         } else if (parent != null) {
           e.setParentUuid(parent);
         }
@@ -370,7 +333,13 @@
         e.setWrittenOn(timestamp);
         e.setSide(c.side == Side.PARENT ? (short) 0 : (short) 1);
         e.setMessage(c.message);
-        e.setRange(c.range);
+        if (c.range != null) {
+          e.setRange(new CommentRange(
+              c.range.startLine,
+              c.range.startCharacter,
+              c.range.endLine,
+              c.range.endCharacter));
+        }
         ups.add(e);
       }
     }
@@ -407,8 +376,8 @@
     return drafts;
   }
 
-  private boolean updateLabels(RevisionResource rsrc, Map<String, Short> labels)
-      throws OrmException {
+  private boolean updateLabels(RevisionResource rsrc, ChangeUpdate update,
+      Map<String, Short> labels) throws OrmException {
     if (labels == null) {
       labels = Collections.emptyMap();
     }
@@ -432,17 +401,18 @@
         // User requested delete of this label.
         if (c != null) {
           if (c.getValue() != 0) {
-            labelDelta.add("-" + normName);
+            addLabelDelta(normName, (short) 0);
           }
           del.add(c);
+          update.putApproval(ent.getKey(), (short) 0);
         }
       } else if (c != null && c.getValue() != ent.getValue()) {
         c.setValue(ent.getValue());
         c.setGranted(timestamp);
-        c.cache(change);
         ups.add(c);
-        labelDelta.add(format(normName, c.getValue()));
+        addLabelDelta(normName, c.getValue());
         categories.put(normName, c.getValue());
+        update.putApproval(ent.getKey(), ent.getValue());
       } else if (c != null && c.getValue() == ent.getValue()) {
         current.put(normName, c);
       } else if (c == null) {
@@ -452,10 +422,10 @@
                 lt.getLabelId()),
             ent.getValue(), TimeUtil.nowTs());
         c.setGranted(timestamp);
-        c.cache(change);
         ups.add(c);
-        labelDelta.add(format(normName, c.getValue()));
+        addLabelDelta(normName, c.getValue());
         categories.put(normName, c.getValue());
+        update.putApproval(ent.getKey(), ent.getValue());
       }
     }
 
@@ -480,7 +450,6 @@
                 .getLabelId()),
             (short) 0, TimeUtil.nowTs());
         c.setGranted(timestamp);
-        c.cache(change);
         ups.add(c);
       } else {
         // Pick a random label that is about to be deleted and keep it.
@@ -488,7 +457,6 @@
         PatchSetApproval c = i.next();
         c.setValue((short) 0);
         c.setGranted(timestamp);
-        c.cache(change);
         i.remove();
         ups.add(c);
       }
@@ -499,8 +467,10 @@
       List<PatchSetApproval> del) throws OrmException {
     LabelTypes labelTypes = rsrc.getControl().getLabelTypes();
     Map<String, PatchSetApproval> current = Maps.newHashMap();
-    for (PatchSetApproval a : db.get().patchSetApprovals().byPatchSetUser(
-          rsrc.getPatchSet().getId(), rsrc.getAccountId())) {
+
+    for (PatchSetApproval a : approvalsUtil.byPatchSetUser(
+        db.get(), rsrc.getNotes(), rsrc.getPatchSet().getId(),
+        rsrc.getAccountId())) {
       if (a.isSubmit()) {
         continue;
       }
@@ -515,14 +485,8 @@
     return current;
   }
 
-  private static String format(String name, short value) {
-    StringBuilder sb = new StringBuilder(name.length() + 2);
-    sb.append(name);
-    if (value >= 0) {
-      sb.append('+');
-    }
-    sb.append(value);
-    return sb.toString();
+  private void addLabelDelta(String name, short value) {
+    labelDelta.add(new LabelVote(name, value).format());
   }
 
   private boolean insertMessage(RevisionResource rsrc, String msg)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index faec4f7..7fde39c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -14,21 +14,18 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
+import com.google.common.collect.Maps;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.reviewdb.client.Account;
@@ -36,24 +33,24 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountInfo;
 import com.google.gerrit.server.account.AccountsCollection;
 import com.google.gerrit.server.account.GroupMembers;
-import com.google.gerrit.server.change.PostReviewers.Input;
 import com.google.gerrit.server.change.ReviewerJson.PostResult;
 import com.google.gerrit.server.change.ReviewerJson.ReviewerInfo;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.group.GroupsCollection;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.AddReviewerSender;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -65,32 +62,25 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
-public class PostReviewers implements RestModifyView<ChangeResource, Input> {
+public class PostReviewers implements RestModifyView<ChangeResource, AddReviewerInput> {
   private static final Logger log = LoggerFactory
       .getLogger(PostReviewers.class);
 
   public static final int DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK = 10;
   public static final int DEFAULT_MAX_REVIEWERS = 20;
 
-  public static class Input {
-    @DefaultInput
-    public String reviewer;
-    public Boolean confirmed;
-
-    boolean confirmed() {
-      return Objects.firstNonNull(confirmed, false);
-    }
-  }
-
   private final AccountsCollection accounts;
   private final ReviewerResource.Factory reviewerFactory;
+  private final ApprovalsUtil approvalsUtil;
   private final AddReviewerSender.Factory addReviewerSenderFactory;
   private final Provider<GroupsCollection> groupsCollection;
   private final GroupMembers.Factory groupMembersFactory;
   private final AccountInfo.Loader.Factory accountLoaderFactory;
   private final Provider<ReviewDb> dbProvider;
+  private final ChangeUpdate.Factory updateFactory;
   private final IdentifiedUser currentUser;
   private final IdentifiedUser.GenericFactory identifiedUserFactory;
   private final Config cfg;
@@ -102,11 +92,13 @@
   @Inject
   PostReviewers(AccountsCollection accounts,
       ReviewerResource.Factory reviewerFactory,
+      ApprovalsUtil approvalsUtil,
       AddReviewerSender.Factory addReviewerSenderFactory,
       Provider<GroupsCollection> groupsCollection,
       GroupMembers.Factory groupMembersFactory,
       AccountInfo.Loader.Factory accountLoaderFactory,
       Provider<ReviewDb> db,
+      ChangeUpdate.Factory updateFactory,
       IdentifiedUser currentUser,
       IdentifiedUser.GenericFactory identifiedUserFactory,
       @GerritServerConfig Config cfg,
@@ -116,11 +108,13 @@
       ChangeIndexer indexer) {
     this.accounts = accounts;
     this.reviewerFactory = reviewerFactory;
+    this.approvalsUtil = approvalsUtil;
     this.addReviewerSenderFactory = addReviewerSenderFactory;
     this.groupsCollection = groupsCollection;
     this.groupMembersFactory = groupMembersFactory;
     this.accountLoaderFactory = accountLoaderFactory;
     this.dbProvider = db;
+    this.updateFactory = updateFactory;
     this.currentUser = currentUser;
     this.identifiedUserFactory = identifiedUserFactory;
     this.cfg = cfg;
@@ -131,9 +125,9 @@
   }
 
   @Override
-  public PostResult apply(ChangeResource rsrc, Input input)
-      throws BadRequestException, ResourceNotFoundException, AuthException,
-      UnprocessableEntityException, OrmException, EmailException, IOException {
+  public PostResult apply(ChangeResource rsrc, AddReviewerInput input)
+      throws AuthException, BadRequestException, UnprocessableEntityException,
+      OrmException, EmailException, IOException {
     if (input.reviewer == null) {
       throw new BadRequestException("missing reviewer field");
     }
@@ -154,12 +148,15 @@
 
   private PostResult putAccount(ReviewerResource rsrc) throws OrmException,
       EmailException, IOException {
+    Account.Id id = rsrc.getUser().getAccountId();
+    ChangeControl control = rsrc.getControl().forUser(
+        identifiedUserFactory.create(id));
     PostResult result = new PostResult();
-    addReviewers(rsrc, result, ImmutableSet.of(rsrc.getUser()));
+    addReviewers(rsrc, result, ImmutableMap.of(id, control));
     return result;
   }
 
-  private PostResult putGroup(ChangeResource rsrc, Input input)
+  private PostResult putGroup(ChangeResource rsrc, AddReviewerInput input)
       throws BadRequestException,
       UnprocessableEntityException, OrmException, EmailException, IOException {
     GroupDescription.Basic group = groupsCollection.get().parseInternal(input.reviewer);
@@ -170,7 +167,7 @@
       return result;
     }
 
-    Set<IdentifiedUser> reviewers = Sets.newLinkedHashSet();
+    Map<Account.Id, ChangeControl> reviewers = Maps.newHashMap();
     ChangeControl control = rsrc.getControl();
     Set<Account> members;
     try {
@@ -211,7 +208,7 @@
         // Does not account for draft status as a user might want to let a
         // reviewer see a draft.
         if (control.forUser(user).isRefVisible()) {
-          reviewers.add(user);
+          reviewers.put(user.getAccountId(), control);
         }
       }
     }
@@ -221,82 +218,65 @@
   }
 
   private void addReviewers(ChangeResource rsrc, PostResult result,
-      Set<IdentifiedUser> reviewers)
+      Map<Account.Id, ChangeControl> reviewers)
       throws OrmException, EmailException, IOException {
-    if (reviewers.isEmpty()) {
-      result.reviewers = ImmutableList.of();
-      return;
-    }
-
     ReviewDb db = dbProvider.get();
-    PatchSet.Id psid = rsrc.getChange().currentPatchSetId();
-    Set<Account.Id> existing = Sets.newHashSet();
-    for (PatchSetApproval psa : db.patchSetApprovals().byPatchSet(psid)) {
-      existing.add(psa.getAccountId());
-    }
-
-    result.reviewers = Lists.newArrayListWithCapacity(reviewers.size());
-    List<PatchSetApproval> toInsert =
-        Lists.newArrayListWithCapacity(reviewers.size());
-    for (IdentifiedUser user : reviewers) {
-      Account.Id id = user.getAccountId();
-      if (existing.contains(id)) {
-        continue;
-      }
-      ChangeControl control = rsrc.getControl().forUser(user);
-      PatchSetApproval psa = dummyApproval(control, psid, id);
-      result.reviewers.add(json.format(
-          new ReviewerInfo(id), control, ImmutableList.of(psa)));
-      toInsert.add(psa);
-    }
-    if (toInsert.isEmpty()) {
-      return;
-    }
-
+    ChangeUpdate update = updateFactory.create(rsrc.getControl());
+    List<PatchSetApproval> added;
     db.changes().beginTransaction(rsrc.getChange().getId());
     try {
       ChangeUtil.bumpRowVersionNotLastUpdatedOn(rsrc.getChange().getId(), db);
-      db.patchSetApprovals().insert(toInsert);
+      added = approvalsUtil.addReviewers(db, rsrc.getNotes(), update,
+          rsrc.getControl().getLabelTypes(), rsrc.getChange(),
+          reviewers.keySet());
       db.commit();
     } finally {
       db.rollback();
     }
 
-    CheckedFuture<?, IOException> indexFuture = indexer.indexAsync(rsrc.getChange());
+    update.commit();
+    CheckedFuture<?, IOException> indexFuture =
+        indexer.indexAsync(rsrc.getChange().getId());
+    result.reviewers = Lists.newArrayListWithCapacity(added.size());
+    for (PatchSetApproval psa : added) {
+      result.reviewers.add(json.format(
+          new ReviewerInfo(psa.getAccountId()),
+          reviewers.get(psa.getAccountId()),
+          ImmutableList.of(psa)));
+    }
     accountLoaderFactory.create(true).fill(result.reviewers);
-    postAdd(rsrc.getChange(), result);
+    postAdd(rsrc.getChange(), added);
     indexFuture.checkedGet();
   }
 
-  private void postAdd(Change change, PostResult result)
+  private void postAdd(Change change, List<PatchSetApproval> added)
       throws OrmException, EmailException {
-    if (result.reviewers.isEmpty()) {
+    if (added.isEmpty()) {
       return;
     }
 
     // Execute hook for added reviewers
     //
     PatchSet patchSet = dbProvider.get().patchSets().get(change.currentPatchSetId());
-    for (AccountInfo info : result.reviewers) {
-      Account account = accountCache.get(info._id).getAccount();
+    for (PatchSetApproval psa : added) {
+      Account account = accountCache.get(psa.getAccountId()).getAccount();
       hooks.doReviewerAddedHook(change, account, patchSet, dbProvider.get());
     }
 
     // Email the reviewers
     //
     // The user knows they added themselves, don't bother emailing them.
-    List<Account.Id> added =
-        Lists.newArrayListWithCapacity(result.reviewers.size());
-    for (AccountInfo info : result.reviewers) {
-      if (!info._id.equals(currentUser.getAccountId())) {
-        added.add(info._id);
+    List<Account.Id> toMail = Lists.newArrayListWithCapacity(added.size());
+    for (PatchSetApproval psa : added) {
+      if (!psa.getAccountId().equals(currentUser.getAccountId())) {
+        toMail.add(psa.getAccountId());
       }
     }
     if (!added.isEmpty()) {
       try {
         AddReviewerSender cm = addReviewerSenderFactory.create(change);
         cm.setFrom(currentUser.getAccountId());
-        cm.addReviewers(added);
+        cm.addReviewers(toMail);
         cm.send();
       } catch (Exception err) {
         log.error("Cannot send email to new reviewers of change "
@@ -306,18 +286,6 @@
   }
 
   public static boolean isLegalReviewerGroup(AccountGroup.UUID groupUUID) {
-    return !(AccountGroup.ANONYMOUS_USERS.equals(groupUUID)
-             || AccountGroup.REGISTERED_USERS.equals(groupUUID));
-  }
-
-  private PatchSetApproval dummyApproval(ChangeControl ctl,
-      PatchSet.Id patchSetId, Account.Id reviewerId) {
-    LabelId id =
-        Iterables.getLast(ctl.getLabelTypes().getLabelTypes()).getLabelId();
-    PatchSetApproval dummyApproval = new PatchSetApproval(
-        new PatchSetApproval.Key(patchSetId, reviewerId, id), (short) 0,
-        TimeUtil.nowTs());
-    dummyApproval.cache(ctl.getChange());
-    return dummyApproval;
+    return !SystemGroupBackend.isSystemGroup(groupUUID);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
index 8b0fe99..fab8836 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
@@ -27,14 +27,18 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.change.Publish.Input;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.PatchSetNotificationSender;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import org.eclipse.jgit.lib.Config;
+
 import java.io.IOException;
 
 public class Publish implements RestModifyView<RevisionResource, Input>,
@@ -43,25 +47,31 @@
   }
 
   private final Provider<ReviewDb> dbProvider;
+  private final ChangeUpdate.Factory updateFactory;
   private final PatchSetNotificationSender sender;
   private final ChangeHooks hooks;
   private final ChangeIndexer indexer;
+  private final boolean allowDrafts;
 
   @Inject
   public Publish(Provider<ReviewDb> dbProvider,
+      ChangeUpdate.Factory updateFactory,
       PatchSetNotificationSender sender,
       ChangeHooks hooks,
-      ChangeIndexer indexer) {
+      ChangeIndexer indexer,
+      @GerritServerConfig Config cfg) {
     this.dbProvider = dbProvider;
+    this.updateFactory = updateFactory;
     this.sender = sender;
     this.hooks = hooks;
     this.indexer = indexer;
+    this.allowDrafts = cfg.getBoolean("change", "allowDrafts", true);
   }
 
   @Override
-  public Object apply(RevisionResource rsrc, Input input) throws IOException,
-      ResourceNotFoundException, ResourceConflictException,
-      OrmException, AuthException {
+  public Response<?> apply(RevisionResource rsrc, Input input)
+      throws AuthException, ResourceNotFoundException,
+      ResourceConflictException, OrmException, IOException {
     if (!rsrc.getPatchSet().isDraft()) {
       throw new ResourceConflictException("Patch set is not a draft");
     }
@@ -70,16 +80,23 @@
       throw new AuthException("Cannot publish this draft patch set");
     }
 
+    if (!allowDrafts) {
+      throw new ResourceConflictException("Draft workflow is disabled.");
+    }
+
     PatchSet updatedPatchSet = updateDraftPatchSet(rsrc);
     Change updatedChange = updateDraftChange(rsrc);
+    ChangeUpdate update = updateFactory.create(rsrc.getControl(),
+        updatedChange.getLastUpdatedOn());
 
     try {
       if (!updatedPatchSet.isDraft()
           || updatedChange.getStatus() == Change.Status.NEW) {
         CheckedFuture<?, IOException> indexFuture =
-            indexer.indexAsync(updatedChange);
+            indexer.indexAsync(updatedChange.getId());
         hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, dbProvider.get());
-        sender.send(rsrc.getChange().getStatus() == Change.Status.DRAFT,
+        sender.send(rsrc.getNotes(), update,
+            rsrc.getChange().getStatus() == Change.Status.DRAFT,
             rsrc.getUser(), updatedChange, updatedPatchSet,
             rsrc.getControl().getLabelTypes());
         indexFuture.checkedGet();
@@ -92,7 +109,7 @@
   }
 
   private Change updateDraftChange(RevisionResource rsrc) throws OrmException {
-    Change updatedChange = dbProvider.get().changes()
+    return dbProvider.get().changes()
         .atomicUpdate(rsrc.getChange().getId(),
         new AtomicUpdate<Change>() {
       @Override
@@ -104,11 +121,10 @@
         return change;
       }
     });
-    return updatedChange;
   }
 
   private PatchSet updateDraftPatchSet(RevisionResource rsrc) throws OrmException {
-    final PatchSet updatedPatchSet = dbProvider.get().patchSets()
+    return dbProvider.get().patchSets()
         .atomicUpdate(rsrc.getPatchSet().getId(),
         new AtomicUpdate<PatchSet>() {
       @Override
@@ -117,7 +133,6 @@
         return patchset;
       }
     });
-    return updatedPatchSet;
   }
 
   @Override
@@ -148,9 +163,9 @@
     }
 
     @Override
-    public Object apply(ChangeResource rsrc, Input input) throws AuthException,
-        ResourceConflictException, ResourceConflictException, IOException,
-        OrmException, ResourceNotFoundException, AuthException {
+    public Response<?> apply(ChangeResource rsrc, Input input)
+        throws AuthException, ResourceConflictException,
+        ResourceNotFoundException, IOException, OrmException {
       PatchSet ps = dbProvider.get().patchSets()
         .get(rsrc.getChange().currentPatchSetId());
       if (ps == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
index 803af17..881b876 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraft.java
@@ -15,15 +15,14 @@
 package com.google.gerrit.server.change;
 
 import com.google.gerrit.common.changes.Side;
-import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.CommentRange;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.client.CommentRange;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.PutDraft.Input;
 import com.google.gerrit.server.util.TimeUtil;
@@ -59,8 +58,8 @@
   }
 
   @Override
-  public Object apply(DraftResource rsrc, Input in) throws AuthException,
-      BadRequestException, ResourceConflictException, OrmException {
+  public Response<CommentInfo> apply(DraftResource rsrc, Input in) throws
+      BadRequestException, OrmException {
     PatchLineComment c = rsrc.getComment();
     if (in == null || in.message == null || in.message.trim().isEmpty()) {
       return delete.get().apply(rsrc, null);
@@ -90,7 +89,7 @@
     } else {
       db.get().patchComments().update(Collections.singleton(update(c, in)));
     }
-    return new CommentInfo(c, null);
+    return Response.ok(new CommentInfo(c, null));
   }
 
   private PatchLineComment update(PatchLineComment e, Input in) {
@@ -105,7 +104,7 @@
       e.setRange(in.range);
       e.setLine(in.range != null ? in.range.getEndLine() : in.line);
     }
-    e.updated(TimeUtil.nowTs());
+    e.setWrittenOn(TimeUtil.nowTs());
     return e;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index d663dc6..1ef0ee3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -18,9 +18,7 @@
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.webui.UiAction;
@@ -34,6 +32,7 @@
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
@@ -49,7 +48,6 @@
   static class Input {
     @DefaultInput
     String topic;
-    String message;
   }
 
   @Inject
@@ -61,9 +59,8 @@
   }
 
   @Override
-  public Object apply(ChangeResource req, Input input)
-      throws BadRequestException, AuthException,
-      ResourceConflictException, Exception {
+  public Response<String> apply(ChangeResource req, Input input)
+      throws AuthException, OrmException, IOException {
     if (input == null) {
       input = new Input();
     }
@@ -94,12 +91,7 @@
           new ChangeMessage.Key(change.getId(), ChangeUtil.messageUUID(db)),
           currentUser.getAccountId(), TimeUtil.nowTs(),
           change.currentPatchSetId());
-      StringBuilder msgBuf = new StringBuilder(summary);
-      if (!Strings.isNullOrEmpty(input.message)) {
-        msgBuf.append("\n\n");
-        msgBuf.append(input.message);
-      }
-      cmsg.setMessage(msgBuf.toString());
+      cmsg.setMessage(summary);
 
       db.changes().beginTransaction(change.getId());
       try {
@@ -117,14 +109,15 @@
       } finally {
         db.rollback();
       }
-      CheckedFuture<?, IOException> indexFuture = indexer.indexAsync(change);
+      CheckedFuture<?, IOException> indexFuture =
+          indexer.indexAsync(change.getId());
       hooks.doTopicChangedHook(change, currentUser.getAccount(),
           oldTopicName, db);
       indexFuture.checkedGet();
     }
     return Strings.isNullOrEmpty(newTopicName)
-        ? Response.none()
-        : newTopicName;
+        ? Response.<String>none()
+        : Response.ok(newTopicName);
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index 3831c20..e1a019f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.gerrit.common.changes.ListChangesOption;
 import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index d31d1f0..b3641e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -17,8 +17,8 @@
 import com.google.common.base.Strings;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.api.changes.RestoreInput;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.webui.UiAction;
@@ -26,11 +26,9 @@
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
-import com.google.gerrit.server.change.Restore.Input;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.ReplyToChangeSender;
 import com.google.gerrit.server.mail.RestoredSender;
@@ -46,7 +44,7 @@
 import java.io.IOException;
 import java.util.Collections;
 
-public class Restore implements RestModifyView<ChangeResource, Input>,
+public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
     UiAction<ChangeResource> {
   private static final Logger log = LoggerFactory.getLogger(Restore.class);
 
@@ -56,11 +54,6 @@
   private final ChangeJson json;
   private final ChangeIndexer indexer;
 
-  public static class Input {
-    @DefaultInput
-    public String message;
-  }
-
   @Inject
   Restore(ChangeHooks hooks,
       RestoredSender.Factory restoredSenderFactory,
@@ -75,8 +68,9 @@
   }
 
   @Override
-  public Object apply(ChangeResource req, Input input)
-      throws Exception {
+  public ChangeInfo apply(ChangeResource req, RestoreInput input)
+      throws AuthException, ResourceConflictException, OrmException,
+      IOException {
     ChangeControl control = req.getControl();
     IdentifiedUser caller = (IdentifiedUser) control.getCurrentUser();
     Change change = req.getChange();
@@ -109,13 +103,13 @@
       }
       message = newMessage(input, caller, change);
       db.changeMessages().insert(Collections.singleton(message));
-      new ApprovalsUtil(db).syncChangeStatus(change);
       db.commit();
     } finally {
       db.rollback();
     }
 
-    CheckedFuture<?, IOException> indexFuture = indexer.indexAsync(change);
+    CheckedFuture<?, IOException> indexFuture =
+        indexer.indexAsync(change.getId());
     try {
       ReplyToChangeSender cm = restoredSenderFactory.create(change);
       cm.setFrom(caller.getAccountId());
@@ -143,7 +137,7 @@
           && resource.getControl().canRestore());
   }
 
-  private ChangeMessage newMessage(Input input, IdentifiedUser caller,
+  private ChangeMessage newMessage(RestoreInput input, IdentifiedUser caller,
       Change change) throws OrmException {
     StringBuilder msg = new StringBuilder();
     msg.append("Restored");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index 73a6b03..022c12c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -15,71 +15,49 @@
 package com.google.gerrit.server.change;
 
 import com.google.common.base.Strings;
-import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.api.changes.RevertInput;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.Revert.Input;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.mail.RevertedSender;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.ssh.NoSshInfo;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
 
-public class Revert implements RestModifyView<ChangeResource, Input>,
+import java.io.IOException;
+
+public class Revert implements RestModifyView<ChangeResource, RevertInput>,
     UiAction<ChangeResource> {
-  private final ChangeHooks hooks;
-  private final RevertedSender.Factory revertedSenderFactory;
-  private final CommitValidators.Factory commitValidatorsFactory;
-  private final Provider<ReviewDb> dbProvider;
   private final ChangeJson json;
-  private final GitRepositoryManager gitManager;
+  private final ChangeUtil changeUtil;
   private final PersonIdent myIdent;
-  private final PatchSetInfoFactory patchSetInfoFactory;
-  private final ChangeInserter.Factory changeInserterFactory;
-
-  public static class Input {
-    public String message;
-  }
 
   @Inject
-  Revert(ChangeHooks hooks,
-      RevertedSender.Factory revertedSenderFactory,
-      final CommitValidators.Factory commitValidatorsFactory,
-      Provider<ReviewDb> dbProvider,
-      ChangeJson json,
-      GitRepositoryManager gitManager,
-      final PatchSetInfoFactory patchSetInfoFactory,
-      @GerritPersonIdent final PersonIdent myIdent,
-      final ChangeInserter.Factory changeInserterFactory) {
-    this.hooks = hooks;
-    this.revertedSenderFactory = revertedSenderFactory;
-    this.commitValidatorsFactory = commitValidatorsFactory;
-    this.dbProvider = dbProvider;
+  Revert(ChangeJson json,
+      ChangeUtil changeUtil,
+      @GerritPersonIdent PersonIdent myIdent) {
     this.json = json;
-    this.gitManager = gitManager;
+    this.changeUtil = changeUtil;
     this.myIdent = myIdent;
-    this.changeInserterFactory = changeInserterFactory;
-    this.patchSetInfoFactory = patchSetInfoFactory;
   }
 
   @Override
-  public Object apply(ChangeResource req, Input input) throws Exception {
+  public ChangeInfo apply(ChangeResource req, RevertInput input)
+      throws AuthException, BadRequestException, ResourceConflictException,
+      ResourceNotFoundException, IOException, OrmException, EmailException {
     ChangeControl control = req.getControl();
     Change change = req.getChange();
     if (!control.canAddPatchSet()) {
@@ -88,24 +66,17 @@
       throw new ResourceConflictException("change is " + status(change));
     }
 
-    final Repository git = gitManager.openRepository(control.getProject().getNameKey());
     try {
-      CommitValidators commitValidators =
-          commitValidatorsFactory.create(control.getRefControl(), new NoSshInfo(), git);
-
       Change.Id revertedChangeId =
-          ChangeUtil.revert(control.getRefControl(), change.currentPatchSetId(),
-              (IdentifiedUser) control.getCurrentUser(),
-              commitValidators,
-              Strings.emptyToNull(input.message), dbProvider.get(),
-              revertedSenderFactory, hooks, git, patchSetInfoFactory,
-              myIdent, changeInserterFactory);
+          changeUtil.revert(control, change.currentPatchSetId(),
+              Strings.emptyToNull(input.message),
+              myIdent, new NoSshInfo());
 
       return json.format(revertedChangeId);
     } catch (InvalidChangeOperationException e) {
       throw new BadRequestException(e.getMessage());
-    } finally {
-      git.close();
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage());
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java
index 022f178..dd27139 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java
@@ -38,7 +38,7 @@
     }
 
     @Override
-    public Object apply(FileResource resource, Input input)
+    public Response<String> apply(FileResource resource, Input input)
         throws OrmException {
       ReviewDb db = dbProvider.get();
       AccountPatchReview apr = getExisting(db, resource);
@@ -66,7 +66,7 @@
     }
 
     @Override
-    public Object apply(FileResource resource, Input input)
+    public Response<?> apply(FileResource resource, Input input)
         throws OrmException {
       ReviewDb db = dbProvider.get();
       AccountPatchReview apr = getExisting(db, resource);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java
index 734d524..5008d02 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerJson.java
@@ -27,8 +27,10 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.account.AccountInfo;
 import com.google.gerrit.server.git.LabelNormalizer;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
@@ -42,14 +44,20 @@
 
 public class ReviewerJson {
   private final Provider<ReviewDb> db;
+  private final ChangeData.Factory changeDataFactory;
+  private final ApprovalsUtil approvalsUtil;
   private final LabelNormalizer labelNormalizer;
   private final AccountInfo.Loader.Factory accountLoaderFactory;
 
   @Inject
   ReviewerJson(Provider<ReviewDb> db,
+      ChangeData.Factory changeDataFactory,
+      ApprovalsUtil approvalsUtil,
       LabelNormalizer labelNormalizer,
       AccountInfo.Loader.Factory accountLoaderFactory) {
     this.db = db;
+    this.changeDataFactory = changeDataFactory;
+    this.approvalsUtil = approvalsUtil;
     this.labelNormalizer = labelNormalizer;
     this.accountLoaderFactory = accountLoaderFactory;
   }
@@ -59,7 +67,10 @@
     List<ReviewerInfo> infos = Lists.newArrayListWithCapacity(rsrcs.size());
     AccountInfo.Loader loader = accountLoaderFactory.create(true);
     for (ReviewerResource rsrc : rsrcs) {
-      ReviewerInfo info = format(rsrc, null);
+      ReviewerInfo info = format(new ReviewerInfo(
+          rsrc.getUser().getAccountId()),
+          rsrc.getUserControl(),
+          rsrc.getNotes());
       loader.put(info);
       infos.add(info);
     }
@@ -72,19 +83,20 @@
   }
 
   public ReviewerInfo format(ReviewerInfo out, ChangeControl ctl,
-      List<PatchSetApproval> approvals) throws OrmException {
+      ChangeNotes changeNotes) throws OrmException {
     PatchSet.Id psId = ctl.getChange().currentPatchSetId();
+    return format(out, ctl,
+        approvalsUtil.byPatchSetUser(db.get(), changeNotes, psId, out._id));
+  }
 
-    if (approvals == null) {
-      approvals = ChangeData.sortApprovals(db.get().patchSetApprovals()
-          .byPatchSetUser(psId, out._id));
-    }
-    approvals = labelNormalizer.normalize(ctl, approvals);
+  public ReviewerInfo format(ReviewerInfo out, ChangeControl ctl,
+      List<PatchSetApproval> approvals) throws OrmException {
     LabelTypes labelTypes = ctl.getLabelTypes();
 
     // Don't use Maps.newTreeMap(Comparator) due to OpenJDK bug 100167.
     out.approvals = new TreeMap<String,String>(labelTypes.nameComparator());
-    for (PatchSetApproval ca : approvals) {
+    for (PatchSetApproval ca :
+        labelNormalizer.normalize(ctl, approvals).getNormalized()) {
       for (PermissionRange pr : ctl.getLabelRanges()) {
         if (!pr.isEmpty()) {
           LabelType at = labelTypes.byLabel(ca.getLabelId());
@@ -97,8 +109,8 @@
 
     // Add dummy approvals for all permitted labels for the user even if they
     // do not exist in the DB.
-    ChangeData cd = new ChangeData(ctl);
-    PatchSet ps = cd.currentPatchSet(db);
+    ChangeData cd = changeDataFactory.create(db.get(), ctl);
+    PatchSet ps = cd.currentPatchSet();
     if (ps != null) {
       for (SubmitRecord rec :
           ctl.canSubmit(db.get(), ps, cd, true, false, true)) {
@@ -122,12 +134,6 @@
     return out;
   }
 
-  private ReviewerInfo format(ReviewerResource rsrc,
-      List<PatchSetApproval> approvals) throws OrmException {
-    return format(new ReviewerInfo(rsrc.getUser().getAccountId()),
-        rsrc.getUserControl(), approvals);
-  }
-
   public static class ReviewerInfo extends AccountInfo {
     final String kind = "gerritcodereview#reviewer";
     Map<String, String> approvals;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewers.java
index cf9e089..e7c913a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewers.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ChildCollection;
@@ -23,30 +22,33 @@
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.account.AccountsCollection;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import java.util.Set;
+import java.util.Collection;
 
 public class Reviewers implements
     ChildCollection<ChangeResource, ReviewerResource> {
   private final DynamicMap<RestView<ReviewerResource>> views;
   private final Provider<ReviewDb> dbProvider;
+  private final ApprovalsUtil approvalsUtil;
   private final AccountsCollection accounts;
   private final ReviewerResource.Factory resourceFactory;
   private final Provider<ListReviewers> list;
 
   @Inject
   Reviewers(Provider<ReviewDb> dbProvider,
+      ApprovalsUtil approvalsUtil,
       AccountsCollection accounts,
       ReviewerResource.Factory resourceFactory,
       DynamicMap<RestView<ReviewerResource>> views,
       Provider<ListReviewers> list) {
     this.dbProvider = dbProvider;
+    this.approvalsUtil = approvalsUtil;
     this.accounts = accounts;
     this.resourceFactory = resourceFactory;
     this.views = views;
@@ -76,13 +78,9 @@
     throw new ResourceNotFoundException(id);
   }
 
-  private Set<Account.Id> fetchAccountIds(ChangeResource rsrc)
+  private Collection<Account.Id> fetchAccountIds(ChangeResource rsrc)
       throws OrmException {
-    Set<Account.Id> accountIds = Sets.newHashSet();
-    for (PatchSetApproval a
-         : dbProvider.get().patchSetApprovals().byChange(rsrc.getChange().getId())) {
-      accountIds.add(a.getAccountId());
-    }
-    return accountIds;
+    return approvalsUtil.getReviewers(
+        dbProvider.get(), rsrc.getNotes()).values();
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
index b15719f..eb9f4ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.inject.TypeLiteral;
 
@@ -53,6 +54,10 @@
     return getControl().getChange();
   }
 
+  public ChangeNotes getNotes() {
+    return getChangeResource().getNotes();
+  }
+
   public PatchSet getPatchSet() {
     return ps;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index cb41125..c2ac123 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -18,26 +18,40 @@
 
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Table;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.extensions.api.changes.SubmitInput;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.extensions.webui.UiAction;
+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.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.ProjectUtil;
-import com.google.gerrit.server.change.Submit.Input;
+import com.google.gerrit.server.account.AccountsCollection;
+import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LabelNormalizer;
 import com.google.gerrit.server.git.MergeQueue;
+import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
 import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.AtomicUpdate;
@@ -46,21 +60,19 @@
 import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.PersonIdent;
 
 import java.io.IOException;
 import java.sql.Timestamp;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
-public class Submit implements RestModifyView<RevisionResource, Input>,
+public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
     UiAction<RevisionResource> {
-  public static class Input {
-    public boolean waitForMerge;
-  }
-
   public enum Status {
-    SUBMITTED, MERGED;
+    SUBMITTED, MERGED
   }
 
   public static class Output {
@@ -73,30 +85,56 @@
     }
   }
 
+  private final PersonIdent serverIdent;
   private final Provider<ReviewDb> dbProvider;
   private final GitRepositoryManager repoManager;
+  private final IdentifiedUser.GenericFactory userFactory;
+  private final ChangeUpdate.Factory updateFactory;
+  private final ApprovalsUtil approvalsUtil;
   private final MergeQueue mergeQueue;
   private final ChangeIndexer indexer;
+  private final LabelNormalizer labelNormalizer;
+  private final AccountsCollection accounts;
+  private final ChangesCollection changes;
 
   @Inject
-  Submit(Provider<ReviewDb> dbProvider,
+  Submit(@GerritPersonIdent PersonIdent serverIdent,
+      Provider<ReviewDb> dbProvider,
       GitRepositoryManager repoManager,
+      IdentifiedUser.GenericFactory userFactory,
+      ChangeUpdate.Factory updateFactory,
+      ApprovalsUtil approvalsUtil,
       MergeQueue mergeQueue,
-      ChangeIndexer indexer) {
+      AccountsCollection accounts,
+      ChangesCollection changes,
+      ChangeIndexer indexer,
+      LabelNormalizer labelNormalizer) {
+    this.serverIdent = serverIdent;
     this.dbProvider = dbProvider;
     this.repoManager = repoManager;
+    this.userFactory = userFactory;
+    this.updateFactory = updateFactory;
+    this.approvalsUtil = approvalsUtil;
     this.mergeQueue = mergeQueue;
+    this.accounts = accounts;
+    this.changes = changes;
     this.indexer = indexer;
+    this.labelNormalizer = labelNormalizer;
   }
 
   @Override
-  public Output apply(RevisionResource rsrc, Input input) throws AuthException,
-      ResourceConflictException, RepositoryNotFoundException, IOException,
-      OrmException {
+  public Output apply(RevisionResource rsrc, SubmitInput input)
+      throws AuthException, ResourceConflictException,
+      RepositoryNotFoundException, IOException, OrmException,
+      UnprocessableEntityException {
+    input.onBehalfOf = Strings.emptyToNull(input.onBehalfOf);
+    if (input.onBehalfOf != null) {
+      rsrc = onBehalfOf(rsrc, input);
+    }
     ChangeControl control = rsrc.getControl();
     IdentifiedUser caller = (IdentifiedUser) control.getCurrentUser();
     Change change = rsrc.getChange();
-    if (!control.canSubmit()) {
+    if (input.onBehalfOf == null && !control.canSubmit()) {
       throw new AuthException("submit not permitted");
     } else if (!change.getStatus().isOpen()) {
       throw new ResourceConflictException("change is " + status(change));
@@ -111,8 +149,7 @@
           rsrc.getPatchSet().getRevision().get()));
     }
 
-    checkSubmitRule(rsrc);
-    change = submit(rsrc, caller);
+    change = submit(rsrc, caller, false);
     if (change == null) {
       throw new ResourceConflictException("change is "
           + status(dbProvider.get().changes().get(rsrc.getChange().getId())));
@@ -148,8 +185,9 @@
     PatchSet.Id current = resource.getChange().currentPatchSetId();
     return new UiAction.Description()
       .setTitle(String.format(
-          "Submit revision %d",
-          resource.getPatchSet().getPatchSetId()))
+          "Merge patch set %d into %s",
+          resource.getPatchSet().getPatchSetId(),
+          resource.getChange().getDest().getShortName()))
       .setVisible(!resource.getPatchSet().isDraft()
           && resource.getChange().getStatus().isOpen()
           && resource.getPatchSet().getId().equals(current)
@@ -163,7 +201,7 @@
    */
   public ChangeMessage getConflictMessage(RevisionResource rsrc)
       throws OrmException {
-    ChangeMessage msg = Iterables.getFirst(Iterables.filter(
+    return Iterables.getFirst(Iterables.filter(
       Lists.reverse(dbProvider.get().changeMessages()
           .byChange(rsrc.getChange().getId())
           .toList()),
@@ -173,17 +211,24 @@
           return input.getAuthor() == null;
         }
       }), null);
-    return msg;
   }
 
-  public Change submit(RevisionResource rsrc, IdentifiedUser caller)
-      throws OrmException, IOException {
+  public Change submit(RevisionResource rsrc, IdentifiedUser caller,
+      boolean force) throws ResourceConflictException, OrmException,
+      IOException {
+    List<SubmitRecord> submitRecords = checkSubmitRule(rsrc, force);
     final Timestamp timestamp = TimeUtil.nowTs();
     Change change = rsrc.getChange();
+    ChangeUpdate update = updateFactory.create(rsrc.getControl(), timestamp);
+    update.submit(submitRecords);
+
     ReviewDb db = dbProvider.get();
     db.changes().beginTransaction(change.getId());
     try {
-      approve(rsrc.getPatchSet(), caller, timestamp);
+      BatchMetaDataUpdate batch = approve(rsrc, update, caller, timestamp);
+      // Write update commit after all normalized label commits.
+      batch.write(update, new CommitBuilder());
+
       change = db.changes().atomicUpdate(
         change.getId(),
         new AtomicUpdate<Change>() {
@@ -205,43 +250,114 @@
     } finally {
       db.rollback();
     }
-    indexer.index(change);
+    indexer.index(db, change);
     return change;
   }
 
-  private void approve(PatchSet rev, IdentifiedUser caller, Timestamp timestamp)
+  private BatchMetaDataUpdate approve(RevisionResource rsrc,
+      ChangeUpdate update, IdentifiedUser caller, Timestamp timestamp)
       throws OrmException {
-    PatchSetApproval submit = Iterables.getFirst(Iterables.filter(
-      dbProvider.get().patchSetApprovals()
-        .byPatchSetUser(rev.getId(), caller.getAccountId()),
-      new Predicate<PatchSetApproval>() {
-        @Override
-        public boolean apply(PatchSetApproval input) {
-          return input.isSubmit();
-        }
-      }), null);
-    if (submit == null) {
+    PatchSet.Id psId = rsrc.getPatchSet().getId();
+    List<PatchSetApproval> approvals =
+        approvalsUtil.byPatchSet(dbProvider.get(), rsrc.getNotes(), psId);
+
+    Map<PatchSetApproval.Key, PatchSetApproval> byKey =
+        Maps.newHashMapWithExpectedSize(approvals.size());
+    for (PatchSetApproval psa : approvals) {
+      if (!byKey.containsKey(psa.getKey())) {
+        byKey.put(psa.getKey(), psa);
+      }
+    }
+
+    PatchSetApproval submit = ApprovalsUtil.getSubmitter(psId, byKey.values());
+    if (submit == null || submit.getAccountId() != caller.getAccountId()) {
       submit = new PatchSetApproval(
           new PatchSetApproval.Key(
-              rev.getId(),
+              rsrc.getPatchSet().getId(),
               caller.getAccountId(),
               LabelId.SUBMIT),
           (short) 1, TimeUtil.nowTs());
+      byKey.put(submit.getKey(), submit);
     }
     submit.setValue((short) 1);
     submit.setGranted(timestamp);
-    dbProvider.get().patchSetApprovals().upsert(Collections.singleton(submit));
+
+    // Flatten out existing approvals for this patch set based upon the current
+    // permissions. Once the change is closed the approvals are not updated at
+    // presentation view time, except for zero votes used to indicate a reviewer
+    // was added. So we need to make sure votes are accurate now. This way if
+    // permissions get modified in the future, historical records stay accurate.
+    LabelNormalizer.Result normalized =
+        labelNormalizer.normalize(rsrc.getControl(), byKey.values());
+
+    // TODO(dborowitz): Don't use a label in notedb; just check when status
+    // change happened.
+    update.putApproval(submit.getLabel(), submit.getValue());
+
+    dbProvider.get().patchSetApprovals().upsert(normalized.getNormalized());
+    dbProvider.get().patchSetApprovals().delete(normalized.getDeleted());
+
+    try {
+      return saveToBatch(rsrc, update, normalized, timestamp);
+    } catch (IOException e) {
+      throw new OrmException(e);
+    }
   }
 
-  private void checkSubmitRule(RevisionResource rsrc)
-      throws ResourceConflictException {
-  List<SubmitRecord> results = rsrc.getControl().canSubmit(
+  private BatchMetaDataUpdate saveToBatch(RevisionResource rsrc,
+      ChangeUpdate callerUpdate, LabelNormalizer.Result normalized,
+      Timestamp timestamp) throws IOException {
+    Table<Account.Id, String, Optional<Short>> byUser = HashBasedTable.create();
+    for (PatchSetApproval psa : normalized.getUpdated()) {
+      byUser.put(psa.getAccountId(), psa.getLabel(),
+          Optional.of(psa.getValue()));
+    }
+    for (PatchSetApproval psa : normalized.getDeleted()) {
+      byUser.put(psa.getAccountId(), psa.getLabel(), Optional.<Short> absent());
+    }
+
+    ChangeControl ctl = rsrc.getControl();
+    BatchMetaDataUpdate batch = callerUpdate.openUpdate();
+    for (Account.Id accountId : byUser.rowKeySet()) {
+      if (!accountId.equals(callerUpdate.getUser().getAccountId())) {
+        ChangeUpdate update = updateFactory.create(
+            ctl.forUser(userFactory.create(dbProvider, accountId)), timestamp);
+        update.setSubject("Finalize approvals at submit");
+        putApprovals(update, byUser.row(accountId));
+
+        CommitBuilder commit = new CommitBuilder();
+        commit.setCommitter(new PersonIdent(serverIdent, timestamp));
+        batch.write(update, commit);
+      }
+    }
+
+    putApprovals(callerUpdate,
+        byUser.row(callerUpdate.getUser().getAccountId()));
+    return batch;
+  }
+
+  private static void putApprovals(ChangeUpdate update,
+      Map<String, Optional<Short>> approvals) {
+    for (Map.Entry<String, Optional<Short>> e : approvals.entrySet()) {
+      if (e.getValue().isPresent()) {
+        update.putApproval(e.getKey(), e.getValue().get());
+      } else {
+        update.removeApproval(e.getKey());
+      }
+    }
+  }
+
+  private List<SubmitRecord> checkSubmitRule(RevisionResource rsrc,
+      boolean force) throws ResourceConflictException {
+    List<SubmitRecord> results = rsrc.getControl().canSubmit(
         dbProvider.get(),
         rsrc.getPatchSet());
     Optional<SubmitRecord> ok = findOkRecord(results);
     if (ok.isPresent()) {
       // Rules supplied a valid solution.
-      return;
+      return ImmutableList.of(ok.get());
+    } else if (force) {
+      return results;
     } else if (results.isEmpty()) {
       throw new IllegalStateException(String.format(
           "ChangeControl.canSubmit returned empty list for %s in %s",
@@ -269,17 +385,18 @@
 
               case REJECT:
                 if (msg.length() > 0) msg.append("; ");
-                msg.append("blocked by " + lbl.label);
+                msg.append("blocked by ").append(lbl.label);
                 continue;
 
               case NEED:
                 if (msg.length() > 0) msg.append("; ");
-                msg.append("needs " + lbl.label);
+                msg.append("needs ").append(lbl.label);
                 continue;
 
               case IMPOSSIBLE:
                 if (msg.length() > 0) msg.append("; ");
-                msg.append("needs " + lbl.label + " (check project access)");
+                msg.append("needs ").append(lbl.label)
+                   .append(" (check project access)");
                 continue;
 
               default:
@@ -300,6 +417,7 @@
               rsrc.getChange().getProject().get()));
       }
     }
+    throw new IllegalStateException();
   }
 
   private static Optional<SubmitRecord> findOkRecord(Collection<SubmitRecord> in) {
@@ -315,8 +433,31 @@
     return change != null ? change.getStatus().name().toLowerCase() : "deleted";
   }
 
+  private RevisionResource onBehalfOf(RevisionResource rsrc, SubmitInput in)
+      throws AuthException, UnprocessableEntityException, OrmException {
+    ChangeControl caller = rsrc.getControl();
+    if (!caller.canSubmit()) {
+      throw new AuthException("submit not permitted");
+    }
+    if (!caller.canSubmitAs()) {
+      throw new AuthException("submit on behalf of not permitted");
+    }
+    IdentifiedUser targetUser = accounts.parseId(in.onBehalfOf);
+    if (targetUser == null) {
+      throw new UnprocessableEntityException(String.format(
+          "Account Not Found: %s", in.onBehalfOf));
+    }
+    ChangeControl target = caller.forUser(targetUser);
+    if (!target.getRefControl().isVisible()) {
+      throw new UnprocessableEntityException(String.format(
+          "on_behalf_of account %s cannot see destination ref",
+          targetUser.getAccountId()));
+    }
+    return new RevisionResource(changes.parse(target), rsrc.getPatchSet());
+  }
+
   public static class CurrentRevision implements
-      RestModifyView<ChangeResource, Input> {
+      RestModifyView<ChangeResource, SubmitInput> {
     private final Provider<ReviewDb> dbProvider;
     private final Submit submit;
     private final ChangeJson json;
@@ -331,9 +472,10 @@
     }
 
     @Override
-    public Object apply(ChangeResource rsrc, Input input) throws AuthException,
-        ResourceConflictException, RepositoryNotFoundException, IOException,
-        OrmException {
+    public ChangeInfo apply(ChangeResource rsrc, SubmitInput input)
+        throws AuthException, ResourceConflictException,
+        RepositoryNotFoundException, IOException, OrmException,
+        UnprocessableEntityException {
       PatchSet ps = dbProvider.get().patchSets()
         .get(rsrc.getChange().currentPatchSetId());
       if (ps == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
index 1a670cc..4510616 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
@@ -54,7 +54,7 @@
 import java.util.Map;
 import java.util.Set;
 
-class SuggestReviewers implements RestReadView<ChangeResource> {
+public class SuggestReviewers implements RestReadView<ChangeResource> {
 
   private static final String MAX_SUFFIX = "\u9fa5";
   private static final int MAX = 10;
@@ -268,10 +268,10 @@
     return false;
   }
 
-  static class SuggestedReviewerInfo implements Comparable<SuggestedReviewerInfo> {
+  public static class SuggestedReviewerInfo implements Comparable<SuggestedReviewerInfo> {
     String kind = "gerritcodereview#suggestedreviewer";
-    AccountInfo account;
-    GroupBaseInfo group;
+    public AccountInfo account;
+    public GroupBaseInfo group;
 
     SuggestedReviewerInfo(AccountInfo a) {
       this.account = a;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
index db18f0d..4f739e6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
@@ -14,7 +14,8 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.common.base.Charsets;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
@@ -47,7 +48,7 @@
 
 public class TestSubmitRule implements RestModifyView<RevisionResource, Input> {
   public enum Filters {
-    RUN, SKIP;
+    RUN, SKIP
   }
 
   public static class Input {
@@ -57,6 +58,7 @@
   }
 
   private final ReviewDb db;
+  private final ChangeData.Factory changeDataFactory;
   private final RulesCache rules;
   private final AccountInfo.Loader.Factory accountInfoFactory;
 
@@ -64,16 +66,19 @@
   private Filters filters = Filters.RUN;
 
   @Inject
-  TestSubmitRule(ReviewDb db, RulesCache rules,
+  TestSubmitRule(ReviewDb db,
+      ChangeData.Factory changeDataFactory,
+      RulesCache rules,
       AccountInfo.Loader.Factory infoFactory) {
     this.db = db;
+    this.changeDataFactory = changeDataFactory;
     this.rules = rules;
     this.accountInfoFactory = infoFactory;
   }
 
   @Override
-  public Object apply(RevisionResource rsrc, Input input) throws OrmException,
-      BadRequestException, AuthException {
+  public List<Record> apply(RevisionResource rsrc, Input input)
+      throws AuthException, BadRequestException, OrmException {
     if (input == null) {
       input = new Input();
     }
@@ -88,13 +93,13 @@
         rsrc.getControl().getProjectControl(),
         rsrc.getControl(),
         rsrc.getChange(),
-        new ChangeData(rsrc.getChange()),
+        changeDataFactory.create(db, rsrc.getChange()),
         false,
         "locate_submit_rule", "can_submit",
         "locate_submit_filter", "filter_submit_results",
         input.filters == Filters.SKIP,
         input.rule != null
-          ? new ByteArrayInputStream(input.rule.getBytes(Charsets.UTF_8))
+          ? new ByteArrayInputStream(input.rule.getBytes(UTF_8))
           : null);
 
     List<Term> results;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
index 5a4f9e2..c5b8b8a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
@@ -14,7 +14,8 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.common.base.Charsets;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.common.base.Objects;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -28,7 +29,6 @@
 import com.google.gerrit.server.project.RuleEvalException;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
 import com.googlecode.prolog_cafe.lang.SymbolTerm;
@@ -41,20 +41,24 @@
 
 public class TestSubmitType implements RestModifyView<RevisionResource, Input> {
   private final ReviewDb db;
+  private final ChangeData.Factory changeDataFactory;
   private final RulesCache rules;
 
   @Option(name = "--filters", usage = "impact of filters in parent projects")
   private Filters filters = Filters.RUN;
 
   @Inject
-  TestSubmitType(ReviewDb db, RulesCache rules) {
+  TestSubmitType(ReviewDb db,
+      ChangeData.Factory changeDataFactory,
+      RulesCache rules) {
     this.db = db;
+    this.changeDataFactory = changeDataFactory;
     this.rules = rules;
   }
 
   @Override
   public SubmitType apply(RevisionResource rsrc, Input input)
-      throws OrmException, BadRequestException, AuthException {
+      throws AuthException, BadRequestException {
     if (input == null) {
       input = new Input();
     }
@@ -69,13 +73,13 @@
         rsrc.getControl().getProjectControl(),
         rsrc.getControl(),
         rsrc.getChange(),
-        new ChangeData(rsrc.getChange()),
+        changeDataFactory.create(db, rsrc.getChange()),
         false,
         "locate_submit_type", "get_submit_type",
         "locate_submit_type_filter", "filter_submit_type_results",
         input.filters == Filters.SKIP,
         input.rule != null
-          ? new ByteArrayInputStream(input.rule.getBytes(Charsets.UTF_8))
+          ? new ByteArrayInputStream(input.rule.getBytes(UTF_8))
           : null);
 
     List<Term> results;
@@ -120,7 +124,7 @@
 
     @Override
     public SubmitType apply(RevisionResource resource)
-        throws BadRequestException, OrmException, AuthException {
+        throws AuthException, BadRequestException {
       return test.apply(resource, null);
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
deleted file mode 100644
index e6ce4c2..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-package com.google.gerrit.server.changedetail;
-
-import com.google.gerrit.common.data.ReviewResult;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.ChangeIndexer;
-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.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-public class DeleteDraftPatchSet implements Callable<ReviewResult> {
-
-  public interface Factory {
-    DeleteDraftPatchSet create(PatchSet.Id patchSetId);
-  }
-
-  private final ChangeControl.Factory changeControlFactory;
-  private final ReviewDb db;
-  private final GitRepositoryManager gitManager;
-  private final GitReferenceUpdated gitRefUpdated;
-  private final PatchSetInfoFactory patchSetInfoFactory;
-  private final ChangeIndexer indexer;
-
-  private final PatchSet.Id patchSetId;
-
-  @Inject
-  DeleteDraftPatchSet(ChangeControl.Factory changeControlFactory,
-      ReviewDb db, GitRepositoryManager gitManager,
-      GitReferenceUpdated gitRefUpdated, PatchSetInfoFactory patchSetInfoFactory,
-      ChangeIndexer indexer,
-      @Assisted final PatchSet.Id patchSetId) {
-    this.changeControlFactory = changeControlFactory;
-    this.db = db;
-    this.gitManager = gitManager;
-    this.gitRefUpdated = gitRefUpdated;
-    this.patchSetInfoFactory = patchSetInfoFactory;
-    this.indexer = indexer;
-
-    this.patchSetId = patchSetId;
-  }
-
-  @Override
-  public ReviewResult call() throws NoSuchChangeException, OrmException {
-    final ReviewResult result = new ReviewResult();
-
-    final Change.Id changeId = patchSetId.getParentKey();
-    result.setChangeId(changeId);
-    final ChangeControl control = changeControlFactory.validateFor(changeId);
-    final PatchSet patch = db.patchSets().get(patchSetId);
-    if (patch == null) {
-      throw new NoSuchChangeException(changeId);
-    }
-    if (!patch.isDraft()) {
-      result.addError(new ReviewResult.Error(
-          ReviewResult.Error.Type.NOT_A_DRAFT));
-      return result;
-    }
-
-    if (!control.canDeleteDraft(db)) {
-      result.addError(new ReviewResult.Error(
-          ReviewResult.Error.Type.DELETE_NOT_PERMITTED));
-      return result;
-    }
-    final Change change = control.getChange();
-
-    try {
-      ChangeUtil.deleteOnlyDraftPatchSet(patch, change, gitManager, gitRefUpdated, db);
-    } catch (IOException e) {
-      result.addError(new ReviewResult.Error(
-          ReviewResult.Error.Type.GIT_ERROR, e.getMessage()));
-    }
-
-    List<PatchSet> restOfPatches = db.patchSets().byChange(changeId).toList();
-    if (restOfPatches.size() == 0) {
-      try {
-        ChangeUtil.deleteDraftChange(patchSetId, gitManager, gitRefUpdated, db,
-            indexer);
-        result.setChangeId(null);
-      } catch (IOException e) {
-        result.addError(new ReviewResult.Error(
-            ReviewResult.Error.Type.GIT_ERROR, e.getMessage()));
-      }
-    } else {
-      PatchSet.Id highestId = null;
-      for (PatchSet ps : restOfPatches) {
-        if (highestId == null || ps.getPatchSetId() > highestId.get()) {
-          highestId = ps.getId();
-        }
-      }
-      if (change.currentPatchSetId().equals(patchSetId)) {
-        try {
-          change.setCurrentPatchSet(patchSetInfoFactory.get(db, highestId));
-        } catch (PatchSetInfoNotAvailableException e) {
-          throw new NoSuchChangeException(changeId);
-        }
-        db.changes().update(Collections.singleton(change));
-      }
-    }
-    return result;
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
deleted file mode 100644
index 82b74d1..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/PublishDraft.java
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-package com.google.gerrit.server.changedetail;
-
-import com.google.common.util.concurrent.CheckedFuture;
-import com.google.gerrit.common.ChangeHooks;
-import com.google.gerrit.common.data.LabelTypes;
-import com.google.gerrit.common.data.ReviewResult;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountResolver;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.ChangeIndexer;
-import com.google.gerrit.server.mail.CreateChangeSender;
-import com.google.gerrit.server.mail.PatchSetNotificationSender;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
-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.gwtorm.server.AtomicUpdate;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.io.IOException;
-import java.util.concurrent.Callable;
-
-public class PublishDraft implements Callable<ReviewResult> {
-  public interface Factory {
-    PublishDraft create(PatchSet.Id patchSetId);
-  }
-
-  private final ChangeControl.Factory changeControlFactory;
-  private final ReviewDb db;
-  private final ChangeHooks hooks;
-  private final ChangeIndexer indexer;
-  private final PatchSetNotificationSender sender;
-
-  private final PatchSet.Id patchSetId;
-
-  @Inject
-  PublishDraft(final ChangeControl.Factory changeControlFactory,
-      final ReviewDb db, final ChangeHooks hooks,
-      final GitRepositoryManager repoManager,
-      final PatchSetInfoFactory patchSetInfoFactory,
-      final ApprovalsUtil approvalsUtil,
-      final AccountResolver accountResolver,
-      final CreateChangeSender.Factory createChangeSenderFactory,
-      final ReplacePatchSetSender.Factory replacePatchSetFactory,
-      final ChangeIndexer indexer,
-      final PatchSetNotificationSender sender,
-      @Assisted final PatchSet.Id patchSetId) {
-    this.changeControlFactory = changeControlFactory;
-    this.db = db;
-    this.hooks = hooks;
-    this.indexer = indexer;
-    this.sender = sender;
-
-    this.patchSetId = patchSetId;
-  }
-
-  @Override
-  public ReviewResult call() throws NoSuchChangeException, OrmException,
-      IOException, PatchSetInfoNotAvailableException {
-    final ReviewResult result = new ReviewResult();
-
-    final Change.Id changeId = patchSetId.getParentKey();
-    result.setChangeId(changeId);
-    final ChangeControl control = changeControlFactory.validateFor(changeId);
-    final LabelTypes labelTypes = control.getLabelTypes();
-    final PatchSet patch = db.patchSets().get(patchSetId);
-    if (patch == null) {
-      throw new NoSuchChangeException(changeId);
-    }
-    if (!patch.isDraft()) {
-      result.addError(new ReviewResult.Error(
-          ReviewResult.Error.Type.NOT_A_DRAFT));
-      return result;
-    }
-
-    if (!control.canPublish(db)) {
-      result.addError(new ReviewResult.Error(
-          ReviewResult.Error.Type.PUBLISH_NOT_PERMITTED));
-    } else {
-      final PatchSet updatedPatchSet = db.patchSets().atomicUpdate(patchSetId,
-          new AtomicUpdate<PatchSet>() {
-        @Override
-        public PatchSet update(PatchSet patchset) {
-          patchset.setDraft(false);
-          return patchset;
-        }
-      });
-
-      final Change updatedChange = db.changes().atomicUpdate(changeId,
-          new AtomicUpdate<Change>() {
-        @Override
-        public Change update(Change change) {
-          if (change.getStatus() == Change.Status.DRAFT) {
-            change.setStatus(Change.Status.NEW);
-            ChangeUtil.updated(change);
-          }
-          return change;
-        }
-      });
-
-      if (!updatedPatchSet.isDraft() || updatedChange.getStatus() == Change.Status.NEW) {
-        CheckedFuture<?, IOException> indexFuture = indexer.indexAsync(updatedChange);
-        hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, db);
-
-        sender.send(control.getChange().getStatus() == Change.Status.DRAFT,
-            (IdentifiedUser) control.getCurrentUser(), updatedChange, updatedPatchSet,
-            labelTypes);
-        indexFuture.checkedGet();
-      }
-    }
-
-    return result;
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
index 7825296..8de6904 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -292,10 +292,10 @@
     rebasedCommit = revWalk.parseCommit(newId);
 
     final ChangeControl changeControl =
-        changeControlFactory.validateFor(change.getId(), uploader);
+        changeControlFactory.validateFor(change, uploader);
 
     PatchSetInserter patchSetInserter = patchSetInserterFactory
-        .create(git, revWalk, changeControl.getRefControl(), uploader, change, rebasedCommit)
+        .create(git, revWalk, changeControl, rebasedCommit)
         .setCopyLabels(true)
         .setValidatePolicy(validate)
         .setDraft(originalPatchSet.isDraft())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 0b414f2..6d01e7c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -197,7 +197,7 @@
   }
 
   /** Whether git-over-http should use Gerrit basic authentication scheme. */
-  public boolean isGitBasichAuth() {
+  public boolean isGitBasicAuth() {
     return gitBasicAuth;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
index 2e54f3e..26081ca4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
@@ -35,6 +35,7 @@
   public String runAs;
   public String runGC;
   public String streamEvents;
+  public String viewAllAccounts;
   public String viewCaches;
   public String viewConnections;
   public String viewQueue;
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 6a90165..c6598d4 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
@@ -23,13 +23,16 @@
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.events.HeadUpdatedListener;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.systemstatus.MessageOfTheDay;
 import com.google.gerrit.extensions.webui.TopMenu;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.rules.PrologModule;
 import com.google.gerrit.rules.RulesCache;
 import com.google.gerrit.server.AnonymousUser;
@@ -37,7 +40,6 @@
 import com.google.gerrit.server.CmdLineParserModule;
 import com.google.gerrit.server.FileTypeRegistry;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.InternalUser;
 import com.google.gerrit.server.MimeUtilFileTypeRegistry;
 import com.google.gerrit.server.PluginUser;
 import com.google.gerrit.server.account.AccountByEmailCacheImpl;
@@ -51,22 +53,20 @@
 import com.google.gerrit.server.account.CapabilityControl;
 import com.google.gerrit.server.account.ChangeUserName;
 import com.google.gerrit.server.account.EmailExpander;
-import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupCacheImpl;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.account.GroupDetailFactory;
 import com.google.gerrit.server.account.GroupIncludeCacheImpl;
 import com.google.gerrit.server.account.GroupInfoCacheFactory;
 import com.google.gerrit.server.account.GroupMembers;
-import com.google.gerrit.server.account.IncludingGroupMembership;
-import com.google.gerrit.server.account.InternalGroupBackend;
 import com.google.gerrit.server.account.PerformCreateGroup;
 import com.google.gerrit.server.account.PerformRenameGroup;
-import com.google.gerrit.server.account.UniversalGroupBackend;
 import com.google.gerrit.server.auth.AuthBackend;
 import com.google.gerrit.server.auth.UniversalAuthBackend;
 import com.google.gerrit.server.avatar.AvatarProvider;
 import com.google.gerrit.server.cache.CacheRemovalListener;
+import com.google.gerrit.server.change.ChangeKindCache;
+import com.google.gerrit.server.change.MergeabilityChecker;
 import com.google.gerrit.server.events.EventFactory;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.ChangeCache;
@@ -76,6 +76,7 @@
 import com.google.gerrit.server.git.MergeQueue;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.NotesBranchUtil;
+import com.google.gerrit.server.git.ReceivePackInitializer;
 import com.google.gerrit.server.git.ReloadSubmitQueueOp;
 import com.google.gerrit.server.git.TagCache;
 import com.google.gerrit.server.git.TransferConfig;
@@ -84,8 +85,8 @@
 import com.google.gerrit.server.git.validators.MergeValidationListener;
 import com.google.gerrit.server.git.validators.MergeValidators;
 import com.google.gerrit.server.git.validators.MergeValidators.ProjectConfigValidator;
+import com.google.gerrit.server.group.GroupModule;
 import com.google.gerrit.server.mail.AddReviewerSender;
-import com.google.gerrit.server.mail.CommitMessageEditedSender;
 import com.google.gerrit.server.mail.CreateChangeSender;
 import com.google.gerrit.server.mail.EmailModule;
 import com.google.gerrit.server.mail.FromAddressGenerator;
@@ -95,9 +96,11 @@
 import com.google.gerrit.server.mail.RegisterNewEmailSender;
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
 import com.google.gerrit.server.mail.VelocityRuntimeProvider;
+import com.google.gerrit.server.notedb.NoteDbModule;
 import com.google.gerrit.server.patch.PatchListCacheImpl;
 import com.google.gerrit.server.patch.PatchScriptFactory;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.plugins.ReloadPluginListener;
 import com.google.gerrit.server.project.AccessControlModule;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.CommentLinkInfo;
@@ -109,17 +112,24 @@
 import com.google.gerrit.server.project.ProjectNode;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.project.SectionSortCache;
+import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.ConflictsCacheImpl;
 import com.google.gerrit.server.ssh.SshAddressesModule;
 import com.google.gerrit.server.tools.ToolsCatalog;
 import com.google.gerrit.server.util.IdGenerator;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gerrit.server.validators.GroupCreationValidationListener;
+import com.google.gerrit.server.validators.ProjectCreationValidationListener;
 import com.google.inject.Inject;
 import com.google.inject.TypeLiteral;
+import com.google.inject.internal.UniqueAnnotations;
 
 import org.apache.velocity.runtime.RuntimeInstance;
+import org.eclipse.jgit.transport.PostReceiveHook;
 
 import java.util.List;
+import java.util.Set;
 
 
 /** Starts global state with standard dependencies. */
@@ -141,18 +151,22 @@
     install(authModule);
     install(AccountByEmailCacheImpl.module());
     install(AccountCacheImpl.module());
+    install(ChangeCache.module());
+    install(ChangeKindCache.module());
+    install(ConflictsCacheImpl.module());
     install(GroupCacheImpl.module());
     install(GroupIncludeCacheImpl.module());
     install(PatchListCacheImpl.module());
     install(ProjectCacheImpl.module());
     install(SectionSortCache.module());
     install(TagCache.module());
-    install(ChangeCache.module());
 
     install(new AccessControlModule());
     install(new CmdLineParserModule());
     install(new EmailModule());
     install(new GitModule());
+    install(new GroupModule());
+    install(new NoteDbModule());
     install(new PrologModule());
     install(new SshAddressesModule());
     install(ThreadLocalRequestContext.module());
@@ -162,13 +176,12 @@
     factory(AccountInfoCacheFactory.Factory.class);
     factory(AddReviewerSender.Factory.class);
     factory(CapabilityControl.Factory.class);
+    factory(ChangeData.Factory.class);
     factory(ChangeQueryBuilder.Factory.class);
-    factory(CommitMessageEditedSender.Factory.class);
     factory(CreateChangeSender.Factory.class);
     factory(GroupDetailFactory.Factory.class);
     factory(GroupInfoCacheFactory.Factory.class);
     factory(GroupMembers.Factory.class);
-    factory(InternalUser.Factory.class);
     factory(MergedSender.Factory.class);
     factory(MergeFailSender.Factory.class);
     factory(MergeUtil.Factory.class);
@@ -186,18 +199,15 @@
     bind(AccountVisibility.class)
         .toProvider(AccountVisibilityProvider.class)
         .in(SINGLETON);
+    bind(new TypeLiteral<Set<AccountGroup.UUID>>() {})
+        .annotatedWith(ProjectOwnerGroups.class)
+        .toProvider(ProjectOwnerGroupsProvider.class).in(SINGLETON);
 
     bind(AuthBackend.class).to(UniversalAuthBackend.class).in(SINGLETON);
     DynamicSet.setOf(binder(), AuthBackend.class);
 
     bind(GroupControl.Factory.class).in(SINGLETON);
     bind(GroupControl.GenericFactory.class).in(SINGLETON);
-    factory(IncludingGroupMembership.Factory.class);
-    bind(GroupBackend.class).to(UniversalGroupBackend.class).in(SINGLETON);
-    DynamicSet.setOf(binder(), GroupBackend.class);
-
-    bind(InternalGroupBackend.class).in(SINGLETON);
-    DynamicSet.bind(binder(), GroupBackend.class).to(InternalGroupBackend.class);
 
     bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
     bind(ToolsCatalog.class);
@@ -224,6 +234,7 @@
     install(new AuditModule());
     install(new com.google.gerrit.server.access.Module());
     install(new com.google.gerrit.server.account.Module());
+    install(new com.google.gerrit.server.api.Module());
     install(new com.google.gerrit.server.change.Module());
     install(new com.google.gerrit.server.config.Module());
     install(new com.google.gerrit.server.group.Module());
@@ -234,17 +245,27 @@
     DynamicSet.setOf(binder(), CacheRemovalListener.class);
     DynamicMap.mapOf(binder(), CapabilityDefinition.class);
     DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
+    DynamicSet.setOf(binder(), ReceivePackInitializer.class);
+    DynamicSet.setOf(binder(), PostReceiveHook.class);
     DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
     DynamicSet.setOf(binder(), ProjectDeletedListener.class);
+    DynamicSet.setOf(binder(), HeadUpdatedListener.class);
     DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class);
+    DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(MergeabilityChecker.class);
+    DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
+        .to(ProjectConfigEntry.UpdateChecker.class);
     DynamicSet.setOf(binder(), ChangeListener.class);
     DynamicSet.setOf(binder(), CommitValidationListener.class);
     DynamicSet.setOf(binder(), MergeValidationListener.class);
+    DynamicSet.setOf(binder(), ProjectCreationValidationListener.class);
+    DynamicSet.setOf(binder(), GroupCreationValidationListener.class);
     DynamicItem.itemOf(binder(), AvatarProvider.class);
     DynamicSet.setOf(binder(), LifecycleListener.class);
     DynamicSet.setOf(binder(), TopMenu.class);
+    DynamicSet.setOf(binder(), MessageOfTheDay.class);
     DynamicMap.mapOf(binder(), DownloadScheme.class);
     DynamicMap.mapOf(binder(), DownloadCommand.class);
+    DynamicMap.mapOf(binder(), ProjectConfigEntry.class);
 
     bind(AnonymousUser.class);
 
@@ -259,5 +280,9 @@
 
     bind(new TypeLiteral<List<CommentLinkInfo>>() {})
         .toProvider(CommentLinkProvider.class).in(SINGLETON);
+
+    bind(ReloadPluginListener.class)
+        .annotatedWith(UniqueAnnotations.create())
+        .to(PluginConfigFactory.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index af14015..31fa329 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -18,12 +18,9 @@
 
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.RequestCleanup;
-import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
-import com.google.gerrit.server.changedetail.PublishDraft;
 import com.google.gerrit.server.git.BanCommit;
 import com.google.gerrit.server.git.MergeOp;
 import com.google.gerrit.server.git.SubmoduleOp;
-import com.google.gerrit.server.patch.RemoveReviewer;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.PerRequestProjectControlCache;
 import com.google.gerrit.server.project.ProjectControl;
@@ -48,9 +45,6 @@
     // Not really per-request, but dammit, I don't know where else to
     // easily park this stuff.
     //
-    factory(DeleteDraftPatchSet.Factory.class);
-    factory(PublishDraft.Factory.class);
-    factory(RemoveReviewer.Factory.class);
     factory(SuggestParentCandidates.Factory.class);
     factory(BanCommit.Factory.class);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
index 641a48f..5e2f71f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.config;
 
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.util.ServerRequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.inject.Inject;
@@ -35,7 +35,7 @@
     // If no group was set, default to "registered users"
     //
     if (groupIds.isEmpty()) {
-      groupIds = Collections.singleton(AccountGroup.REGISTERED_USERS);
+      groupIds = Collections.singleton(SystemGroupBackend.REGISTERED_USERS);
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
index edae46b..79cfd88 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
@@ -14,17 +14,15 @@
 
 package com.google.gerrit.server.config;
 
-import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.util.ServerRequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.lib.Config;
 
-import java.util.Collections;
-import java.util.HashSet;
-
 public class GitUploadPackGroupsProvider extends GroupSetProvider {
   @Inject
   public GitUploadPackGroupsProvider(GroupBackend gb,
@@ -36,10 +34,9 @@
     // If no group was set, default to "registered users" and "anonymous"
     //
     if (groupIds.isEmpty()) {
-      HashSet<AccountGroup.UUID> all = new HashSet<AccountGroup.UUID>();
-      all.add(AccountGroup.REGISTERED_USERS);
-      all.add(AccountGroup.ANONYMOUS_USERS);
-      groupIds = Collections.unmodifiableSet(all);
+      groupIds = ImmutableSet.of(
+          SystemGroupBackend.REGISTERED_USERS,
+          SystemGroupBackend.ANONYMOUS_USERS);
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
index c64f786..88616e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
@@ -19,9 +19,6 @@
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.config.CapabilityDefinition;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -43,9 +40,7 @@
 
   @Override
   public Map<String, CapabilityInfo> apply(ConfigResource resource)
-      throws AuthException, BadRequestException, ResourceConflictException,
-      IllegalArgumentException, SecurityException, IllegalAccessException,
-      NoSuchFieldException {
+      throws IllegalAccessException, NoSuchFieldException {
     Map<String, CapabilityInfo> output = Maps.newTreeMap();
     collectCoreCapabilities(output);
     collectPluginCapabilities(output);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTopMenus.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTopMenus.java
index 68ae5c1..c8e9d1e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTopMenus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTopMenus.java
@@ -31,7 +31,7 @@
   }
 
   @Override
-  public Object apply(ConfigResource resource) {
+  public List<TopMenu.MenuEntry> apply(ConfigResource resource) {
     List<TopMenu.MenuEntry> entries = Lists.newArrayList();
     for (TopMenu extension : extensions) {
       entries.addAll(extension.getEntries());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java
index b0f9ba3..27fcd72 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfig.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.config;
 
 import com.google.common.base.Objects;
+import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.ProjectState;
@@ -23,6 +24,7 @@
 import org.eclipse.jgit.lib.Config;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Set;
 
 public class PluginConfig {
@@ -81,30 +83,74 @@
   }
 
   public String getString(String name, String defaultValue) {
-    return Objects.firstNonNull(cfg.getString(PLUGIN, pluginName, name), defaultValue);
+    if (defaultValue == null) {
+      return cfg.getString(PLUGIN, pluginName, name);
+    } else {
+      return Objects.firstNonNull(cfg.getString(PLUGIN, pluginName, name), defaultValue);
+    }
+  }
+
+  public void setString(String name, String value) {
+    if (Strings.isNullOrEmpty(value)) {
+      cfg.unset(PLUGIN, pluginName, name);
+    } else {
+      cfg.setString(PLUGIN, pluginName, name, value);
+    }
   }
 
   public String[] getStringList(String name) {
     return cfg.getStringList(PLUGIN, pluginName, name);
   }
 
+  public void setStringList(String name, List<String> values) {
+    if (values == null || values.isEmpty()) {
+      cfg.unset(PLUGIN, pluginName, name);
+    } else {
+      cfg.setStringList(PLUGIN, pluginName, name, values);
+    }
+  }
+
   public int getInt(String name, int defaultValue) {
     return cfg.getInt(PLUGIN, pluginName, name, defaultValue);
   }
 
+  public void setInt(String name, int value) {
+    cfg.setInt(PLUGIN, pluginName, name, value);
+  }
+
   public long getLong(String name, long defaultValue) {
     return cfg.getLong(PLUGIN, pluginName, name, defaultValue);
   }
 
+  public void setLong(String name, long value) {
+    cfg.setLong(PLUGIN, pluginName, name, value);
+  }
+
   public boolean getBoolean(String name, boolean defaultValue) {
     return cfg.getBoolean(PLUGIN, pluginName, name, defaultValue);
   }
 
+  public void setBoolean(String name, boolean value) {
+    cfg.setBoolean(PLUGIN, pluginName, name, value);
+  }
+
   public <T extends Enum<?>> T getEnum(String name, T defaultValue) {
     return cfg.getEnum(PLUGIN, pluginName, name, defaultValue);
   }
 
+  public <T extends Enum<?>> void setEnum(String name, T value) {
+    cfg.setEnum(PLUGIN, pluginName, name, value);
+  }
+
   public <T extends Enum<?>> T getEnum(T[] all, String name, T defaultValue) {
     return cfg.getEnum(all, PLUGIN, pluginName, name, defaultValue);
   }
+
+  public void unset(String name) {
+    cfg.unset(PLUGIN, pluginName, name);
+  }
+
+  public Set<String> getNames() {
+    return cfg.getNames(PLUGIN, pluginName);
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
index 294d8a5..11a7c90 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
@@ -14,27 +14,54 @@
 
 package com.google.gerrit.server.config;
 
+import com.google.common.collect.Maps;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.ProjectLevelConfig;
+import com.google.gerrit.server.plugins.Plugin;
+import com.google.gerrit.server.plugins.ReloadPluginListener;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
 
 @Singleton
-public class PluginConfigFactory {
-  private final Config cfg;
+public class PluginConfigFactory implements ReloadPluginListener {
+  private static final Logger log =
+      LoggerFactory.getLogger(PluginConfigFactory.class);
+
+  private final SitePaths site;
+  private final GerritServerConfigProvider cfgProvider;
   private final ProjectCache projectCache;
   private final ProjectState.Factory projectStateFactory;
+  private final Map<String, Config> pluginConfigs;
+
+  private volatile FileSnapshot cfgSnapshot;
+  private volatile Config cfg;
 
   @Inject
-  PluginConfigFactory(@GerritServerConfig Config cfg,
+  PluginConfigFactory(SitePaths site, GerritServerConfigProvider cfgProvider,
       ProjectCache projectCache, ProjectState.Factory projectStateFactory) {
-    this.cfg = cfg;
+    this.site = site;
+    this.cfgProvider = cfgProvider;
     this.projectCache = projectCache;
     this.projectStateFactory = projectStateFactory;
+    this.pluginConfigs = Maps.newHashMap();
+
+    this.cfgSnapshot = FileSnapshot.save(site.gerrit_config);
+    this.cfg = cfgProvider.get();
   }
 
   /**
@@ -54,6 +81,31 @@
    * @return the plugin configuration from the 'gerrit.config' file
    */
   public PluginConfig getFromGerritConfig(String pluginName) {
+    return getFromGerritConfig(pluginName, false);
+  }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * 'gerrit.config' file.
+   *
+   * The returned plugin configuration provides access to all parameters of the
+   * 'gerrit.config' file that are set in the 'plugin' subsection of the
+   * specified plugin.
+   *
+   * E.g.: [plugin "my-plugin"] myKey = myValue
+   *
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @param refresh if <code>true</code> it is checked if the 'gerrit.config'
+   *        file was modified and if yes the Gerrit configuration is reloaded,
+   *        if <code>false</code> the cached Gerrit configuration is used
+   * @return the plugin configuration from the 'gerrit.config' file
+   */
+  public PluginConfig getFromGerritConfig(String pluginName, boolean refresh) {
+    if (refresh && cfgSnapshot.isModified(site.gerrit_config)) {
+      cfgSnapshot = FileSnapshot.save(site.gerrit_config);
+      cfg = cfgProvider.get();
+    }
     return new PluginConfig(pluginName, cfg);
   }
 
@@ -84,6 +136,28 @@
     if (projectState == null) {
       throw new NoSuchProjectException(projectName);
     }
+    return getFromProjectConfig(projectState, pluginName);
+  }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * 'project.config' file of the specified project.
+   *
+   * The returned plugin configuration provides access to all parameters of the
+   * 'project.config' file that are set in the 'plugin' subsection of the
+   * specified plugin.
+   *
+   * E.g.: [plugin "my-plugin"] myKey = myValue
+   *
+   * @param projectState the project for which the plugin configuration should
+   *        be returned
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the 'project.config' file of the
+   *         specified project
+   */
+  public PluginConfig getFromProjectConfig(ProjectState projectState,
+      String pluginName) {
     return projectState.getConfig().getPluginConfig(pluginName);
   }
 
@@ -128,4 +202,177 @@
     return getFromProjectConfig(projectName, pluginName).withInheritance(
         projectStateFactory);
   }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * 'project.config' file of the specified project. Parameters which are not
+   * set in the 'project.config' of this project are inherited from the parent
+   * project's 'project.config' files.
+   *
+   * The returned plugin configuration provides access to all parameters of the
+   * 'project.config' file that are set in the 'plugin' subsection of the
+   * specified plugin.
+   *
+   * E.g.: child project: [plugin "my-plugin"] myKey = childValue
+   *
+   * parent project: [plugin "my-plugin"] myKey = parentValue anotherKey =
+   * someValue
+   *
+   * return: [plugin "my-plugin"] myKey = childValue anotherKey = someValue
+   *
+   * @param projectState the project for which the plugin configuration should
+   *        be returned
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the 'project.config' file of the
+   *         specified project with inherited non-set parameters from the parent
+   *         projects
+   */
+  public PluginConfig getFromProjectConfigWithInheritance(
+      ProjectState projectState, String pluginName) {
+    return getFromProjectConfig(projectState, pluginName).withInheritance(
+        projectStateFactory);
+  }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * plugin configuration file 'etc/<plugin-name>.config'.
+   *
+   * The plugin configuration is only loaded once and is then cached.
+   *
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the 'etc/<plugin-name>.config' file
+   */
+  public synchronized Config getGlobalPluginConfig(String pluginName) {
+    if (pluginConfigs.containsKey(pluginName)) {
+      return pluginConfigs.get(pluginName);
+    }
+
+    File pluginConfigFile = new File(site.etc_dir, pluginName + ".config");
+    FileBasedConfig cfg = new FileBasedConfig(pluginConfigFile, FS.DETECTED);
+    pluginConfigs.put(pluginName, cfg);
+    if (!cfg.getFile().exists()) {
+      log.info("No " + pluginConfigFile.getAbsolutePath() + "; assuming defaults");
+      return cfg;
+    }
+
+    try {
+      cfg.load();
+    } catch (IOException e) {
+      log.warn("Failed to load " + pluginConfigFile.getAbsolutePath(), e);
+    } catch (ConfigInvalidException e) {
+      log.warn("Failed to load " + pluginConfigFile.getAbsolutePath(), e);
+    }
+
+    return cfg;
+  }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * '<plugin-name>.config' file in the 'refs/meta/config' branch of the
+   * specified project.
+   *
+   * @param projectName the name of the project for which the plugin
+   *        configuration should be returned
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the '<plugin-name>.config' file of
+   *         the specified project
+   * @throws NoSuchProjectException thrown if the specified project does not
+   *         exist
+   */
+  public Config getProjectPluginConfig(Project.NameKey projectName,
+      String pluginName) throws NoSuchProjectException {
+    return getPluginConfig(projectName, pluginName).get();
+  }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * '<plugin-name>.config' file in the 'refs/meta/config' branch of the
+   * specified project.
+   *
+   * @param projectState the project for which the plugin configuration should
+   *        be returned
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the '<plugin-name>.config' file of
+   *         the specified project
+   */
+  public Config getProjectPluginConfig(ProjectState projectState,
+      String pluginName) {
+    return projectState.getConfig(pluginName).get();
+  }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * '<plugin-name>.config' file in the 'refs/meta/config' branch of the
+   * specified project. Parameters which are not set in the
+   * '<plugin-name>.config' of this project are inherited from the parent
+   * project's '<plugin-name>.config' files.
+   *
+   * E.g.: child project: [mySection "mySubsection"] myKey = childValue
+   *
+   * parent project: [mySection "mySubsection"] myKey = parentValue anotherKey =
+   * someValue
+   *
+   * return: [mySection "mySubsection"] myKey = childValue anotherKey =
+   * someValue
+   *
+   * @param projectName the name of the project for which the plugin
+   *        configuration should be returned
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the '<plugin-name>.config' file of
+   *         the specified project with inheriting non-set parameters from the
+   *         parent projects
+   * @throws NoSuchProjectException thrown if the specified project does not
+   *         exist
+   */
+  public Config getProjectPluginConfigWithInheritance(Project.NameKey projectName,
+      String pluginName) throws NoSuchProjectException {
+    return getPluginConfig(projectName, pluginName).getWithInheritance();
+  }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * '<plugin-name>.config' file in the 'refs/meta/config' branch of the
+   * specified project. Parameters which are not set in the
+   * '<plugin-name>.config' of this project are inherited from the parent
+   * project's '<plugin-name>.config' files.
+   *
+   * E.g.: child project: [mySection "mySubsection"] myKey = childValue
+   *
+   * parent project: [mySection "mySubsection"] myKey = parentValue anotherKey =
+   * someValue
+   *
+   * return: [mySection "mySubsection"] myKey = childValue anotherKey =
+   * someValue
+   *
+   * @param projectState the project for which the plugin configuration should
+   *        be returned
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the '<plugin-name>.config' file of
+   *         the specified project with inheriting non-set parameters from the
+   *         parent projects
+   */
+  public Config getProjectPluginConfigWithInheritance(ProjectState projectState,
+      String pluginName) {
+    return projectState.getConfig(pluginName).getWithInheritance();
+  }
+
+  private ProjectLevelConfig getPluginConfig(Project.NameKey projectName,
+      String pluginName) throws NoSuchProjectException {
+    ProjectState projectState = projectCache.get(projectName);
+    if (projectState == null) {
+      throw new NoSuchProjectException(projectName);
+    }
+    return projectState.getConfig(pluginName);
+  }
+
+  @Override
+  public synchronized void onReloadPlugin(Plugin oldPlugin, Plugin newPlugin) {
+    pluginConfigs.remove(oldPlugin.getName());
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
new file mode 100644
index 0000000..385570b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -0,0 +1,294 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicMap.Entry;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+@ExtensionPoint
+public class ProjectConfigEntry {
+  public enum Type {
+    STRING, INT, LONG, BOOLEAN, LIST, ARRAY
+  }
+
+  private final String displayName;
+  private final String description;
+  private final boolean inheritable;
+  private final String defaultValue;
+  private final Type type;
+  private final List<String> permittedValues;
+
+  public ProjectConfigEntry(String displayName, String defaultValue) {
+    this(displayName, defaultValue, false);
+  }
+
+  public ProjectConfigEntry(String displayName, String defaultValue,
+      boolean inheritable) {
+    this(displayName, defaultValue, inheritable, null);
+  }
+
+  public ProjectConfigEntry(String displayName, String defaultValue,
+      boolean inheritable, String description) {
+    this(displayName, defaultValue, Type.STRING, null, inheritable, description);
+  }
+
+  public ProjectConfigEntry(String displayName, int defaultValue) {
+    this(displayName, defaultValue, false);
+  }
+
+  public ProjectConfigEntry(String displayName, int defaultValue,
+      boolean inheritable) {
+    this(displayName, defaultValue, inheritable, null);
+  }
+
+  public ProjectConfigEntry(String displayName, int defaultValue,
+      boolean inheritable, String description) {
+    this(displayName, Integer.toString(defaultValue), Type.INT, null,
+        inheritable, description);
+  }
+
+  public ProjectConfigEntry(String displayName, long defaultValue) {
+    this(displayName, defaultValue, false);
+  }
+
+  public ProjectConfigEntry(String displayName, long defaultValue,
+      boolean inheritable) {
+    this(displayName, defaultValue, inheritable, null);
+  }
+
+  public ProjectConfigEntry(String displayName, long defaultValue,
+      boolean inheritable, String description) {
+    this(displayName, Long.toString(defaultValue), Type.LONG, null,
+        inheritable, description);
+  }
+
+  // For inheritable boolean use 'LIST' type with InheritableBoolean
+  public ProjectConfigEntry(String displayName, boolean defaultValue) {
+    this(displayName, defaultValue, null);
+  }
+
+  //For inheritable boolean use 'LIST' type with InheritableBoolean
+  public ProjectConfigEntry(String displayName, boolean defaultValue,
+      String description) {
+    this(displayName, Boolean.toString(defaultValue), Type.BOOLEAN, null,
+        false, description);
+  }
+
+  public ProjectConfigEntry(String displayName, String defaultValue,
+      List<String> permittedValues) {
+    this(displayName, defaultValue, permittedValues, false);
+  }
+
+  public ProjectConfigEntry(String displayName, String defaultValue,
+      List<String> permittedValues, boolean inheritable) {
+    this(displayName, defaultValue, permittedValues, inheritable, null);
+  }
+
+  public ProjectConfigEntry(String displayName, String defaultValue,
+      List<String> permittedValues, boolean inheritable, String description) {
+    this(displayName, defaultValue, Type.LIST, permittedValues, inheritable,
+        description);
+  }
+
+  public <T extends Enum<?>> ProjectConfigEntry(String displayName,
+      T defaultValue, Class<T> permittedValues) {
+    this(displayName, defaultValue, permittedValues, false);
+  }
+
+  public <T extends Enum<?>> ProjectConfigEntry(String displayName,
+      T defaultValue, Class<T> permittedValues, boolean inheritable) {
+    this(displayName, defaultValue, permittedValues, inheritable, null);
+  }
+
+  public <T extends Enum<?>> ProjectConfigEntry(String displayName,
+      T defaultValue, Class<T> permittedValues, boolean inheritable,
+      String description) {
+    this(displayName, defaultValue.name(), Type.LIST, Lists.transform(
+        Arrays.asList(permittedValues.getEnumConstants()),
+        new Function<Enum<?>, String>() {
+          @Override
+          public String apply(Enum<?> e) {
+            return e.name();
+          }
+        }), inheritable, description);
+  }
+
+  public ProjectConfigEntry(String displayName, String defaultValue,
+      Type type, List<String> permittedValues, boolean inheritable,
+      String description) {
+    this.displayName = displayName;
+    this.defaultValue = defaultValue;
+    this.type = type;
+    this.permittedValues = permittedValues;
+    this.inheritable = inheritable;
+    this.description = description;
+    if (type == Type.ARRAY && inheritable) {
+      throw new ProvisionException(
+          "ARRAY doesn't support inheritable values");
+    }
+  }
+
+  public String getDisplayName() {
+    return displayName;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public boolean isInheritable() {
+    return inheritable;
+  }
+
+  public String getDefaultValue() {
+    return defaultValue;
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  public List<String> getPermittedValues() {
+    return permittedValues;
+  }
+
+  public boolean isEditable(ProjectState project) {
+    return true;
+  }
+
+  public String getWarning(ProjectState project) {
+    return null;
+  }
+
+  public void onUpdate(Project.NameKey project, String oldValue, String newValue) {
+  }
+
+  public void onUpdate(Project.NameKey project, Boolean oldValue, Boolean newValue) {
+  }
+
+  public void onUpdate(Project.NameKey project, Integer oldValue, Integer newValue) {
+  }
+
+  public void onUpdate(Project.NameKey project, Long oldValue, Long newValue) {
+  }
+
+  public static class UpdateChecker implements GitReferenceUpdatedListener {
+    private static final Logger log = LoggerFactory.getLogger(UpdateChecker.class);
+
+    private final MetaDataUpdate.Server metaDataUpdateFactory;
+    private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
+
+    @Inject
+    UpdateChecker(MetaDataUpdate.Server metaDataUpdateFactory,
+        DynamicMap<ProjectConfigEntry> pluginConfigEntries) {
+      this.metaDataUpdateFactory = metaDataUpdateFactory;
+      this.pluginConfigEntries = pluginConfigEntries;
+    }
+
+    @Override
+    public void onGitReferenceUpdated(Event event) {
+      Project.NameKey p = new Project.NameKey(event.getProjectName());
+      if (!event.getRefName().equals(RefNames.REFS_CONFIG)) {
+        return;
+      }
+
+      try {
+        ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
+        ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
+        if (oldCfg != null && newCfg != null) {
+          for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+            ProjectConfigEntry configEntry = e.getProvider().get();
+            String newValue = getValue(newCfg, e);
+            String oldValue = getValue(oldCfg, e);
+            if ((newValue == null && oldValue == null)
+                || (newValue != null && newValue.equals(oldValue))) {
+              return;
+            }
+
+            switch (configEntry.getType()) {
+              case BOOLEAN:
+                configEntry.onUpdate(p, toBoolean(oldValue), toBoolean(newValue));
+                break;
+              case INT:
+                configEntry.onUpdate(p, toInt(oldValue), toInt(newValue));
+                break;
+              case LONG:
+                configEntry.onUpdate(p, toLong(oldValue), toLong(newValue));
+                break;
+              case LIST:
+              case STRING:
+              default:
+                configEntry.onUpdate(p, oldValue, newValue);
+            }
+          }
+        }
+      } catch (IOException | ConfigInvalidException e) {
+        log.error(String.format(
+            "Failed to check if plugin config of project %s was updated.",
+            p.get()), e);
+      }
+    }
+
+    private ProjectConfig parseConfig(Project.NameKey p, String idStr)
+        throws IOException, ConfigInvalidException, RepositoryNotFoundException {
+      ObjectId id = ObjectId.fromString(idStr);
+      if (ObjectId.zeroId().equals(id)) {
+        return null;
+      }
+      return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
+    }
+
+    private static String getValue(ProjectConfig cfg, Entry<ProjectConfigEntry> e) {
+      String value = cfg.getPluginConfig(e.getPluginName()).getString(e.getExportName());
+      if (value == null) {
+        value = e.getProvider().get().getDefaultValue();
+      }
+      return value;
+    }
+  }
+
+  private static Boolean toBoolean(String value) {
+    return value != null ? Boolean.parseBoolean(value) : null;
+  }
+
+  private static Integer toInt(String value) {
+    return value != null ? Integer.parseInt(value) : null;
+  }
+
+  private static Long toLong(String value) {
+    return value != null ? Long.parseLong(value) : null;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/TrackingFooters.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/TrackingFooters.java
index bfb6db5..6fbc206 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/TrackingFooters.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/TrackingFooters.java
@@ -14,12 +14,12 @@
 
 package com.google.gerrit.server.config;
 
-import com.google.common.collect.Sets;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
 
 import org.eclipse.jgit.revwalk.FooterLine;
 
 import java.util.List;
-import java.util.Set;
 import java.util.regex.Matcher;
 
 public class TrackingFooters {
@@ -33,8 +33,12 @@
     return trackingFooters;
   }
 
-  public Set<String> extract(List<FooterLine> lines) {
-    Set<String> r = Sets.newHashSet();
+  public boolean isEmpty() {
+    return trackingFooters.isEmpty();
+  }
+
+  public Multimap<String, String> extract(List<FooterLine> lines) {
+    Multimap<String, String> r = ArrayListMultimap.create();
     for (FooterLine footer : lines) {
       for (TrackingFooter config : trackingFooters) {
         if (footer.matches(config.footerKey())) {
@@ -44,7 +48,7 @@
                 ? m.group(1)
                 : m.group();
             if (!id.isEmpty()) {
-              r.add(id);
+              r.put(config.system(), id);
             }
           }
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java
index d5cd11a..835dc40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java
@@ -85,21 +85,10 @@
       Class.forName(PGPPublicKey.class.getName());
       addBouncyCastleProvider();
       return true;
-    } catch (NoClassDefFoundError noBouncyCastle) {
-      return false;
-    } catch (ClassNotFoundException noBouncyCastle) {
-      return false;
-    } catch (SecurityException noBouncyCastle) {
-      return false;
-    } catch (NoSuchMethodException noBouncyCastle) {
-      return false;
-    } catch (InstantiationException noBouncyCastle) {
-      return false;
-    } catch (IllegalAccessException noBouncyCastle) {
-      return false;
-    } catch (InvocationTargetException noBouncyCastle) {
-      return false;
-    } catch (ClassCastException noBouncyCastle) {
+    } catch (NoClassDefFoundError | ClassNotFoundException | SecurityException
+        | NoSuchMethodException | InstantiationException
+        | IllegalAccessException | InvocationTargetException
+        | ClassCastException noBouncyCastle) {
       return false;
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
index 17c9060..8c1fdb6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
@@ -108,16 +108,10 @@
     return true;
   }
 
-  @SuppressWarnings("resource")
   private static PGPPublicKeyRingCollection readPubRing(final File pub) {
-    try {
-      InputStream in = new FileInputStream(pub);
-      try {
-        in = PGPUtil.getDecoderStream(in);
+    try (InputStream fin = new FileInputStream(pub);
+        InputStream in = PGPUtil.getDecoderStream(fin)) {
         return new PGPPublicKeyRingCollection(in);
-      } finally {
-        in.close();
-      }
     } catch (IOException e) {
       throw new ProvisionException("Cannot read " + pub, e);
     } catch (PGPException e) {
@@ -173,17 +167,23 @@
     }
   }
 
+  @SuppressWarnings("deprecation")
+  private final PGPEncryptedDataGenerator cpk()
+      throws NoSuchProviderException, PGPException {
+    PGPEncryptedDataGenerator cpk =
+        new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, true, prng, "BC");
+    cpk.addMethod(dest);
+    return cpk;
+  }
+
   private byte[] encrypt(final String name, final Date date,
       final byte[] rawText) throws NoSuchProviderException, PGPException,
       IOException {
     final byte[] zText = compress(name, date, rawText);
-    final PGPEncryptedDataGenerator cpk =
-        new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, true, prng, "BC");
-    cpk.addMethod(dest);
 
     final ByteArrayOutputStream buf = new ByteArrayOutputStream();
     final ArmoredOutputStream aout = new ArmoredOutputStream(buf);
-    final OutputStream cout = cpk.open(aout, zText.length);
+    final OutputStream cout = cpk().open(aout, zText.length);
     cout.write(zText);
     cout.close();
     aout.close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java
index e210865..471f6a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java
@@ -17,7 +17,7 @@
 import java.net.URL;
 import java.net.URLConnection;
 
-/** {@link ContactStoreConnection} with an underlying {@HttpURLConnection}. */
+/** {@link ContactStoreConnection} with an underlying {@literal @HttpURLConnection}. */
 public class HttpContactStoreConnection implements ContactStoreConnection {
   public static Module module() {
     return new AbstractModule() {
@@ -48,9 +48,9 @@
         "application/x-www-form-urlencoded; charset=UTF-8");
     conn.setDoOutput(true);
     conn.setFixedLengthStreamingMode(body.length);
-    final OutputStream out = conn.getOutputStream();
-    out.write(body);
-    out.close();
+    try (OutputStream out = conn.getOutputStream()) {
+      out.write(body);
+    }
     if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
       throw new IOException("Connection failed: " + conn.getResponseCode());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java
index 7339829..5f5fd33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java
@@ -43,4 +43,5 @@
     public List<DependencyAttribute> dependsOn;
     public List<DependencyAttribute> neededBy;
     public List<SubmitRecordAttribute> submitRecords;
+    public List<AccountAttribute> allReviewers;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/Constants.java
similarity index 61%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/documentation/Constants.java
index c48f968..388c1d8 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/Constants.java
@@ -12,14 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.server.documentation;
 
-public class GroupInfo {
-  public String id;
-  public String name;
-  public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+public class Constants {
+  public static final String PACKAGE = "com/google/gerrit/server/documentation";
+  public static final String INDEX_ZIP = "index.zip";
+
+  public static final String DOC_FIELD = "doc";
+  public static final String TITLE_FIELD = "title";
+  public static final String URL_FIELD = "url";
+
+  private Constants() {}
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
new file mode 100644
index 0000000..e4fa926
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -0,0 +1,152 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.documentation;
+
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.queryparser.classic.ParseException;
+import org.apache.lucene.queryparser.classic.QueryParser;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+@Singleton
+public class QueryDocumentationExecutor {
+  private static final Logger log =
+      LoggerFactory.getLogger(QueryDocumentationExecutor.class);
+
+  private static final Version LUCENE_VERSION = Version.LUCENE_46;
+
+  private IndexSearcher searcher;
+  private QueryParser parser;
+
+  public static class DocResult {
+    public String title;
+    public String url;
+    public String content;
+  }
+
+  @Inject
+  public QueryDocumentationExecutor() {
+    try {
+      Directory dir = readIndexDirectory();
+      if (dir == null) {
+        searcher = null;
+        parser = null;
+        return;
+      }
+      IndexReader reader = DirectoryReader.open(dir);
+      searcher = new IndexSearcher(reader);
+      StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION);
+      parser = new QueryParser(LUCENE_VERSION, Constants.DOC_FIELD, analyzer);
+    } catch (IOException e) {
+      log.error("Cannot initialize documentation full text index", e);
+      searcher = null;
+      parser = null;
+    }
+  }
+
+  public List<DocResult> doQuery(String q) throws DocQueryException {
+    if (parser == null || searcher == null) {
+      throw new DocQueryException("Documentation search not available");
+    }
+    try {
+      Query query = parser.parse(q);
+      // TODO(fishywang): Currently as we don't have much documentation, we just use MAX_VALUE here
+      // and skipped paging. Maybe add paging later.
+      TopDocs results = searcher.search(query, Integer.MAX_VALUE);
+      ScoreDoc[] hits = results.scoreDocs;
+      int totalHits = results.totalHits;
+
+      List<DocResult> out = Lists.newArrayListWithCapacity(totalHits);
+      for (int i = 0; i < totalHits; i++) {
+        DocResult result = new DocResult();
+        Document doc = searcher.doc(hits[i].doc);
+        result.url = doc.get(Constants.URL_FIELD);
+        result.title = doc.get(Constants.TITLE_FIELD);
+        out.add(result);
+      }
+      return out;
+    } catch (IOException e) {
+      throw new DocQueryException(e);
+    } catch (ParseException e) {
+      throw new DocQueryException(e);
+    }
+  }
+
+  protected Directory readIndexDirectory() throws IOException {
+    Directory dir = new RAMDirectory();
+    byte[] buffer = new byte[4096];
+    InputStream index = getClass().getResourceAsStream(Constants.INDEX_ZIP);
+    if (index == null) {
+      log.warn("No index available");
+      return null;
+    }
+
+    ZipInputStream zip = new ZipInputStream(index);
+    try {
+      ZipEntry entry;
+      while ((entry = zip.getNextEntry()) != null) {
+        IndexOutput out = dir.createOutput(entry.getName(), null);
+        int count;
+        while ((count = zip.read(buffer)) != -1) {
+          out.writeBytes(buffer, count);
+        }
+        out.close();
+      }
+    } finally {
+      zip.close();
+    }
+    // We must NOT call dir.close() here, as DirectoryReader.open() expects an opened directory.
+    return dir;
+  }
+
+  @SuppressWarnings("serial")
+  public static class DocQueryException extends Exception {
+    DocQueryException() {
+    }
+
+    DocQueryException(String msg) {
+      super(msg);
+    }
+
+    DocQueryException(String msg, Throwable e) {
+      super(msg, e);
+    }
+
+    DocQueryException(Throwable e) {
+      super(e);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 98e803f..9d48574 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.events;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
@@ -28,9 +30,9 @@
 import com.google.gerrit.reviewdb.client.PatchSetAncestor;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.client.TrackingId;
 import com.google.gerrit.reviewdb.client.UserIdentity;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -46,7 +48,7 @@
 import com.google.gerrit.server.data.SubmitLabelAttribute;
 import com.google.gerrit.server.data.SubmitRecordAttribute;
 import com.google.gerrit.server.data.TrackingIdAttribute;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListEntry;
@@ -80,7 +82,8 @@
   private final PatchSetInfoFactory psInfoFactory;
   private final PersonIdent myIdent;
   private final Provider<ReviewDb> db;
-  private final GitRepositoryManager repoManager;
+  private final ChangeData.Factory changeDataFactory;
+  private final ApprovalsUtil approvalsUtil;
 
   @Inject
   EventFactory(AccountCache accountCache,
@@ -88,7 +91,9 @@
       PatchSetInfoFactory psif,
       PatchListCache patchListCache, SchemaFactory<ReviewDb> schema,
       @GerritPersonIdent PersonIdent myIdent,
-      Provider<ReviewDb> db, GitRepositoryManager repoManager) {
+      Provider<ReviewDb> db,
+      ChangeData.Factory changeDataFactory,
+      ApprovalsUtil approvalsUtil) {
     this.accountCache = accountCache;
     this.urlProvider = urlProvider;
     this.patchListCache = patchListCache;
@@ -96,7 +101,8 @@
     this.psInfoFactory = psif;
     this.myIdent = myIdent;
     this.db = db;
-    this.repoManager = repoManager;
+    this.changeDataFactory = changeDataFactory;
+    this.approvalsUtil = approvalsUtil;
   }
 
   /**
@@ -115,7 +121,8 @@
     a.number = change.getId().toString();
     a.subject = change.getSubject();
     try {
-      a.commitMessage = new ChangeData(change).commitMessage(repoManager, db);
+      a.commitMessage =
+          changeDataFactory.create(db.get(), change).commitMessage();
     } catch (Exception e) {
       log.error("Error while getting full commit message for"
           + " change " + a.number);
@@ -158,6 +165,24 @@
   }
 
   /**
+   * Add allReviewers to an existing ChangeAttribute.
+   *
+   * @param a
+   * @param notes
+   */
+  public void addAllReviewers(ChangeAttribute a, ChangeNotes notes)
+      throws OrmException {
+    Collection<Account.Id> reviewers =
+        approvalsUtil.getReviewers(db.get(), notes).values();
+    if (!reviewers.isEmpty()) {
+      a.allReviewers = Lists.newArrayListWithCapacity(reviewers.size());
+      for (Account.Id id : reviewers) {
+        a.allReviewers.add(asAccountAttribute(id));
+      }
+    }
+  }
+
+  /**
    * Add submitRecords to an existing ChangeAttribute.
    *
    * @param ca
@@ -266,11 +291,16 @@
     return d;
   }
 
-  public void addTrackingIds(ChangeAttribute a, Collection<TrackingId> ids) {
-    if (!ids.isEmpty()) {
-      a.trackingIds = new ArrayList<TrackingIdAttribute>(ids.size());
-      for (TrackingId t : ids) {
-        a.trackingIds.add(asTrackingIdAttribute(t));
+  public void addTrackingIds(ChangeAttribute a, Multimap<String, String> set) {
+    if (!set.isEmpty()) {
+      a.trackingIds = new ArrayList<TrackingIdAttribute>(set.size());
+      for (Map.Entry<String, Collection<String>> e : set.asMap().entrySet()) {
+        for (String id : e.getValue()) {
+          TrackingIdAttribute t = new TrackingIdAttribute();
+          t.system = e.getKey();
+          t.id = id;
+          a.trackingIds.add(t);
+        }
       }
     }
   }
@@ -353,13 +383,6 @@
     }
   }
 
-  public TrackingIdAttribute asTrackingIdAttribute(TrackingId id) {
-    TrackingIdAttribute a = new TrackingIdAttribute();
-    a.system = id.getSystem();
-    a.id = id.getTrackingId();
-    return a;
-  }
-
   /**
    * Create a PatchSetAttribute for the given patchset suitable for
    * serialization to JSON.
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 6859262..7a64155 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
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.git;
 
-import static com.google.gerrit.server.git.GitRepositoryManager.REF_REJECT_COMMITS;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_REJECT_COMMITS;
 
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.reviewdb.client.Project;
@@ -96,7 +96,7 @@
         NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(project,
             repo, inserter);
         NoteMap newlyCreated =
-            notesBranchUtil.commitNewNotes(banCommitNotes, REF_REJECT_COMMITS,
+            notesBranchUtil.commitNewNotes(banCommitNotes, REFS_REJECT_COMMITS,
                 createPersonIdent(), buildCommitMessage(commitsToBan, reason));
 
         for (Note n : banCommitNotes) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
index 32dc303..eb4acfc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeCache.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gwtorm.server.SchemaFactory;
@@ -72,7 +73,7 @@
 
   @Override
   public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
-    if (event.getRefName().startsWith("refs/changes/")) {
+    if (event.getRefName().startsWith(RefNames.REFS_CHANGES)) {
       cache.invalidate(new Project.NameKey(event.getProjectName()));
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
index 311856d..b849089 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
@@ -16,6 +16,8 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.ChangeControl;
 
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ObjectId;
@@ -37,10 +39,10 @@
    * This value is only available on commits that have a PatchSet represented in
    * the code review system.
    */
-  PatchSet.Id patchsetId;
+  private PatchSet.Id patchsetId;
 
-  /** The change containing {@link #patchsetId} . */
-  Change change;
+  /** Change control for the change owner. */
+  private ChangeControl control;
 
   /**
    * Ordinal position of this commit within the submit queue.
@@ -54,7 +56,7 @@
    * <p>
    * Only valid if {@link #patchsetId} is not null.
    */
-  CommitMergeStatus statusCode;
+  private CommitMergeStatus statusCode;
 
   /** Commits which are missing ancestors of this commit. */
   List<CodeReviewCommit> missing;
@@ -63,11 +65,43 @@
     super(id);
   }
 
-  void copyFrom(final CodeReviewCommit src) {
+  public ChangeNotes notes() {
+    return getControl().getNotes();
+  }
+
+  public CommitMergeStatus getStatusCode() {
+    return statusCode;
+  }
+
+  public void setStatusCode(CommitMergeStatus statusCode) {
+    this.statusCode = statusCode;
+  }
+
+  public PatchSet.Id getPatchsetId() {
+    return patchsetId;
+  }
+
+  public void setPatchsetId(PatchSet.Id patchsetId) {
+    this.patchsetId = patchsetId;
+  }
+
+  public void copyFrom(final CodeReviewCommit src) {
+    control = src.control;
     patchsetId = src.patchsetId;
-    change = src.change;
     originalOrder = src.originalOrder;
     statusCode = src.statusCode;
     missing = src.missing;
   }
+
+  public Change change() {
+    return getControl().getChange();
+  }
+
+  public ChangeControl getControl() {
+    return control;
+  }
+
+  public void setControl(ChangeControl control) {
+    this.control = control;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
index 8595472..4c3e5f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CommitMergeStatus.java
@@ -68,6 +68,16 @@
   INVALID_PROJECT_CONFIGURATION("Change contains an invalid project configuration."),
 
   /** */
+  INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_PERMITTED(
+      "Change contains an invalid project configuration:\n"
+          + "One of the plugin configuration parameters has a value that is not permitted."),
+
+  /** */
+  INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_EDITABLE(
+      "Change contains an invalid project configuration:\n"
+          + "One of the plugin configuration parameters is not editable."),
+
+  /** */
   INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND(
       "Change contains an invalid project configuration:\n"
           + "Parent project does not exist."),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
index 0118404..ffb91ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -31,27 +31,6 @@
  * environment.
  */
 public interface GitRepositoryManager {
-  /** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
-  public static final String REF_REJECT_COMMITS = "refs/meta/reject-commits";
-
-  /** Configuration settings for a project {@code refs/meta/config} */
-  public static final String REF_CONFIG = "refs/meta/config";
-
-  /** Configurations of project-specific dashboards (canned search queries). */
-  public static String REFS_DASHBOARDS = "refs/meta/dashboards/";
-
-  /**
-   * Prefix applied to merge commit base nodes.
-   * <p>
-   * References in this directory should take the form
-   * {@code refs/cache-automerge/xx/yyyy...} where xx is
-   * the first two digits of the merge commit's object
-   * name, and yyyyy... is the remaining 38. The reference
-   * should point to a treeish that is the automatic merge
-   * result of the merge commit's parents.
-   */
-  public static final String REFS_CACHE_AUTOMERGE = "refs/cache-automerge/";
-
   /**
    * Get (or open) a repository by name.
    *
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
index 3f09916..19b5ec9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
@@ -16,6 +16,10 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
@@ -33,7 +37,73 @@
 import java.util.Collection;
 import java.util.List;
 
+/**
+ * Normalizes votes on labels according to project config and permissions.
+ * <p>
+ * Votes are recorded in the database for a user based on the state of the
+ * project at that time: what labels are defined for the project, and what the
+ * user is allowed to vote on. Both of those can change between the time a vote
+ * is originally made and a later point, for example when a change is submitted.
+ * This class normalizes old votes against current project configuration.
+ */
 public class LabelNormalizer {
+  public static class Result {
+    private final ImmutableList<PatchSetApproval> unchanged;
+    private final ImmutableList<PatchSetApproval> updated;
+    private final ImmutableList<PatchSetApproval> deleted;
+
+    @VisibleForTesting
+    Result(
+        List<PatchSetApproval> unchanged,
+        List<PatchSetApproval> updated,
+        List<PatchSetApproval> deleted) {
+      this.unchanged = ImmutableList.copyOf(unchanged);
+      this.updated = ImmutableList.copyOf(updated);
+      this.deleted = ImmutableList.copyOf(deleted);
+    }
+
+    public ImmutableList<PatchSetApproval> getUnchanged() {
+      return unchanged;
+    }
+
+    public ImmutableList<PatchSetApproval> getUpdated() {
+      return updated;
+    }
+
+    public ImmutableList<PatchSetApproval> getDeleted() {
+      return deleted;
+    }
+
+    public Iterable<PatchSetApproval> getNormalized() {
+      return Iterables.concat(unchanged, updated);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof Result) {
+        Result r = (Result) o;
+        return Objects.equal(unchanged, r.unchanged)
+            && Objects.equal(updated, r.updated)
+            && Objects.equal(deleted, r.deleted);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(unchanged, updated, deleted);
+    }
+
+    @Override
+    public String toString() {
+      return Objects.toStringHelper(this)
+          .add("unchanged", unchanged)
+          .add("updated", updated)
+          .add("deleted", deleted)
+          .toString();
+    }
+  }
+
   private final ChangeControl.GenericFactory changeFactory;
   private final IdentifiedUser.GenericFactory userFactory;
 
@@ -53,7 +123,7 @@
    *     permissions for that label.
    * @throws NoSuchChangeException
    */
-  public List<PatchSetApproval> normalize(Change change,
+  public Result normalize(Change change,
       Collection<PatchSetApproval> approvals) throws NoSuchChangeException {
     return normalize(
         changeFactory.controlFor(change, userFactory.create(change.getOwner())),
@@ -68,9 +138,13 @@
    *     included in the output, nor are approvals where the user has no
    *     permissions for that label.
    */
-  public List<PatchSetApproval> normalize(ChangeControl ctl,
+  public Result normalize(ChangeControl ctl,
       Collection<PatchSetApproval> approvals) {
-    List<PatchSetApproval> result =
+    List<PatchSetApproval> unchanged =
+        Lists.newArrayListWithCapacity(approvals.size());
+    List<PatchSetApproval> updated =
+        Lists.newArrayListWithCapacity(approvals.size());
+    List<PatchSetApproval> deleted =
         Lists.newArrayListWithCapacity(approvals.size());
     LabelTypes labelTypes = ctl.getLabelTypes();
     for (PatchSetApproval psa : approvals) {
@@ -79,19 +153,25 @@
           "Approval %s does not match change %s",
           psa.getKey(), ctl.getChange().getKey());
       if (psa.isSubmit()) {
-        result.add(copy(psa, ctl));
+        unchanged.add(psa);
         continue;
       }
       LabelType label = labelTypes.byLabel(psa.getLabelId());
-      if (label != null) {
-        psa = copy(psa, ctl);
-        applyTypeFloor(label, psa);
-        if (applyRightFloor(ctl, label, psa)) {
-          result.add(psa);
-        }
+      if (label == null) {
+        deleted.add(psa);
+        continue;
+      }
+      PatchSetApproval copy = copy(psa);
+      applyTypeFloor(label, copy);
+      if (!applyRightFloor(ctl, label, copy)) {
+        deleted.add(psa);
+      } else if (copy.getValue() != psa.getValue()) {
+        updated.add(copy);
+      } else {
+        unchanged.add(psa);
       }
     }
-    return result;
+    return new Result(unchanged, updated, deleted);
   }
 
   /**
@@ -105,10 +185,8 @@
     return !getRange(ctl, lt, id).isEmpty();
   }
 
-  private PatchSetApproval copy(PatchSetApproval src, ChangeControl ctl) {
-    PatchSetApproval dest = new PatchSetApproval(src.getPatchSetId(), src);
-    dest.cache(ctl.getChange());
-    return dest;
+  private PatchSetApproval copy(PatchSetApproval src) {
+    return new PatchSetApproval(src.getPatchSetId(), src);
   }
 
   private PermissionRange getRange(ChangeControl ctl, LabelType lt,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index c51a6ec..43e0156 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
@@ -62,17 +61,11 @@
   private static final String UNNAMED =
       "Unnamed repository; edit this file to name it for gitweb.";
 
-  public static class Module extends AbstractModule {
+  public static class Module extends LifecycleModule {
     @Override
     protected void configure() {
       bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
-
-      install(new LifecycleModule() {
-        @Override
-        protected void configure() {
-          listener().to(LocalDiskRepositoryManager.Lifecycle.class);
-        }
-      });
+      listener().to(LocalDiskRepositoryManager.Lifecycle.class);
     }
   }
 
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 7669b77..0919aa0 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
@@ -14,13 +14,10 @@
 
 package com.google.gerrit.server.git;
 
-import static com.google.gerrit.server.git.MergeUtil.getSubmitter;
-
 import static java.util.concurrent.TimeUnit.HOURS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 import static java.util.concurrent.TimeUnit.SECONDS;
-
 import static org.eclipse.jgit.lib.RefDatabase.ALL;
 
 import com.google.common.base.Objects;
@@ -28,7 +25,6 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.Nullable;
@@ -42,6 +38,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
@@ -49,11 +46,14 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.strategy.SubmitStrategy;
+import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
 import com.google.gerrit.server.git.validators.MergeValidationException;
 import com.google.gerrit.server.git.validators.MergeValidators;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.MergeFailSender;
 import com.google.gerrit.server.mail.MergedSender;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
@@ -129,8 +129,8 @@
 
   private final GitRepositoryManager repoManager;
   private final SchemaFactory<ReviewDb> schemaFactory;
+  private final ChangeNotes.Factory notesFactory;
   private final ProjectCache projectCache;
-  private final LabelNormalizer labelNormalizer;
   private final GitReferenceUpdated gitRefUpdated;
   private final MergedSender.Factory mergedSenderFactory;
   private final MergeFailSender.Factory mergeFailSenderFactory;
@@ -139,6 +139,7 @@
   private final ChangeControl.GenericFactory changeControlFactory;
   private final MergeQueue mergeQueue;
   private final MergeValidators.Factory mergeValidatorsFactory;
+  private final ApprovalsUtil approvalsUtil;
 
   private final Branch.NameKey destBranch;
   private ProjectState destProject;
@@ -166,7 +167,8 @@
 
   @Inject
   MergeOp(final GitRepositoryManager grm, final SchemaFactory<ReviewDb> sf,
-      final ProjectCache pc, final LabelNormalizer fs,
+      final ChangeNotes.Factory nf,
+      final ProjectCache pc,
       final GitReferenceUpdated gru, final MergedSender.Factory msf,
       final MergeFailSender.Factory mfsf,
       final PatchSetInfoFactory psif, final IdentifiedUser.GenericFactory iuf,
@@ -179,10 +181,11 @@
       final WorkQueue workQueue,
       final RequestScopePropagator requestScopePropagator,
       final ChangeIndexer indexer,
-      final MergeValidators.Factory mergeValidatorsFactory) {
+      final MergeValidators.Factory mergeValidatorsFactory,
+      final ApprovalsUtil approvalsUtil) {
     repoManager = grm;
     schemaFactory = sf;
-    labelNormalizer = fs;
+    notesFactory = nf;
     projectCache = pc;
     gitRefUpdated = gru;
     mergedSenderFactory = msf;
@@ -200,6 +203,7 @@
     this.requestScopePropagator = requestScopePropagator;
     this.indexer = indexer;
     this.mergeValidatorsFactory = mergeValidatorsFactory;
+    this.approvalsUtil = approvalsUtil;
     destBranch = branch;
     toMerge = ArrayListMultimap.create();
     potentiallyStillSubmittable = new ArrayList<CodeReviewCommit>();
@@ -245,11 +249,14 @@
           }
           final SubmitStrategy strategy = createStrategy(submitType);
           preMerge(strategy, toMerge.get(submitType));
-          updateBranch(strategy, branchUpdate);
+          RefUpdate update = updateBranch(strategy, branchUpdate);
           reopen = true;
 
           updateChangeStatus(toSubmit.get(submitType));
           updateSubscriptions(toSubmit.get(submitType));
+          if (update != null) {
+            fireRefUpdated(update);
+          }
 
           for (final Iterator<CodeReviewCommit> it =
               potentiallyStillSubmittable.iterator(); it.hasNext();) {
@@ -260,7 +267,7 @@
               // missing are still attempted to be merged with another submit
               // strategy, retry to merge this commit in the next turn
               it.remove();
-              commit.statusCode = null;
+              commit.setStatusCode(null);
               commit.missing = null;
               toMergeNextTurn.put(submitType, commit);
             }
@@ -277,8 +284,8 @@
       for (final CodeReviewCommit commit : potentiallyStillSubmittableOnNextRun) {
         final Capable capable = isSubmitStillPossible(commit);
         if (capable != Capable.OK) {
-          sendMergeFail(commit.change,
-              message(commit.change, capable.getMessage()), false);
+          sendMergeFail(commit.notes(),
+              message(commit.change(), capable.getMessage()), false);
         }
       }
     } catch (NoSuchProjectException noProject) {
@@ -325,17 +332,22 @@
     }
 
     for (CodeReviewCommit missingCommit : commit.missing) {
-      loadChangeInfo(missingCommit);
+      try {
+        loadChangeInfo(missingCommit);
+      } catch (NoSuchChangeException | OrmException e) {
+        log.error("Cannot check if missing commits can be submitted", e);
+        return false;
+      }
 
-      if (missingCommit.patchsetId == null) {
+      if (missingCommit.getPatchsetId() == null) {
         // The commit doesn't have a patch set, so it cannot be
         // submitted to the branch.
         //
         return false;
       }
 
-      if (!missingCommit.change.currentPatchSetId().equals(
-          missingCommit.patchsetId)) {
+      if (!missingCommit.change().currentPatchSetId().equals(
+          missingCommit.getPatchsetId())) {
         // If the missing commit is not the current patch set,
         // the change must be rebased to use the proper parent.
         //
@@ -517,8 +529,13 @@
         continue;
       }
 
-      commit.change = chg;
-      commit.patchsetId = ps.getId();
+      try {
+        commit.setControl(changeControlFactory.controlFor(chg,
+            identifiedUserFactory.create(chg.getOwner())));
+      } catch (NoSuchChangeException e) {
+        throw new MergeException("Failed to validate changes", e);
+      }
+      commit.setPatchsetId(ps.getId());
       commit.originalOrder = commitOrder++;
       commits.put(changeId, commit);
 
@@ -529,7 +546,7 @@
         //
         try {
           if (rw.isMergedInto(commit, branchTip)) {
-            commit.statusCode = CommitMergeStatus.ALREADY_MERGED;
+            commit.setStatusCode(CommitMergeStatus.ALREADY_MERGED);
             try {
               setMerged(chg, null);
             } catch (OrmException e) {
@@ -542,7 +559,7 @@
         }
       }
 
-      final SubmitType submitType = getSubmitType(chg, ps);
+      final SubmitType submitType = getSubmitType(commit.getControl(), ps);
       if (submitType == null) {
         commits.put(changeId,
             CodeReviewCommit.error(CommitMergeStatus.NO_SUBMIT_TYPE));
@@ -557,99 +574,93 @@
     return toSubmit;
   }
 
-  private SubmitType getSubmitType(final Change change, final PatchSet ps) {
-    try {
-      final SubmitTypeRecord r =
-          changeControlFactory.controlFor(change,
-              identifiedUserFactory.create(change.getOwner()))
-              .getSubmitTypeRecord(db, ps);
-      if (r.status != SubmitTypeRecord.Status.OK) {
-        log.error("Failed to get submit type for " + change.getKey());
-        return null;
-      }
-      return r.type;
-    } catch (NoSuchChangeException e) {
-      log.error("Failed to get submit type for " + change.getKey(), e);
+  private SubmitType getSubmitType(ChangeControl ctl, PatchSet ps) {
+    SubmitTypeRecord r = ctl.getSubmitTypeRecord(db, ps);
+    if (r.status != SubmitTypeRecord.Status.OK) {
+      log.error("Failed to get submit type for " + ctl.getChange().getKey());
       return null;
     }
+    return r.type;
+  }
+
+  private RefUpdate updateBranch(final SubmitStrategy strategy,
+      final RefUpdate branchUpdate) throws MergeException {
+    if (branchTip == mergeTip || mergeTip == null) {
+      // nothing to do
+      return null;
+    }
+
+    if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
+      try {
+        ProjectConfig cfg =
+            new ProjectConfig(destProject.getProject().getNameKey());
+        cfg.load(repo, mergeTip);
+      } catch (Exception e) {
+        throw new MergeException("Submit would store invalid"
+            + " project configuration " + mergeTip.name() + " for "
+            + destProject.getProject().getName(), e);
+      }
+    }
+
+    branchUpdate.setRefLogIdent(refLogIdent);
+    branchUpdate.setForceUpdate(false);
+    branchUpdate.setNewObjectId(mergeTip);
+    branchUpdate.setRefLogMessage("merged", true);
+    try {
+      switch (branchUpdate.update(rw)) {
+        case NEW:
+        case FAST_FORWARD:
+          if (branchUpdate.getResult() == RefUpdate.Result.FAST_FORWARD) {
+            tagCache.updateFastForward(destBranch.getParentKey(),
+                branchUpdate.getName(),
+                branchUpdate.getOldObjectId(),
+                mergeTip);
+          }
+
+          if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
+            projectCache.evict(destProject.getProject());
+            destProject = projectCache.get(destProject.getProject().getNameKey());
+            repoManager.setProjectDescription(
+                destProject.getProject().getNameKey(),
+                destProject.getProject().getDescription());
+          }
+
+          return branchUpdate;
+
+        case LOCK_FAILURE:
+          String msg;
+          if (strategy.retryOnLockFailure()) {
+            mergeQueue.recheckAfter(destBranch, LOCK_FAILURE_RETRY_DELAY,
+                MILLISECONDS);
+            msg = "will retry";
+          } else {
+            msg = "will not retry";
+          }
+          throw new IOException(branchUpdate.getResult().name() + ", " + msg);
+        default:
+          throw new IOException(branchUpdate.getResult().name());
+      }
+    } catch (IOException e) {
+      throw new MergeException("Cannot update " + branchUpdate.getName(), e);
+    }
   }
 
-  private void updateBranch(final SubmitStrategy strategy,
-      final RefUpdate branchUpdate) throws MergeException {
-    if ((branchTip == null && mergeTip == null) || branchTip == mergeTip) {
-      // nothing to do
-      return;
+  private void fireRefUpdated(RefUpdate branchUpdate) {
+    gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate);
+
+    Account account = null;
+    PatchSetApproval submitter = approvalsUtil.getSubmitter(
+        db, mergeTip.notes(), mergeTip.getPatchsetId());
+    if (submitter != null) {
+      account = accountCache.get(submitter.getAccountId()).getAccount();
     }
-
-    if (mergeTip != null && (branchTip == null || branchTip != mergeTip)) {
-      if (GitRepositoryManager.REF_CONFIG.equals(branchUpdate.getName())) {
-        try {
-          ProjectConfig cfg =
-              new ProjectConfig(destProject.getProject().getNameKey());
-          cfg.load(repo, mergeTip);
-        } catch (Exception e) {
-          throw new MergeException("Submit would store invalid"
-              + " project configuration " + mergeTip.name() + " for "
-              + destProject.getProject().getName(), e);
-        }
-      }
-
-      branchUpdate.setRefLogIdent(refLogIdent);
-      branchUpdate.setForceUpdate(false);
-      branchUpdate.setNewObjectId(mergeTip);
-      branchUpdate.setRefLogMessage("merged", true);
-      try {
-        switch (branchUpdate.update(rw)) {
-          case NEW:
-          case FAST_FORWARD:
-            if (branchUpdate.getResult() == RefUpdate.Result.FAST_FORWARD) {
-              tagCache.updateFastForward(destBranch.getParentKey(),
-                  branchUpdate.getName(),
-                  branchUpdate.getOldObjectId(),
-                  mergeTip);
-            }
-
-            if (GitRepositoryManager.REF_CONFIG.equals(branchUpdate.getName())) {
-              projectCache.evict(destProject.getProject());
-              destProject = projectCache.get(destProject.getProject().getNameKey());
-              repoManager.setProjectDescription(
-                  destProject.getProject().getNameKey(),
-                  destProject.getProject().getDescription());
-            }
-
-            gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate);
-
-            Account account = null;
-            final PatchSetApproval submitter = getSubmitter(db, mergeTip.patchsetId);
-            if (submitter != null) {
-              account = accountCache.get(submitter.getAccountId()).getAccount();
-            }
-            hooks.doRefUpdatedHook(destBranch, branchUpdate, account);
-            break;
-
-          case LOCK_FAILURE:
-            String msg;
-            if (strategy.retryOnLockFailure()) {
-              mergeQueue.recheckAfter(destBranch, LOCK_FAILURE_RETRY_DELAY,
-                  MILLISECONDS);
-              msg = "will retry";
-            } else {
-              msg = "will not retry";
-            }
-            throw new IOException(branchUpdate.getResult().name() + ", " + msg);
-          default:
-            throw new IOException(branchUpdate.getResult().name());
-        }
-      } catch (IOException e) {
-        throw new MergeException("Cannot update " + branchUpdate.getName(), e);
-      }
-    }
+    hooks.doRefUpdatedHook(destBranch, branchUpdate, account);
   }
 
   private void updateChangeStatus(final List<Change> submitted) {
     for (final Change c : submitted) {
       final CodeReviewCommit commit = commits.get(c.getId());
-      final CommitMergeStatus s = commit != null ? commit.statusCode : null;
+      final CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
       if (s == null) {
         // Shouldn't ever happen, but leave the change alone. We'll pick
         // it up on the next pass.
@@ -679,10 +690,12 @@
           case CANNOT_CHERRY_PICK_ROOT:
           case NOT_FAST_FORWARD:
           case INVALID_PROJECT_CONFIGURATION:
+          case INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_PERMITTED:
+          case INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_EDITABLE:
           case INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND:
           case INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT:
           case SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN:
-            setNew(c, message(c, txt));
+            setNew(commit, message(c, txt));
             break;
 
           case MISSING_DEPENDENCY:
@@ -690,7 +703,7 @@
             break;
 
           default:
-            setNew(c, message(c, "Unspecified merge failure: " + s.name()));
+            setNew(commit, message(c, "Unspecified merge failure: " + s.name()));
             break;
         }
       } catch (OrmException err) {
@@ -718,7 +731,7 @@
 
   private Capable isSubmitStillPossible(final CodeReviewCommit commit) {
     final Capable capable;
-    final Change c = commit.change;
+    final Change c = commit.change();
     final boolean submitStillPossible = isSubmitForMissingCommitsStillPossible(commit);
     final long now = TimeUtil.nowMs();
     final long waitUntil = c.getLastUpdatedOn().getTime() + DEPENDENCY_DELAY;
@@ -743,7 +756,7 @@
       m.append("\n");
       for (CodeReviewCommit missingCommit : commit.missing) {
         m.append("* ");
-        m.append(missingCommit.change.getKey().get());
+        m.append(missingCommit.change().getKey().get());
         m.append("\n");
       }
       capable = new Capable(m.toString());
@@ -753,20 +766,19 @@
       // dependencies.
       //
       StringBuilder m = new StringBuilder();
-      m.append("Change cannot be merged due"
-          + " to unsatisfiable dependencies.\n");
+      m.append("Change cannot be merged due to unsatisfiable dependencies.\n");
       m.append("\n");
       m.append("The following dependency errors were found:\n");
       m.append("\n");
       for (CodeReviewCommit missingCommit : commit.missing) {
-        if (missingCommit.patchsetId != null) {
+        if (missingCommit.getPatchsetId() != null) {
           m.append("* Depends on patch set ");
-          m.append(missingCommit.patchsetId.get());
+          m.append(missingCommit.getPatchsetId().get());
           m.append(" of ");
-          m.append(missingCommit.change.getKey().abbreviate());
-          if (missingCommit.patchsetId.get() != missingCommit.change.currentPatchSetId().get()) {
+          m.append(missingCommit.change().getKey().abbreviate());
+          if (missingCommit.getPatchsetId().get() != missingCommit.change().currentPatchSetId().get()) {
             m.append(", however the current patch set is ");
-            m.append(missingCommit.change.currentPatchSetId().get());
+            m.append(missingCommit.change().currentPatchSetId().get());
           }
           m.append(".\n");
 
@@ -784,17 +796,15 @@
     return capable;
   }
 
-  private void loadChangeInfo(final CodeReviewCommit commit) {
-    if (commit.patchsetId == null) {
-      try {
-        List<PatchSet> matches =
-            db.patchSets().byRevision(new RevId(commit.name())).toList();
-        if (matches.size() == 1) {
-          final PatchSet ps = matches.get(0);
-          commit.patchsetId = ps.getId();
-          commit.change = db.changes().get(ps.getId().getParentKey());
-        }
-      } catch (OrmException e) {
+  private void loadChangeInfo(final CodeReviewCommit commit)
+      throws NoSuchChangeException, OrmException {
+    if (commit.getControl() == null) {
+      List<PatchSet> matches =
+          db.patchSets().byRevision(new RevId(commit.name())).toList();
+      if (matches.size() == 1) {
+        PatchSet ps = matches.get(0);
+        commit.setPatchsetId(ps.getId());
+        commit.setControl(changeControl(db.changes().get(ps.getId().getParentKey())));
       }
     }
   }
@@ -812,7 +822,7 @@
     return m;
   }
 
-  private void setMerged(final Change c, final ChangeMessage msg)
+  private void setMerged(Change c, ChangeMessage msg)
       throws OrmException, IOException {
     try {
       db.changes().beginTransaction(c.getId());
@@ -820,9 +830,10 @@
       // We must pull the patchset out of commits, because the patchset ID is
       // modified when using the cherry-pick merge strategy.
       CodeReviewCommit commit = commits.get(c.getId());
-      PatchSet.Id merged = commit.change.currentPatchSetId();
-      setMergedPatchSet(c.getId(), merged);
-      PatchSetApproval submitter = saveApprovals(c, merged);
+      PatchSet.Id merged = commit.change().currentPatchSetId();
+      c = setMergedPatchSet(c.getId(), merged);
+      PatchSetApproval submitter =
+          approvalsUtil.getSubmitter(db, commit.notes(), merged);
       addMergedMessage(submitter, msg);
 
       db.commit();
@@ -840,12 +851,12 @@
     } finally {
       db.rollback();
     }
-    indexer.index(c);
+    indexer.index(db, c);
   }
 
-  private void setMergedPatchSet(Change.Id changeId, final PatchSet.Id merged)
+  private Change setMergedPatchSet(Change.Id changeId, final PatchSet.Id merged)
       throws OrmException {
-    db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
+    return db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
       @Override
       public Change update(Change c) {
         c.setStatus(Change.Status.MERGED);
@@ -869,46 +880,6 @@
     });
   }
 
-  private PatchSetApproval saveApprovals(Change c, PatchSet.Id merged)
-      throws OrmException {
-    // Flatten out existing approvals for this patch set based upon the current
-    // permissions. Once the change is closed the approvals are not updated at
-    // presentation view time, except for zero votes used to indicate a reviewer
-    // was added. So we need to make sure votes are accurate now. This way if
-    // permissions get modified in the future, historical records stay accurate.
-    PatchSetApproval submitter = null;
-    try {
-      c.setStatus(Change.Status.MERGED);
-
-      List<PatchSetApproval> approvals =
-          db.patchSetApprovals().byPatchSet(merged).toList();
-      Set<PatchSetApproval.Key> toDelete =
-          Sets.newHashSetWithExpectedSize(approvals.size());
-      for (PatchSetApproval a : approvals) {
-        if (a.getValue() != 0) {
-          toDelete.add(a.getKey());
-        }
-      }
-
-      approvals = labelNormalizer.normalize(c, approvals);
-      for (PatchSetApproval a : approvals) {
-        toDelete.remove(a.getKey());
-        if (a.getValue() > 0 && a.isSubmit()) {
-          if (submitter == null
-              || a.getGranted().compareTo(submitter.getGranted()) > 0) {
-            submitter = a;
-          }
-        }
-        a.cache(c);
-      }
-      db.patchSetApprovals().update(approvals);
-      db.patchSetApprovals().deleteKeys(toDelete);
-    } catch (NoSuchChangeException err) {
-      throw new OrmException(err);
-    }
-    return submitter;
-  }
-
   private void addMergedMessage(PatchSetApproval submitter, ChangeMessage msg)
       throws OrmException {
     if (msg != null) {
@@ -938,9 +909,7 @@
         }
 
         try {
-          final ChangeControl control = changeControlFactory.controlFor(c,
-              identifiedUserFactory.create(c.getOwner()));
-          final MergedSender cm = mergedSenderFactory.create(control);
+          MergedSender cm = mergedSenderFactory.create(changeControl(c));
           if (from != null) {
             cm.setFrom(from.getAccountId());
           }
@@ -958,12 +927,21 @@
     }));
   }
 
-  private void setNew(Change c, ChangeMessage msg) {
-    sendMergeFail(c, msg, true);
+  private ChangeControl changeControl(Change c) throws NoSuchChangeException {
+    return changeControlFactory.controlFor(
+        c, identifiedUserFactory.create(c.getOwner()));
+  }
+
+  private void setNew(CodeReviewCommit c, ChangeMessage msg) {
+    sendMergeFail(c.notes(), msg, true);
+  }
+
+  private void setNew(Change c, ChangeMessage msg) throws OrmException {
+    sendMergeFail(notesFactory.create(c), msg, true);
   }
 
   private enum RetryStatus {
-    UNSUBMIT, RETRY_NO_MESSAGE, RETRY_ADD_MESSAGE;
+    UNSUBMIT, RETRY_NO_MESSAGE, RETRY_ADD_MESSAGE
   }
 
   private RetryStatus getRetryStatus(
@@ -995,11 +973,12 @@
     }
   }
 
-  private void sendMergeFail(final Change c, final ChangeMessage msg,
+  private void sendMergeFail(ChangeNotes notes, final ChangeMessage msg,
       boolean makeNew) {
     PatchSetApproval submitter = null;
     try {
-      submitter = getSubmitter(db, c.currentPatchSetId());
+      submitter = approvalsUtil.getSubmitter(
+          db, notes, notes.getChange().currentPatchSetId());
     } catch (Exception e) {
       log.error("Cannot get submitter", e);
     }
@@ -1014,6 +993,7 @@
     }
 
     final boolean setStatusNew = makeNew;
+    final Change c = notes.getChange();
     Change change = null;
     try {
       db.changes().beginTransaction(c.getId());
@@ -1043,7 +1023,7 @@
 
     CheckedFuture<?, IOException> indexFuture;
     if (change != null) {
-      indexFuture = indexer.indexAsync(change);
+      indexFuture = indexer.indexAsync(change.getId());
     } else {
       indexFuture = null;
     }
@@ -1149,9 +1129,8 @@
             change.currentPatchSetId());
         msg.setMessage("Project was deleted.");
         db.changeMessages().insert(Collections.singleton(msg));
-        new ApprovalsUtil(db).syncChangeStatus(change);
         db.commit();
-        indexer.index(change);
+        indexer.index(db, change);
       }
     } finally {
       db.rollback();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
index 3911d96..9b4582d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSorter.java
@@ -26,12 +26,12 @@
 import java.util.Iterator;
 import java.util.Set;
 
-class MergeSorter {
+public class MergeSorter {
   private final RevWalk rw;
   private final RevFlag canMergeFlag;
   private final Set<RevCommit> accepted;
 
-  MergeSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
+  public MergeSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
       final RevFlag canMergeFlag) {
     this.rw = rw;
     this.canMergeFlag = canMergeFlag;
@@ -59,7 +59,7 @@
           // aren't permitted to merge at this time. Drop n.
           //
           if (n.missing == null) {
-            n.statusCode = CommitMergeStatus.MISSING_DEPENDENCY;
+            n.setStatusCode(CommitMergeStatus.MISSING_DEPENDENCY);
             n.missing = new ArrayList<CodeReviewCommit>();
           }
           n.missing.add((CodeReviewCommit) c);
@@ -68,7 +68,7 @@
         }
       }
 
-      if (n.statusCode == CommitMergeStatus.MISSING_DEPENDENCY) {
+      if (n.getStatusCode() == CommitMergeStatus.MISSING_DEPENDENCY) {
         continue;
       }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index 9a407e1..b9624fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -14,14 +14,20 @@
 
 package com.google.gerrit.server.git;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -44,6 +50,7 @@
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.Merger;
 import org.eclipse.jgit.merge.ThreeWayMerger;
 import org.eclipse.jgit.revwalk.FooterKey;
 import org.eclipse.jgit.revwalk.FooterLine;
@@ -65,6 +72,7 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.TimeZone;
@@ -72,6 +80,10 @@
 public class MergeUtil {
   private static final Logger log = LoggerFactory.getLogger(MergeUtil.class);
 
+  public static boolean useRecursiveMerge(Config cfg) {
+    return cfg.getBoolean("core", null, "useRecursiveMerge", false);
+  }
+
   public static interface Factory {
     MergeUtil create(ProjectState project);
     MergeUtil create(ProjectState project, boolean useContentMerge);
@@ -86,6 +98,7 @@
   private final Provider<ReviewDb> db;
   private final IdentifiedUser.GenericFactory identifiedUserFactory;
   private final Provider<String> urlProvider;
+  private final ApprovalsUtil approvalsUtil;
   private final ProjectState project;
   private final boolean useContentMerge;
   private final boolean useRecursiveMerge;
@@ -95,9 +108,10 @@
       final Provider<ReviewDb> db,
       final IdentifiedUser.GenericFactory identifiedUserFactory,
       @CanonicalWebUrl @Nullable final Provider<String> urlProvider,
+      final ApprovalsUtil approvalsUtil,
       @Assisted final ProjectState project) {
-    this(serverConfig, db, identifiedUserFactory, urlProvider, project,
-        project.isUseContentMerge());
+    this(serverConfig, db, identifiedUserFactory, urlProvider, approvalsUtil,
+        project, project.isUseContentMerge());
   }
 
   @AssistedInject
@@ -105,15 +119,16 @@
       final Provider<ReviewDb> db,
       final IdentifiedUser.GenericFactory identifiedUserFactory,
       @CanonicalWebUrl @Nullable final Provider<String> urlProvider,
+      final ApprovalsUtil approvalsUtil,
       @Assisted final ProjectState project,
       @Assisted boolean useContentMerge) {
     this.db = db;
     this.identifiedUserFactory = identifiedUserFactory;
     this.urlProvider = urlProvider;
+    this.approvalsUtil = approvalsUtil;
     this.project = project;
     this.useContentMerge = useContentMerge;
-    this.useRecursiveMerge =
-        serverConfig.getBoolean("core", null, "useRecursiveMerge", false);
+    this.useRecursiveMerge = useRecursiveMerge(serverConfig);
   }
 
   public CodeReviewCommit getFirstFastForward(
@@ -152,30 +167,8 @@
     });
   }
 
-  public PatchSetApproval getSubmitter(final PatchSet.Id c) {
-    return getSubmitter(db.get(), c);
-  }
-
-  public static PatchSetApproval getSubmitter(final ReviewDb reviewDb,
-      final PatchSet.Id c) {
-    if (c == null) {
-      return null;
-    }
-    PatchSetApproval submitter = null;
-    try {
-      final List<PatchSetApproval> approvals =
-          reviewDb.patchSetApprovals().byPatchSet(c).toList();
-      for (PatchSetApproval a : approvals) {
-        if (a.getValue() > 0 && a.isSubmit()) {
-          if (submitter == null
-              || a.getGranted().compareTo(submitter.getGranted()) > 0) {
-            submitter = a;
-          }
-        }
-      }
-    } catch (OrmException e) {
-    }
-    return submitter;
+  public PatchSetApproval getSubmitter(CodeReviewCommit c) {
+    return approvalsUtil.getSubmitter(db.get(), c.notes(), c.getPatchsetId());
   }
 
   public RevCommit createCherryPickFromCommit(Repository repo,
@@ -224,16 +217,16 @@
       msgbuf.append('\n');
     }
 
-    if (!contains(footers, CHANGE_ID, n.change.getKey().get())) {
+    if (!contains(footers, CHANGE_ID, n.change().getKey().get())) {
       msgbuf.append(CHANGE_ID.getName());
       msgbuf.append(": ");
-      msgbuf.append(n.change.getKey().get());
+      msgbuf.append(n.change().getKey().get());
       msgbuf.append('\n');
     }
 
     final String siteUrl = urlProvider.get();
     if (siteUrl != null) {
-      final String url = siteUrl + n.patchsetId.getParentKey().get();
+      final String url = siteUrl + n.getPatchsetId().getParentKey().get();
       if (!contains(footers, REVIEWED_ON, url)) {
         msgbuf.append(REVIEWED_ON.getName());
         msgbuf.append(": ");
@@ -244,7 +237,7 @@
 
     PatchSetApproval submitAudit = null;
 
-    for (final PatchSetApproval a : getApprovalsForCommit(n)) {
+    for (final PatchSetApproval a : safeGetApprovals(n)) {
       if (a.getValue() <= 0) {
         // Negative votes aren't counted.
         continue;
@@ -318,19 +311,11 @@
     return "Verified".equalsIgnoreCase(id.get());
   }
 
-  public List<PatchSetApproval> getApprovalsForCommit(final CodeReviewCommit n) {
+  private List<PatchSetApproval> safeGetApprovals(CodeReviewCommit n) {
     try {
-      List<PatchSetApproval> approvalList =
-          db.get().patchSetApprovals().byPatchSet(n.patchsetId).toList();
-      Collections.sort(approvalList, new Comparator<PatchSetApproval>() {
-        @Override
-        public int compare(final PatchSetApproval a, final PatchSetApproval b) {
-          return a.getGranted().compareTo(b.getGranted());
-        }
-      });
-      return approvalList;
+      return approvalsUtil.byPatchSet(db.get(), n.notes(), n.getPatchsetId());
     } catch (OrmException e) {
-      log.error("Can't read approval records for " + n.patchsetId, e);
+      log.error("Can't read approval records for " + n.getPatchsetId(), e);
       return Collections.emptyList();
     }
   }
@@ -358,7 +343,7 @@
       final RevWalk rw, final List<CodeReviewCommit> codeReviewCommits) {
     PatchSetApproval submitter = null;
     for (final CodeReviewCommit c : codeReviewCommits) {
-      PatchSetApproval s = getSubmitter(c.patchsetId);
+      PatchSetApproval s = getSubmitter(c);
       if (submitter == null
           || (s != null && s.getGranted().compareTo(submitter.getGranted()) > 0)) {
         submitter = s;
@@ -483,7 +468,7 @@
     }
   }
 
-  public ObjectInserter createDryRunInserter() {
+  public static ObjectInserter createDryRunInserter() {
     return new ObjectInserter() {
       @Override
       public ObjectId insert(int objectType, long length, InputStream in)
@@ -555,7 +540,7 @@
     rw.markUninteresting(mergeTip);
     CodeReviewCommit failed;
     while ((failed = (CodeReviewCommit) rw.next()) != null) {
-      failed.statusCode = failure;
+      failed.setStatusCode(failure);
     }
     return failed;
   }
@@ -572,29 +557,12 @@
     rw.markUninteresting(mergeTip);
     for (final RevCommit c : rw) {
       final CodeReviewCommit crc = (CodeReviewCommit) c;
-      if (crc.patchsetId != null) {
+      if (crc.getPatchsetId() != null) {
         merged.add(crc);
       }
     }
 
-    final StringBuilder msgbuf = new StringBuilder();
-    if (merged.size() == 1) {
-      final CodeReviewCommit c = merged.get(0);
-      rw.parseBody(c);
-      msgbuf.append("Merge \"");
-      msgbuf.append(c.getShortMessage());
-      msgbuf.append("\"");
-
-    } else {
-      msgbuf.append("Merge changes ");
-      for (final Iterator<CodeReviewCommit> i = merged.iterator(); i.hasNext();) {
-        msgbuf.append(i.next().change.getKey().abbreviate());
-        if (i.hasNext()) {
-          msgbuf.append(',');
-        }
-      }
-    }
-
+    StringBuilder msgbuf = new StringBuilder().append(summarize(rw, merged));
     if (!R_HEADS_MASTER.equals(destBranch.get())) {
       msgbuf.append(" into ");
       msgbuf.append(destBranch.getShortName());
@@ -619,26 +587,76 @@
     mergeCommit.setCommitter(myIdent);
     mergeCommit.setMessage(msgbuf.toString());
 
-    return (CodeReviewCommit) rw.parseCommit(commit(inserter, mergeCommit));
+    CodeReviewCommit mergeResult =
+        (CodeReviewCommit) rw.parseCommit(commit(inserter, mergeCommit));
+    mergeResult.setControl(n.getControl());
+    return mergeResult;
+  }
+
+  private String summarize(RevWalk rw, List<CodeReviewCommit> merged)
+      throws IOException {
+    if (merged.size() == 1) {
+      CodeReviewCommit c = merged.get(0);
+      rw.parseBody(c);
+      return String.format("Merge \"%s\"", c.getShortMessage());
+    }
+
+    LinkedHashSet<String> topics = new LinkedHashSet<>(4);
+    for (CodeReviewCommit c : merged) {
+      if (!Strings.isNullOrEmpty(c.change().getTopic())) {
+        topics.add(c.change().getTopic());
+      }
+    }
+
+    if (topics.size() == 1) {
+      return String.format("Merge topic '%s'", Iterables.getFirst(topics, null));
+    } else if (topics.size() > 1) {
+      return String.format("Merge topics '%s'", Joiner.on("', '").join(topics));
+    } else {
+      return String.format("Merge changes %s%s",
+          Joiner.on(',').join(Iterables.transform(
+              Iterables.limit(merged, 5),
+              new Function<CodeReviewCommit, String>() {
+                @Override
+                public String apply(CodeReviewCommit in) {
+                  return in.change().getKey().abbreviate();
+                }
+              })),
+          merged.size() > 5 ? ", ..." : "");
+    }
   }
 
   public ThreeWayMerger newThreeWayMerger(final Repository repo,
       final ObjectInserter inserter) {
-    ThreeWayMerger m;
+    return newThreeWayMerger(repo, inserter,
+        mergeStrategyName(useContentMerge, useRecursiveMerge));
+  }
+
+  public static String mergeStrategyName(boolean useContentMerge,
+      boolean useRecursiveMerge) {
     if (useContentMerge) {
       // Settings for this project allow us to try and automatically resolve
       // conflicts within files if needed. Use either the old resolve merger or
       // new recursive merger, and instruct to operate in core.
       if (useRecursiveMerge) {
-        m = MergeStrategy.RECURSIVE.newMerger(repo, true);
+        return MergeStrategy.RECURSIVE.getName();
       } else {
-        m = MergeStrategy.RESOLVE.newMerger(repo, true);
+        return MergeStrategy.RESOLVE.getName();
       }
     } else {
       // No auto conflict resolving allowed. If any of the
       // affected files was modified, merge will fail.
-      m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
+      return MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.getName();
     }
+  }
+
+  public static ThreeWayMerger newThreeWayMerger(Repository repo,
+      final ObjectInserter inserter, String strategyName) {
+    MergeStrategy strategy = MergeStrategy.get(strategyName);
+    checkArgument(strategy != null, "invalid merge strategy: %s", strategyName);
+    Merger m = strategy.newMerger(repo, true);
+    checkArgument(m instanceof ThreeWayMerger,
+        "merge strategy %s does not support three-way merging", strategyName);
     m.setObjectInserter(new ObjectInserter.Filter() {
       @Override
       protected ObjectInserter delegate() {
@@ -653,7 +671,7 @@
       public void release() {
       }
     });
-    return m;
+    return (ThreeWayMerger) m;
   }
 
   public ObjectId commit(final ObjectInserter inserter,
@@ -688,10 +706,10 @@
 
       CodeReviewCommit c;
       while ((c = (CodeReviewCommit) rw.next()) != null) {
-        if (c.patchsetId != null) {
-          c.statusCode = CommitMergeStatus.CLEAN_MERGE;
+        if (c.getPatchsetId() != null) {
+          c.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
           if (submitApproval == null) {
-            submitApproval = getSubmitter(c.patchsetId);
+            submitApproval = getSubmitter(c);
           }
         }
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
index 43e0975..e8b4b6a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -49,19 +49,24 @@
     }
 
     public PersonIdent getUserPersonIdent() {
-      return createPersonIdent();
+      return createPersonIdent(identifiedUser.get());
     }
 
     public MetaDataUpdate create(Project.NameKey name)
         throws RepositoryNotFoundException, IOException {
+      return create(name, identifiedUser.get());
+    }
+
+    public MetaDataUpdate create(Project.NameKey name, IdentifiedUser user)
+        throws RepositoryNotFoundException, IOException {
       MetaDataUpdate md = factory.create(name, mgr.openRepository(name));
-      md.getCommitBuilder().setAuthor(createPersonIdent());
+      md.getCommitBuilder().setAuthor(createPersonIdent(user));
       md.getCommitBuilder().setCommitter(serverIdent);
       return md;
     }
 
-    private PersonIdent createPersonIdent() {
-      return identifiedUser.get().newCommitterIdent(
+    private PersonIdent createPersonIdent(IdentifiedUser user) {
+      return user.newCommitterIdent(
           serverIdent.getWhen(), serverIdent.getTimeZone());
     }
   }
@@ -97,6 +102,7 @@
   private final Project.NameKey projectName;
   private final Repository db;
   private final CommitBuilder commit;
+  private boolean allowEmpty;
 
   @Inject
   public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
@@ -118,6 +124,10 @@
         getCommitBuilder().getCommitter().getTimeZone()));
   }
 
+  public void setAllowEmpty(boolean allowEmpty) {
+    this.allowEmpty = allowEmpty;
+  }
+
   /** Close the cached Repository handle. */
   public void close() {
     getRepository().close();
@@ -131,6 +141,10 @@
     return db;
   }
 
+  boolean allowEmpty() {
+    return allowEmpty;
+  }
+
   public CommitBuilder getCommitBuilder() {
     return commit;
   }
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 f7b5ab0..45ecb63 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
@@ -179,7 +179,7 @@
    * <p>
    * Must be called from the main thread, <em>not</em> a worker thread. Once a
    * worker thread calls {@link #end()}, the future has an additional
-   * <code>maxInterval</code> to finish before it is forcefully cancelled and
+   * {@code maxInterval} to finish before it is forcefully cancelled and
    * {@link ExecutionException} is thrown.
    *
    * @param workerFuture a future that returns when worker threads are finished.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
index 2c6c7b8..f1b0a78 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
@@ -88,8 +88,8 @@
   }
 
   /**
-   * Create a new commit in the <code>notesBranch</code> by updating existing
-   * or creating new notes from the <code>notes</code> map.
+   * Create a new commit in the {@code notesBranch} by updating existing
+   * or creating new notes from the {@code notes} map.
    *
    * @param notes map of notes
    * @param notesBranch notes branch to update
@@ -106,16 +106,16 @@
   }
 
   /**
-   * Create a new commit in the <code>notesBranch</code> by creating not yet
-   * existing notes from the <code>notes</code> map. The notes from the
-   * <code>notes</code> map which already exist in the note-tree of the
-   * tip of the <code>notesBranch</code> will not be updated.
+   * Create a new commit in the {@code notesBranch} by creating not yet
+   * existing notes from the {@code notes} map. The notes from the
+   * {@code notes} map which already exist in the note-tree of the
+   * tip of the {@code notesBranch} will not be updated.
    *
    * @param notes map of notes
    * @param notesBranch notes branch to update
    * @param commitAuthor author of the commit in the notes branch
    * @param commitMessage for the commit in the notes branch
-   * @return map with those notes from the <code>notes</code> that were newly
+   * @return map with those notes from the {@code notes} that were newly
    *         created
    * @throws IOException
    * @throws ConcurrentRefUpdateException
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotifyConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotifyConfig.java
index 3a0c278..abc53f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotifyConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotifyConfig.java
@@ -25,7 +25,7 @@
 
 public class NotifyConfig implements Comparable<NotifyConfig> {
   public static enum Header {
-    TO, CC, BCC;
+    TO, CC, BCC
   }
 
   private String name;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index e4ee1bf..55f4ff7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -40,6 +40,7 @@
 import com.google.gerrit.common.data.PermissionRule.Action;
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.State;
@@ -80,7 +81,7 @@
   private static final String KEY_LINK = "link";
   private static final String KEY_ENABLED = "enabled";
 
-  private static final String PROJECT_CONFIG = "project.config";
+  public static final String PROJECT_CONFIG = "project.config";
   private static final String GROUP_LIST = "groups";
 
   private static final String PROJECT = "project";
@@ -113,6 +114,7 @@
   private static final String KEY_MAX_OBJECT_SIZE_LIMIT = "maxObjectSizeLimit";
   private static final String KEY_REQUIRE_CONTRIBUTOR_AGREEMENT =
       "requireContributorAgreement";
+  private static final String KEY_CHECK_RECEIVED_OBJECTS = "checkReceivedObjects";
 
   private static final String SUBMIT = "submit";
   private static final String KEY_ACTION = "action";
@@ -156,6 +158,7 @@
   private ObjectId rulesId;
   private long maxObjectSizeLimit;
   private Map<String, Config> pluginConfigs;
+  private boolean checkReceivedObjects;
 
   public static ProjectConfig read(MetaDataUpdate update) throws IOException,
       ConfigInvalidException {
@@ -338,6 +341,13 @@
   }
 
   /**
+   * @return the checkReceivedObjects for this project, default is true.
+   */
+  public boolean getCheckReceivedObjects() {
+    return checkReceivedObjects;
+  }
+
+  /**
    * Check all GroupReferences use current group name, repairing stale ones.
    *
    * @param groupBackend cache to use when looking up group information by UUID.
@@ -370,7 +380,7 @@
 
   @Override
   protected String getRefName() {
-    return GitRepositoryManager.REF_CONFIG;
+    return RefNames.REFS_CONFIG;
   }
 
   @Override
@@ -407,8 +417,7 @@
     loadLabelSections(rc);
     loadCommentLinkSections(rc);
     loadPluginSections(rc);
-
-    maxObjectSizeLimit = rc.getLong(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, 0);
+    loadReceiveSection(rc);
   }
 
   private void loadAccountsSection(
@@ -695,6 +704,11 @@
     commentLinkSections = ImmutableList.copyOf(commentLinkSections);
   }
 
+  private void loadReceiveSection(Config rc) {
+    checkReceivedObjects = rc.getBoolean(RECEIVE, KEY_CHECK_RECEIVED_OBJECTS, true);
+    maxObjectSizeLimit = rc.getLong(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, 0);
+  }
+
   private void loadPluginSections(Config rc) {
     pluginConfigs = Maps.newHashMap();
     for (String plugin : rc.getSubsections(PLUGIN)) {
@@ -745,7 +759,7 @@
   }
 
   @Override
-  protected void onSave(CommitBuilder commit) throws IOException,
+  protected boolean onSave(CommitBuilder commit) throws IOException,
       ConfigInvalidException {
     if (commit.getMessage() == null || "".equals(commit.getMessage())) {
       commit.setMessage("Updated project configuration\n");
@@ -785,6 +799,7 @@
 
     saveConfig(PROJECT_CONFIG, rc);
     saveGroupList();
+    return true;
   }
 
   public static final String validMaxObjectSizeLimit(String value)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java
new file mode 100644
index 0000000..b4f41a0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectLevelConfig.java
@@ -0,0 +1,99 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.project.ProjectState;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Set;
+
+/** Configuration file in the projects refs/meta/config branch. */
+public class ProjectLevelConfig extends VersionedMetaData {
+  private final String fileName;
+  private final ProjectState project;
+  private Config cfg;
+
+  public ProjectLevelConfig(String fileName, ProjectState project) {
+    this.fileName = fileName;
+    this.project = project;
+  }
+
+  @Override
+  protected String getRefName() {
+    return RefNames.REFS_CONFIG;
+  }
+
+  @Override
+  protected void onLoad() throws IOException, ConfigInvalidException {
+    cfg = readConfig(fileName);
+  }
+
+  public Config get() {
+    if (cfg == null) {
+      cfg = new Config();
+    }
+    return cfg;
+  }
+
+  public Config getWithInheritance() {
+    Config cfgWithInheritance = new Config();
+    try {
+      cfgWithInheritance.fromText(get().toText());
+    } catch (ConfigInvalidException e) {
+      // cannot happen
+    }
+    ProjectState parent = Iterables.getFirst(project.parents(), null);
+    if (parent != null) {
+      Config parentCfg = parent.getConfig(fileName).getWithInheritance();
+      for (String section : parentCfg.getSections()) {
+        Set<String> allNames = get().getNames(section);
+        for (String name : parentCfg.getNames(section)) {
+          if (!allNames.contains(name)) {
+            cfgWithInheritance.setStringList(section, null, name,
+                Arrays.asList(parentCfg.getStringList(section, null, name)));
+          }
+        }
+
+        for (String subsection : parentCfg.getSubsections(section)) {
+          allNames = get().getNames(section, subsection);
+          for (String name : parentCfg.getNames(section, subsection)) {
+            if (!allNames.contains(name)) {
+              cfgWithInheritance.setStringList(section, subsection, name,
+                  Arrays.asList(parentCfg.getStringList(section, subsection, name)));
+            }
+          }
+        }
+      }
+    }
+    return cfgWithInheritance;
+  }
+
+  @Override
+  protected boolean onSave(CommitBuilder commit) throws IOException,
+      ConfigInvalidException {
+    if (commit.getMessage() == null || "".equals(commit.getMessage())) {
+      commit.setMessage("Updated configuration\n");
+    }
+    saveConfig(fileName, cfg);
+    return true;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java
index b539416..c7d925c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java
@@ -16,7 +16,7 @@
 
 public interface QueueProvider {
   public static enum QueueType {
-    INTERACTIVE, BATCH;
+    INTERACTIVE, BATCH
   }
 
   public WorkQueue.Executor getQueue(QueueType type);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
index ac1929b..b0232ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
@@ -27,20 +27,20 @@
 import java.util.List;
 import java.util.Set;
 
-class RebaseSorter {
+public class RebaseSorter {
 
   private final RevWalk rw;
   private final RevFlag canMergeFlag;
   private final Set<RevCommit> accepted;
 
-  RebaseSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
+  public RebaseSorter(final RevWalk rw, final Set<RevCommit> alreadyAccepted,
       final RevFlag canMergeFlag) {
     this.rw = rw;
     this.canMergeFlag = canMergeFlag;
     this.accepted = alreadyAccepted;
   }
 
-  List<CodeReviewCommit> sort(Collection<CodeReviewCommit> incoming)
+  public List<CodeReviewCommit> sort(Collection<CodeReviewCommit> incoming)
       throws IOException {
     final List<CodeReviewCommit> sorted = new ArrayList<CodeReviewCommit>();
     final Set<CodeReviewCommit> sort = new HashSet<CodeReviewCommit>(incoming);
@@ -61,16 +61,16 @@
           // aren't permitted to merge at this time. Drop n.
           //
           if (n.missing == null) {
-            n.statusCode = CommitMergeStatus.MISSING_DEPENDENCY;
+            n.setStatusCode(CommitMergeStatus.MISSING_DEPENDENCY);
             n.missing = new ArrayList<CodeReviewCommit>();
           }
-          n.missing.add((CodeReviewCommit) c);
+          n.missing.add(c);
         } else {
           contents.add(c);
         }
       }
 
-      if (n.statusCode == CommitMergeStatus.MISSING_DEPENDENCY) {
+      if (n.getStatusCode() == CommitMergeStatus.MISSING_DEPENDENCY) {
         continue;
       }
 
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 94d209b..400b2ba 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,9 +14,10 @@
 
 package com.google.gerrit.server.git;
 
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
 import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
-import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
 import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
+import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromReviewers;
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static org.eclipse.jgit.lib.RefDatabase.ALL;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
@@ -26,6 +27,7 @@
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
 
 import com.google.common.base.Function;
+import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
@@ -51,16 +53,20 @@
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicMap.Entry;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 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.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalCopier;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
@@ -68,14 +74,14 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountResolver;
 import com.google.gerrit.server.change.ChangeInserter;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.change.PatchSetInserter.ChangeKind;
+import com.google.gerrit.server.change.ChangesCollection;
+import com.google.gerrit.server.change.MergeabilityChecker;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.change.Submit;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.TrackingFooters;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.events.CommitReceivedEvent;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.MultiProgressMonitor.Task;
@@ -87,12 +93,14 @@
 import com.google.gerrit.server.mail.MailUtil.MailRecipients;
 import com.google.gerrit.server.mail.MergedSender;
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gerrit.server.util.MagicBranch;
 import com.google.gerrit.server.util.RequestScopePropagator;
@@ -140,6 +148,7 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -157,8 +166,8 @@
   private static final Logger log =
       LoggerFactory.getLogger(ReceiveCommits.class);
 
-  public static final Pattern NEW_PATCHSET =
-      Pattern.compile("^refs/changes/(?:[0-9][0-9]/)?([1-9][0-9]*)(?:/new)?$");
+  public static final Pattern NEW_PATCHSET = Pattern.compile(
+      "^" + REFS_CHANGES + "(?:[0-9][0-9]/)?([1-9][0-9]*)(?:/new)?$");
 
   private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
 
@@ -169,12 +178,12 @@
   private enum Error {
         CONFIG_UPDATE("You are not allowed to perform this operation.\n"
         + "Configuration changes can only be pushed by project owners\n"
-        + "who also have 'Push' rights on " + GitRepositoryManager.REF_CONFIG),
+        + "who also have 'Push' rights on " + RefNames.REFS_CONFIG),
         UPDATE("You are not allowed to perform this operation.\n"
         + "To push into this reference you need 'Push' rights."),
         DELETE("You need 'Push' rights with the 'Force Push'\n"
             + "flag set to delete references."),
-        DELETE_CHANGES("Cannot delete from 'refs/changes'"),
+        DELETE_CHANGES("Cannot delete from '" + REFS_CHANGES + "'"),
         CODE_REVIEW("You need 'Push' rights to upload code review requests.\n"
             + "Verify that you are pushing to the right branch.");
 
@@ -255,6 +264,8 @@
 
   private final IdentifiedUser currentUser;
   private final ReviewDb db;
+  private final ChangeData.Factory changeDataFactory;
+  private final ChangeUpdate.Factory updateFactory;
   private final SchemaFactory<ReviewDb> schemaFactory;
   private final AccountResolver accountResolver;
   private final CmdLineParser.Factory optionParserFactory;
@@ -265,18 +276,20 @@
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final ChangeHooks hooks;
   private final ApprovalsUtil approvalsUtil;
+  private final ApprovalCopier approvalCopier;
   private final GitRepositoryManager repoManager;
   private final ProjectCache projectCache;
   private final String canonicalWebUrl;
   private final CommitValidators.Factory commitValidatorsFactory;
-  private final TrackingFooters trackingFooters;
   private final TagCache tagCache;
   private final AccountCache accountCache;
+  private final ChangesCollection changes;
   private final ChangeInserter.Factory changeInserterFactory;
   private final WorkQueue workQueue;
   private final ListeningExecutorService changeUpdateExector;
   private final RequestScopePropagator requestScopePropagator;
   private final ChangeIndexer indexer;
+  private final MergeabilityChecker mergeabilityChecker;
   private final SshInfo sshInfo;
   private final AllProjectsName allProjectsName;
   private final ReceiveConfig receiveConfig;
@@ -303,7 +316,7 @@
   private final SubmoduleOp.Factory subOpFactory;
   private final Provider<Submit> submitProvider;
   private final MergeQueue mergeQueue;
-  private final MergeUtil.Factory mergeUtilFactory;
+  private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
 
   private final List<CommitValidationMessage> messages = new ArrayList<CommitValidationMessage>();
   private ListMultimap<Error, String> errors = LinkedListMultimap.create();
@@ -317,6 +330,8 @@
   @Inject
   ReceiveCommits(final ReviewDb db,
       final SchemaFactory<ReviewDb> schemaFactory,
+      final ChangeData.Factory changeDataFactory,
+      final ChangeUpdate.Factory updateFactory,
       final AccountResolver accountResolver,
       final CmdLineParser.Factory optionParserFactory,
       final CreateChangeSender.Factory createChangeSenderFactory,
@@ -326,20 +341,22 @@
       final PatchSetInfoFactory patchSetInfoFactory,
       final ChangeHooks hooks,
       final ApprovalsUtil approvalsUtil,
+      final ApprovalCopier approvalCopier,
       final ProjectCache projectCache,
       final GitRepositoryManager repoManager,
       final TagCache tagCache,
       final AccountCache accountCache,
       final ChangeCache changeCache,
+      final ChangesCollection changes,
       final ChangeInserter.Factory changeInserterFactory,
       final CommitValidators.Factory commitValidatorsFactory,
-      @CanonicalWebUrl @Nullable final String canonicalWebUrl,
+      @CanonicalWebUrl final String canonicalWebUrl,
       @GerritPersonIdent final PersonIdent gerritIdent,
-      final TrackingFooters trackingFooters,
       final WorkQueue workQueue,
       @ChangeUpdateExecutor ListeningExecutorService changeUpdateExector,
       final RequestScopePropagator requestScopePropagator,
       final ChangeIndexer indexer,
+      final MergeabilityChecker mergeabilityChecker,
       final SshInfo sshInfo,
       final AllProjectsName allProjectsName,
       ReceiveConfig config,
@@ -348,9 +365,11 @@
       final SubmoduleOp.Factory subOpFactory,
       final Provider<Submit> submitProvider,
       final MergeQueue mergeQueue,
-      final MergeUtil.Factory mergeUtilFactory) throws IOException {
+      final DynamicMap<ProjectConfigEntry> pluginConfigEntries) throws IOException {
     this.currentUser = (IdentifiedUser) projectControl.getCurrentUser();
     this.db = db;
+    this.changeDataFactory = changeDataFactory;
+    this.updateFactory = updateFactory;
     this.schemaFactory = schemaFactory;
     this.accountResolver = accountResolver;
     this.optionParserFactory = optionParserFactory;
@@ -361,18 +380,20 @@
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.hooks = hooks;
     this.approvalsUtil = approvalsUtil;
+    this.approvalCopier = approvalCopier;
     this.projectCache = projectCache;
     this.repoManager = repoManager;
     this.canonicalWebUrl = canonicalWebUrl;
-    this.trackingFooters = trackingFooters;
     this.tagCache = tagCache;
     this.accountCache = accountCache;
+    this.changes = changes;
     this.changeInserterFactory = changeInserterFactory;
     this.commitValidatorsFactory = commitValidatorsFactory;
     this.workQueue = workQueue;
     this.changeUpdateExector = changeUpdateExector;
     this.requestScopePropagator = requestScopePropagator;
     this.indexer = indexer;
+    this.mergeabilityChecker = mergeabilityChecker;
     this.sshInfo = sshInfo;
     this.allProjectsName = allProjectsName;
     this.receiveConfig = config;
@@ -387,22 +408,24 @@
     this.subOpFactory = subOpFactory;
     this.submitProvider = submitProvider;
     this.mergeQueue = mergeQueue;
-    this.mergeUtilFactory = mergeUtilFactory;
+    this.pluginConfigEntries = pluginConfigEntries;
 
     this.messageSender = new ReceivePackMessageSender();
 
+    ProjectState ps = projectControl.getProjectState();
+
     rp.setAllowCreates(true);
     rp.setAllowDeletes(true);
     rp.setAllowNonFastForwards(true);
-    rp.setCheckReceivedObjects(true);
+    rp.setCheckReceivedObjects(ps.getConfig().getCheckReceivedObjects());
     rp.setRefFilter(new RefFilter() {
       @Override
       public Map<String, Ref> filter(Map<String, Ref> refs) {
         Map<String, Ref> filteredRefs = Maps.newHashMapWithExpectedSize(refs.size());
         for (Map.Entry<String, Ref> e : refs.entrySet()) {
           String name = e.getKey();
-          if (!name.startsWith("refs/changes/")
-              && !name.startsWith(GitRepositoryManager.REFS_CACHE_AUTOMERGE)) {
+          if (!name.startsWith(REFS_CHANGES)
+              && !name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)) {
             filteredRefs.put(name, e.getValue());
           }
         }
@@ -423,9 +446,10 @@
         if (allRefs == null) {
           try {
             allRefs = rp.getRepository().getRefDatabase().getRefs(ALL);
+          } catch (ServiceMayNotContinueException e) {
+            throw e;
           } catch (IOException e) {
-            ServiceMayNotContinueException ex =
-                new ServiceMayNotContinueException(e.getMessage());
+            ServiceMayNotContinueException ex = new ServiceMayNotContinueException();
             ex.initCause(e);
             throw ex;
           }
@@ -610,22 +634,42 @@
             return input.created;
           }
         });
-    if (!Iterables.isEmpty(created) && canonicalWebUrl != null) {
-      final String url = canonicalWebUrl;
+    if (!Iterables.isEmpty(created)) {
       addMessage("");
       addMessage("New Changes:");
       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]");
-        }
-        addMessage(m.toString());
+        addMessage(formatChangeUrl(canonicalWebUrl, c.change));
       }
       addMessage("");
     }
+
+    Iterable<ReplaceRequest> updated =
+        Iterables.filter(replaceByChange.values(),
+            new Predicate<ReplaceRequest>() {
+              @Override
+              public boolean apply(ReplaceRequest input) {
+                return !input.skip && input.inputCommand.getResult() == OK;
+              }
+            });
+    if (!Iterables.isEmpty(updated)) {
+      addMessage("");
+      addMessage("Updated Changes:");
+      for (ReplaceRequest u : updated) {
+        addMessage(formatChangeUrl(canonicalWebUrl, u.change));
+      }
+      addMessage("");
+    }
+  }
+
+  private static String formatChangeUrl(String url, Change change) {
+    StringBuilder m = new StringBuilder()
+        .append("  ")
+        .append(url)
+        .append(change.getChangeId());
+    if (change.getStatus() == Change.Status.DRAFT) {
+      m.append(" [DRAFT]");
+    }
+    return m.toString();
   }
 
   private void insertChangesAndPatchSets() {
@@ -843,6 +887,40 @@
                   continue;
                 }
               }
+
+              for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+                PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
+                ProjectConfigEntry configEntry = e.getProvider().get();
+                String value = pluginCfg.getString(e.getExportName());
+                String oldValue =
+                    projectControl.getProjectState().getConfig()
+                        .getPluginConfig(e.getPluginName())
+                        .getString(e.getExportName());
+                if (configEntry.getType() == ProjectConfigEntry.Type.ARRAY) {
+                  List<String> l =
+                      Arrays.asList(projectControl.getProjectState()
+                          .getConfig().getPluginConfig(e.getPluginName())
+                          .getStringList(e.getExportName()));
+                  oldValue = Joiner.on("\n").join(l);
+                }
+
+                if ((value == null ? oldValue != null : !value.equals(oldValue)) &&
+                    !configEntry.isEditable(projectControl.getProjectState())) {
+                  reject(cmd, String.format(
+                      "invalid project configuration: Not allowed to set parameter"
+                          + " '%s' of plugin '%s' on project '%s'.",
+                      e.getExportName(), e.getPluginName(), project.getName()));
+                  continue;
+                }
+
+                if (ProjectConfigEntry.Type.LIST.equals(configEntry.getType())
+                    && value != null && !configEntry.getPermittedValues().contains(value)) {
+                  reject(cmd, String.format(
+                      "invalid project configuration: The value '%s' is "
+                          + "not permitted for parameter '%s' of plugin '%s'.",
+                      value, e.getExportName(), e.getPluginName()));
+                }
+              }
             } catch (Exception e) {
               reject(cmd, "invalid project configuration");
               log.error("User " + currentUser.getUserName()
@@ -897,8 +975,8 @@
       validateNewCommits(ctl, cmd);
       batch.addCommand(cmd);
     } else {
-      if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
-        errors.put(Error.CONFIG_UPDATE, GitRepositoryManager.REF_CONFIG);
+      if (RefNames.REFS_CONFIG.equals(ctl.getRefName())) {
+        errors.put(Error.CONFIG_UPDATE, RefNames.REFS_CONFIG);
       } else {
         errors.put(Error.UPDATE, ctl.getRefName());
       }
@@ -927,13 +1005,13 @@
 
   private void parseDelete(final ReceiveCommand cmd) {
     RefControl ctl = projectControl.controlForRef(cmd.getRefName());
-    if (ctl.getRefName().startsWith("refs/changes/")) {
+    if (ctl.getRefName().startsWith(REFS_CHANGES)) {
       errors.put(Error.DELETE_CHANGES, ctl.getRefName());
       reject(cmd, "cannot delete changes");
     } else if (ctl.canDelete()) {
       batch.addCommand(cmd);
     } else {
-      if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
+      if (RefNames.REFS_CONFIG.equals(ctl.getRefName())) {
         reject(cmd, "cannot delete project configuration");
       } else {
         errors.put(Error.DELETE, ctl.getRefName());
@@ -979,10 +1057,10 @@
     RefControl ctl;
     Set<Account.Id> reviewer = Sets.newLinkedHashSet();
     Set<Account.Id> cc = Sets.newLinkedHashSet();
-    RevCommit baseCommit;
+    List<RevCommit> baseCommit;
 
     @Option(name = "--base", metaVar = "BASE", usage = "merge base of changes")
-    ObjectId base;
+    List<ObjectId> base;
 
     @Option(name = "--topic", metaVar = "NAME", usage = "attach topic to changes")
     String topic;
@@ -1117,8 +1195,9 @@
     }
 
     if (magicBranch.isDraft()
-        && projectControl.controlForRef("refs/drafts/" + ref)
-            .isBlocked(Permission.PUSH)) {
+        && (!receiveConfig.allowDrafts
+            || projectControl.controlForRef("refs/drafts/" + ref)
+            .isBlocked(Permission.PUSH))) {
       errors.put(Error.CODE_REVIEW, ref);
       reject(cmd, "cannot upload drafts");
       return;
@@ -1142,20 +1221,24 @@
 
     RevWalk walk = rp.getRevWalk();
     if (magicBranch.base != null) {
-      try {
-        magicBranch.baseCommit = walk.parseCommit(magicBranch.base);
-      } catch (IncorrectObjectTypeException notCommit) {
-        reject(cmd, "base must be a commit");
-        return;
-      } catch (MissingObjectException e) {
-        reject(cmd, "base not found");
-        return;
-      } catch (IOException e) {
-        log.warn(String.format(
-            "Project %s cannot read %s",
-            project.getName(), magicBranch.base.name()), e);
-        reject(cmd, "internal server error");
-        return;
+      magicBranch.baseCommit = Lists.newArrayListWithCapacity(
+          magicBranch.base.size());
+      for (ObjectId id : magicBranch.base) {
+        try {
+          magicBranch.baseCommit.add(walk.parseCommit(id));
+        } catch (IncorrectObjectTypeException notCommit) {
+          reject(cmd, "base must be a commit");
+          return;
+        } catch (MissingObjectException e) {
+          reject(cmd, "base not found");
+          return;
+        } catch (IOException e) {
+          log.warn(String.format(
+              "Project %s cannot read %s",
+              project.getName(), id.name()), e);
+          reject(cmd, "internal server error");
+          return;
+        }
       }
     }
 
@@ -1182,7 +1265,6 @@
         walk.markStart(h);
         if (walk.next() == null) {
           reject(magicBranch.cmd, "no common ancestry");
-          return;
         }
       } finally {
         walk.reset();
@@ -1191,7 +1273,6 @@
     } catch (IOException e) {
       magicBranch.cmd.setResult(REJECTED_MISSING_OBJECT);
       log.error("Invalid pack upload; one or more objects weren't sent", e);
-      return;
     }
   }
 
@@ -1212,7 +1293,7 @@
    */
   private NoteMap loadRejectCommitsMap() throws IOException {
     try {
-      Ref ref = repo.getRef(GitRepositoryManager.REF_REJECT_COMMITS);
+      Ref ref = repo.getRef(RefNames.REFS_REJECT_COMMITS);
       if (ref == null) {
         return NoteMap.newEmptyMap();
       }
@@ -1222,7 +1303,7 @@
       return NoteMap.read(rw.getObjectReader(), map);
     } catch (IOException badMap) {
       throw new IOException("Cannot load "
-          + GitRepositoryManager.REF_REJECT_COMMITS, badMap);
+          + RefNames.REFS_REJECT_COMMITS, badMap);
     }
   }
 
@@ -1295,7 +1376,9 @@
       Set<ObjectId> existing = Sets.newHashSet();
       walk.markStart(walk.parseCommit(magicBranch.cmd.getNewId()));
       if (magicBranch.baseCommit != null) {
-        walk.markUninteresting(magicBranch.baseCommit);
+        for (RevCommit c : magicBranch.baseCommit) {
+          walk.markUninteresting(c);
+        }
         assert magicBranch.ctl != null;
         Ref targetRef = allRefs.get(magicBranch.ctl.getRefName());
         if (targetRef != null) {
@@ -1412,7 +1495,7 @@
     for (Ref ref : allRefs.values()) {
       if (ref.getObjectId() == null) {
         continue;
-      } else if (ref.getName().startsWith("refs/changes/")) {
+      } else if (ref.getName().startsWith(REFS_CHANGES)) {
         existing.add(ref.getObjectId());
       } else if (ref.getName().startsWith(R_HEADS)
           || (forRef != null && forRef.equals(ref.getName()))) {
@@ -1547,8 +1630,14 @@
   private void submit(ChangeControl changeCtl, PatchSet ps)
       throws OrmException, IOException {
     Submit submit = submitProvider.get();
-    RevisionResource rsrc = new RevisionResource(new ChangeResource(changeCtl), ps);
-    Change c = submit.submit(rsrc, currentUser);
+    RevisionResource rsrc = new RevisionResource(changes.parse(changeCtl), ps);
+    Change c;
+    try {
+      // Force submit even if submit rule evaluation fails.
+      c = submit.submit(rsrc, currentUser, true);
+    } catch (ResourceConflictException e) {
+      throw new IOException(e);
+    }
     if (c == null) {
       addError("Submitting change " + changeCtl.getChange().getChangeId()
           + " failed.");
@@ -1653,7 +1742,6 @@
     ChangeMessage msg;
     String mergedIntoRef;
     boolean skip;
-    ChangeKind changeKind;
     private PatchSet.Id priorPatchSet;
 
     ReplaceRequest(final Change.Id toChange, final RevCommit newCommit,
@@ -1662,7 +1750,6 @@
       this.newCommit = newCommit;
       this.inputCommand = cmd;
       this.checkMergedInto = checkMergedInto;
-      this.changeKind = ChangeKind.REWORK;
 
       revisions = HashBiMap.create();
       for (Ref ref : refs(toChange)) {
@@ -1772,10 +1859,6 @@
         }
       }
 
-      changeKind =
-          PatchSetInserter.getChangeKind(mergeUtilFactory,
-              projectControl.getProjectState(), repo, priorCommit, newCommit);
-
       PatchSet.Id id =
           ChangeUtil.nextPatchSetId(allRefs, change.currentPatchSetId());
       newPatchSet = new PatchSet(id);
@@ -1833,6 +1916,7 @@
       recipients.add(getRecipientsFromFooters(accountResolver, newPatchSet, footerLines));
       recipients.remove(me);
 
+      ChangeUpdate update = updateFactory.create(changeCtl, newPatchSet.getCreatedOn());
       db.changes().beginTransaction(change.getId());
       try {
         change = db.changes().get(change.getId());
@@ -1849,14 +1933,12 @@
           mergedIntoRef = mergedInto != null ? mergedInto.getName() : null;
         }
 
-        List<PatchSetApproval> oldChangeApprovals =
-            db.patchSetApprovals().byChange(change.getId()).toList();
-        final MailRecipients oldRecipients = getRecipientsFromApprovals(
-            oldChangeApprovals);
-        ApprovalsUtil.copyLabels(db, labelTypes, oldChangeApprovals,
-            priorPatchSet, newPatchSet, changeKind);
-        approvalsUtil.addReviewers(db, labelTypes, change, newPatchSet, info,
-            recipients.getReviewers(), oldRecipients.getAll());
+        ChangeData cd = changeDataFactory.create(db, changeCtl);
+        MailRecipients oldRecipients =
+            getRecipientsFromReviewers(cd.reviewers());
+        approvalCopier.copy(db, changeCtl, newPatchSet);
+        approvalsUtil.addReviewers(db, update, labelTypes, change, newPatchSet,
+            info, recipients.getReviewers(), oldRecipients.getAll());
         recipients.add(oldRecipients);
 
         msg =
@@ -1864,9 +1946,6 @@
                 .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.
@@ -1917,6 +1996,7 @@
       } finally {
         db.rollback();
       }
+      update.commit();
 
       if (mergedIntoRef != null) {
         // Change was already submitted to a branch, close it.
@@ -1927,7 +2007,10 @@
       if (cmd.getResult() == NOT_ATTEMPTED) {
         cmd.execute(rp);
       }
-      CheckedFuture<?, IOException> indexFuture = indexer.indexAsync(change);
+      CheckedFuture<?, IOException> f = mergeabilityChecker.newCheck()
+          .addChange(change)
+          .reindex()
+          .runAsync();
       gitRefUpdated.fire(project.getNameKey(), newPatchSet.getRefName(),
           ObjectId.zeroId(), newCommit);
       hooks.doPatchsetCreatedHook(change, newPatchSet, db);
@@ -1961,7 +2044,7 @@
           return "send-email newpatchset";
         }
       }));
-      indexFuture.checkedGet();
+      f.checkedGet();
 
       if (magicBranch != null && magicBranch.isSubmit()) {
         submit(changeCtl, newPatchSet);
@@ -2056,7 +2139,7 @@
         && ctl.canUploadMerges()
         && !projectControl.getProjectState().isUseSignedOffBy()
         && Iterables.isEmpty(rejectCommits)
-        && !GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())
+        && !RefNames.REFS_CONFIG.equals(ctl.getRefName())
         && !(MagicBranch.isMagicBranch(cmd.getRefName())
             || NEW_PATCHSET.matcher(cmd.getRefName()).matches())) {
       return;
@@ -2234,7 +2317,7 @@
   private SetMultimap<ObjectId, Ref> changeRefsById() throws IOException {
     if (refsById == null) {
       refsById =  HashMultimap.create();
-      for (Ref r : repo.getRefDatabase().getRefs("refs/changes/").values()) {
+      for (Ref r : repo.getRefDatabase().getRefs(REFS_CHANGES).values()) {
         if (PatchSet.isRef(r.getName())) {
           refsById.put(r.getObjectId(), r);
         }
@@ -2252,39 +2335,13 @@
     return r;
   }
 
-  private void markChangeMergedByPush(final ReviewDb db,
-      final ReplaceRequest result) throws OrmException,
-      IOException {
-    Change change = result.change;
-    final String mergedIntoRef = result.mergedIntoRef;
-
-    change.setCurrentPatchSet(result.info);
-    change.setStatus(Change.Status.MERGED);
-    ChangeUtil.updated(change);
-
-    approvalsUtil.syncChangeStatus(change);
-
-    final StringBuilder msgBuf = new StringBuilder();
-    msgBuf.append("Change has been successfully pushed");
-    if (!mergedIntoRef.equals(change.getDest().get())) {
-      msgBuf.append(" into ");
-      if (mergedIntoRef.startsWith(Constants.R_HEADS)) {
-        msgBuf.append("branch ");
-        msgBuf.append(Repository.shortenRefName(mergedIntoRef));
-      } else {
-        msgBuf.append(mergedIntoRef);
-      }
-    }
-    msgBuf.append(".");
-    final ChangeMessage msg = new ChangeMessage(
-        new ChangeMessage.Key(change.getId(), ChangeUtil.messageUUID(db)),
-        currentUser.getAccountId(), TimeUtil.nowTs(), result.info.getKey());
-    msg.setMessage(msgBuf.toString());
-
-    db.changeMessages().insert(Collections.singleton(msg));
-
-    change = db.changes().atomicUpdate(
-        change.getId(), new AtomicUpdate<Change>() {
+  private void markChangeMergedByPush(ReviewDb db, final ReplaceRequest result)
+      throws OrmException, IOException {
+    Change.Id id = result.change.getId();
+    db.changes().beginTransaction(id);
+    Change change;
+    try {
+      change = db.changes().atomicUpdate(id, new AtomicUpdate<Change>() {
           @Override
           public Change update(Change change) {
             if (change.getStatus().isOpen()) {
@@ -2295,7 +2352,32 @@
             return change;
           }
         });
-    indexer.index(change);
+      String mergedIntoRef = result.mergedIntoRef;
+
+      StringBuilder msgBuf = new StringBuilder();
+      msgBuf.append("Change has been successfully pushed");
+      if (!mergedIntoRef.equals(change.getDest().get())) {
+        msgBuf.append(" into ");
+        if (mergedIntoRef.startsWith(Constants.R_HEADS)) {
+          msgBuf.append("branch ");
+          msgBuf.append(Repository.shortenRefName(mergedIntoRef));
+        } else {
+          msgBuf.append(mergedIntoRef);
+        }
+      }
+      msgBuf.append(".");
+      ChangeMessage msg = new ChangeMessage(
+          new ChangeMessage.Key(id, ChangeUtil.messageUUID(db)),
+          currentUser.getAccountId(), change.getLastUpdatedOn(),
+          result.info.getKey());
+      msg.setMessage(msgBuf.toString());
+
+      db.changeMessages().insert(Collections.singleton(msg));
+      db.commit();
+    } finally {
+      db.rollback();
+    }
+    indexer.index(db, change);
   }
 
   private void sendMergedEmail(final ReplaceRequest result) {
@@ -2343,6 +2425,6 @@
   }
 
   private static boolean isConfig(final ReceiveCommand cmd) {
-    return cmd.getRefName().equals(GitRepositoryManager.REF_CONFIG);
+    return cmd.getRefName().equals(RefNames.REFS_CONFIG);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
index 530a388..7ae8271 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.util.MagicBranch;
 import com.google.gwtorm.server.OrmException;
@@ -68,9 +69,10 @@
     if (oldRefs == null) {
       try {
         oldRefs = rp.getRepository().getRefDatabase().getRefs(ALL);
+      } catch (ServiceMayNotContinueException e) {
+        throw e;
       } catch (IOException e) {
-        ServiceMayNotContinueException ex =
-            new ServiceMayNotContinueException(e.getMessage());
+        ServiceMayNotContinueException ex = new ServiceMayNotContinueException();
         ex.initCause(e);
         throw ex;
       }
@@ -163,8 +165,8 @@
   }
 
   private static boolean skip(String name) {
-    return name.startsWith("refs/changes/")
-        || name.startsWith(GitRepositoryManager.REFS_CACHE_AUTOMERGE)
+    return name.startsWith(RefNames.REFS_CHANGES)
+        || name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)
         || MagicBranch.isMagicBranch(name);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java
index d1a3e40..2efc94c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java
@@ -24,6 +24,7 @@
 class ReceiveConfig {
   final boolean checkMagicRefs;
   final boolean checkReferencedObjectsAreReachable;
+  final boolean allowDrafts;
 
   @Inject
   ReceiveConfig(@GerritServerConfig Config config) {
@@ -33,5 +34,8 @@
     checkReferencedObjectsAreReachable = config.getBoolean(
         "receive", null, "checkReferencedObjectsAreReachable",
         true);
+    allowDrafts = config.getBoolean(
+        "change", null, "allowDrafts",
+        true);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceivePackInitializer.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceivePackInitializer.java
new file mode 100644
index 0000000..ec0bdaf
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceivePackInitializer.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.reviewdb.client.Project;
+
+import org.eclipse.jgit.transport.ReceivePack;
+
+@ExtensionPoint
+public interface ReceivePackInitializer {
+
+  /**
+   * ReceivePack initialization.
+   *
+   * Invoked by Gerrit when a new ReceivePack instance is created and just
+   * before it is used. Implementors will usually call setXXX methods on the
+   * receivePack parameter in order to set additional properties on it.
+   *
+   * @param project project for which the ReceivePack is created
+   * @param receivePack the ReceivePack instance which is being initialized
+   */
+  public void init(Project.NameKey project, ReceivePack receivePack);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index a3e9e9f..5424887 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -216,8 +216,9 @@
           for (final Change chg : submitted) {
             final CodeReviewCommit c = commits.get(chg.getId());
             if (c != null
-                && (c.statusCode == CommitMergeStatus.CLEAN_MERGE
-                    || c.statusCode == CommitMergeStatus.CLEAN_PICK || c.statusCode == CommitMergeStatus.CLEAN_REBASE)) {
+                && (c.getStatusCode() == CommitMergeStatus.CLEAN_MERGE
+                    || c.getStatusCode() == CommitMergeStatus.CLEAN_PICK
+                    || c.getStatusCode() == CommitMergeStatus.CLEAN_REBASE)) {
               msgbuf += "\n";
               msgbuf += c.getFullMessage();
             }
@@ -274,7 +275,7 @@
 
         msgbuf.append("\nProject: ");
         msgbuf.append(me.getKey().getParentKey().get());
-        msgbuf.append("  " + me.getValue().getName());
+        msgbuf.append("  ").append(me.getValue().getName());
         msgbuf.append("\n");
         if (modules.size() == 1 && msg != null) {
           msgbuf.append(msg);
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 7c3c0c7..c665d67 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
@@ -54,16 +54,24 @@
  */
 public abstract class VersionedMetaData {
   private RevCommit revision;
-  private ObjectReader reader;
-  private ObjectInserter inserter;
-  private DirCache newTree;
+  protected ObjectReader reader;
+  protected ObjectInserter inserter;
+  protected DirCache newTree;
 
   /** @return name of the reference storing this configuration. */
   protected abstract String getRefName();
 
+  /** Set up the metadata, parsing any state from the loaded revision. */
   protected abstract void onLoad() throws IOException, ConfigInvalidException;
 
-  protected abstract void onSave(CommitBuilder commit) throws IOException,
+  /**
+   * Save any changes to the metadata in a commit.
+   *
+   * @return true if the commit should proceed, false to abort.
+   * @throws IOException
+   * @throws ConfigInvalidException
+   */
+  protected abstract boolean onSave(CommitBuilder commit) throws IOException,
       ConfigInvalidException;
 
   /** @return revision of the metadata that was loaded. */
@@ -71,18 +79,6 @@
     return revision != null ? revision.copy() : null;
   }
 
-  /** Initialize in-memory as though the repository branch doesn't exist. */
-  public void createInMemory() {
-    try {
-      revision = null;
-      onLoad();
-    } catch (IOException err) {
-      throw new RuntimeException("Unexpected IOException", err);
-    } catch (ConfigInvalidException err) {
-      throw new RuntimeException("Unexpected ConfigInvalidException", err);
-    }
-  }
-
   /**
    * Load the current version from the branch.
    * <p>
@@ -116,19 +112,13 @@
    */
   public void load(Repository db, ObjectId id) throws IOException,
       ConfigInvalidException {
-    if (id != null) {
-      reader = db.newObjectReader();
-      try {
-        revision = new RevWalk(reader).parseCommit(id);
-        onLoad();
-      } finally {
-        reader.release();
-        reader = null;
-      }
-    } else {
-      // The branch does not yet exist.
-      revision = null;
+    reader = db.newObjectReader();
+    try {
+      revision = id != null ? new RevWalk(reader).parseCommit(id) : null;
       onLoad();
+    } finally {
+      reader.release();
+      reader = null;
     }
   }
 
@@ -207,7 +197,7 @@
         write(VersionedMetaData.this, commit);
       }
 
-      private void doSave(VersionedMetaData config, CommitBuilder commit) throws IOException {
+      private boolean doSave(VersionedMetaData config, CommitBuilder commit) throws IOException {
         DirCache nt = config.newTree;
         ObjectReader r = config.reader;
         ObjectInserter i = config.inserter;
@@ -215,7 +205,7 @@
           config.newTree = newTree;
           config.reader = reader;
           config.inserter = inserter;
-          config.onSave(commit);
+          return config.onSave(commit);
         } catch (ConfigInvalidException e) {
           throw new IOException("Cannot update " + getRefName() + " in "
               + db.getDirectory() + ": " + e.getMessage(), e);
@@ -228,10 +218,12 @@
 
       @Override
       public void write(VersionedMetaData config, CommitBuilder commit) throws IOException {
-        doSave(config, commit);
+        if (!doSave(config, commit)) {
+          return;
+        }
 
         final ObjectId res = newTree.writeTree(inserter);
-        if (res.equals(srcTree)) {
+        if (res.equals(srcTree) && !update.allowEmpty()) {
           // If there are no changes to the content, don't create the commit.
           return;
         }
@@ -319,7 +311,7 @@
     };
   }
 
-  private DirCache readTree(RevTree tree) throws IOException,
+  protected DirCache readTree(RevTree tree) throws IOException,
       MissingObjectException, IncorrectObjectTypeException {
     DirCache dc = DirCache.newInCore();
     if (tree != null) {
@@ -339,7 +331,7 @@
         rc.fromText(text);
       } catch (ConfigInvalidException err) {
         throw new ConfigInvalidException("Invalid config file " + fileName
-            + " in commit" + revision.name(), err);
+            + " in commit " + revision.name(), err);
       }
     }
     return rc;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index 8c65b1a..343b49c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -19,9 +19,11 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gwtorm.server.OrmException;
+
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
@@ -68,9 +70,9 @@
 
   public Map<String, Ref> filter(Map<String, Ref> refs, boolean filterTagsSeperately) {
     if (projectCtl.allRefsAreVisibleExcept(
-        ImmutableSet.of(GitRepositoryManager.REF_CONFIG))) {
+        ImmutableSet.of(RefNames.REFS_CONFIG))) {
       Map<String, Ref> r = Maps.newHashMap(refs);
-      r.remove(GitRepositoryManager.REF_CONFIG);
+      r.remove(RefNames.REFS_CONFIG);
       return r;
     }
 
@@ -79,7 +81,7 @@
     final List<Ref> deferredTags = new ArrayList<Ref>();
 
     for (Ref ref : refs.values()) {
-      if (ref.getName().startsWith(GitRepositoryManager.REFS_CACHE_AUTOMERGE)) {
+      if (ref.getName().startsWith(RefNames.REFS_CACHE_AUTOMERGE)) {
         continue;
       } else if (PatchSet.isRef(ref.getName())) {
         // Reference to a patch set is visible if the change is visible.
@@ -127,9 +129,10 @@
       RevWalk revWalk) throws ServiceMayNotContinueException {
     try {
       return filter(repository.getRefDatabase().getRefs(RefDatabase.ALL));
+    } catch (ServiceMayNotContinueException e) {
+      throw e;
     } catch (IOException e) {
-      ServiceMayNotContinueException ex =
-          new ServiceMayNotContinueException(e.getMessage());
+      ServiceMayNotContinueException ex = new ServiceMayNotContinueException();
       ex.initCause(e);
       throw ex;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
index d7a2cf4..6e274a1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
@@ -261,7 +261,7 @@
       // prefer to see tasks sorted in: done before running,
       // running before ready, ready before sleeping.
       //
-      DONE, CANCELLED, RUNNING, READY, SLEEPING, OTHER;
+      DONE, CANCELLED, RUNNING, READY, SLEEPING, OTHER
     }
 
     private final Runnable runnable;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
similarity index 69%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index 48f62b5..18df4c1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.git.strategy;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.Change;
@@ -22,8 +22,13 @@
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CommitMergeStatus;
+import com.google.gerrit.server.git.MergeException;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 
@@ -66,13 +71,13 @@
           // create the branch.
           //
           mergeTip = n;
-          n.statusCode = CommitMergeStatus.CLEAN_MERGE;
+          n.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
 
         } else if (n.getParentCount() == 0) {
           // Refuse to merge a root commit into an existing branch,
           // we cannot obtain a delta for the cherry-pick to apply.
           //
-          n.statusCode = CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT;
+          n.setStatusCode(CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT);
 
         } else if (n.getParentCount() == 1) {
           // If there is only one parent, a cherry-pick can be done by
@@ -83,9 +88,9 @@
           mergeTip = writeCherryPickCommit(mergeTip, n);
 
           if (mergeTip != null) {
-            newCommits.put(mergeTip.patchsetId.getParentKey(), mergeTip);
+            newCommits.put(mergeTip.getPatchsetId().getParentKey(), mergeTip);
           } else {
-            n.statusCode = CommitMergeStatus.PATH_CONFLICT;
+            n.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
           }
 
         } else {
@@ -117,30 +122,30 @@
           }
         }
 
-      } catch (IOException e) {
-        throw new MergeException("Cannot merge " + n.name(), e);
-      } catch (OrmException e) {
+      } catch (NoSuchChangeException | IOException | OrmException e) {
         throw new MergeException("Cannot merge " + n.name(), e);
       }
     }
     return mergeTip;
   }
 
-  private CodeReviewCommit writeCherryPickCommit(final CodeReviewCommit mergeTip, final CodeReviewCommit n)
-      throws IOException, OrmException {
+  private CodeReviewCommit writeCherryPickCommit(CodeReviewCommit mergeTip,
+      CodeReviewCommit n) throws IOException, OrmException,
+      NoSuchChangeException {
 
     args.rw.parseBody(n);
 
-    final PatchSetApproval submitAudit =
-        args.mergeUtil.getSubmitter(n.change.currentPatchSetId());
+    final PatchSetApproval submitAudit = args.mergeUtil.getSubmitter(n);
 
-    PersonIdent cherryPickCommitterIdent = null;
+    IdentifiedUser cherryPickUser;
+    PersonIdent cherryPickCommitterIdent;
     if (submitAudit != null) {
-      cherryPickCommitterIdent =
-          args.identifiedUserFactory.create(submitAudit.getAccountId())
-              .newCommitterIdent(submitAudit.getGranted(),
-                  args.myIdent.getTimeZone());
+      cherryPickUser =
+          args.identifiedUserFactory.create(submitAudit.getAccountId());
+      cherryPickCommitterIdent = cherryPickUser.newCommitterIdent(
+          submitAudit.getGranted(), args.myIdent.getTimeZone());
     } else {
+      cherryPickUser = args.identifiedUserFactory.create(n.change().getOwner());
       cherryPickCommitterIdent = args.myIdent;
     }
 
@@ -156,38 +161,50 @@
     }
 
     PatchSet.Id id =
-        ChangeUtil.nextPatchSetId(args.repo, n.change.currentPatchSetId());
+        ChangeUtil.nextPatchSetId(args.repo, n.change().currentPatchSetId());
     final PatchSet ps = new PatchSet(id);
     ps.setCreatedOn(TimeUtil.nowTs());
     ps.setUploader(submitAudit.getAccountId());
     ps.setRevision(new RevId(newCommit.getId().getName()));
-    insertAncestors(args.db, ps.getId(), newCommit);
-    args.db.patchSets().insert(Collections.singleton(ps));
 
-    n.change.setCurrentPatchSet(patchSetInfoFactory.get(newCommit, ps.getId()));
-    args.db.changes().update(Collections.singletonList(n.change));
+    final RefUpdate ru;
 
-    final List<PatchSetApproval> approvals = Lists.newArrayList();
-    for (PatchSetApproval a : args.mergeUtil.getApprovalsForCommit(n)) {
-      approvals.add(new PatchSetApproval(ps.getId(), a));
-    }
-    args.db.patchSetApprovals().insert(approvals);
+    args.db.changes().beginTransaction(n.change().getId());
+    try {
+      insertAncestors(args.db, ps.getId(), newCommit);
+      args.db.patchSets().insert(Collections.singleton(ps));
+      n.change()
+          .setCurrentPatchSet(patchSetInfoFactory.get(newCommit, ps.getId()));
+      args.db.changes().update(Collections.singletonList(n.change()));
 
-    final RefUpdate ru = args.repo.updateRef(ps.getRefName());
-    ru.setExpectedOldObjectId(ObjectId.zeroId());
-    ru.setNewObjectId(newCommit);
-    ru.disableRefLog();
-    if (ru.update(args.rw) != RefUpdate.Result.NEW) {
-      throw new IOException(String.format("Failed to create ref %s in %s: %s",
-          ps.getRefName(), n.change.getDest().getParentKey().get(),
-          ru.getResult()));
+      final List<PatchSetApproval> approvals = Lists.newArrayList();
+      for (PatchSetApproval a
+          : args.approvalsUtil.byPatchSet(args.db, n.notes(), n.getPatchsetId())) {
+        approvals.add(new PatchSetApproval(ps.getId(), a));
+      }
+      args.db.patchSetApprovals().insert(approvals);
+
+      ru = args.repo.updateRef(ps.getRefName());
+      ru.setExpectedOldObjectId(ObjectId.zeroId());
+      ru.setNewObjectId(newCommit);
+      ru.disableRefLog();
+      if (ru.update(args.rw) != RefUpdate.Result.NEW) {
+        throw new IOException(String.format(
+            "Failed to create ref %s in %s: %s", ps.getRefName(), n.change()
+                .getDest().getParentKey().get(), ru.getResult()));
+      }
+
+      args.db.commit();
+    } finally {
+      args.db.rollback();
     }
 
-    gitRefUpdated.fire(n.change.getProject(), ru);
+    gitRefUpdated.fire(n.change().getProject(), ru);
 
     newCommit.copyFrom(n);
-    newCommit.statusCode = CommitMergeStatus.CLEAN_PICK;
-    newCommits.put(newCommit.patchsetId.getParentKey(), newCommit);
+    newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
+    newCommit.setControl(args.changeControlFactory.controlFor(n.change(), cherryPickUser));
+    newCommits.put(newCommit.getPatchsetId().getParentKey(), newCommit);
     setRefLogIdent(submitAudit);
     return newCommit;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/FastForwardOnly.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
similarity index 86%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/FastForwardOnly.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
index 7adf4d5..0b18c0f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/FastForwardOnly.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOnly.java
@@ -12,9 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.git.strategy;
 
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CommitMergeStatus;
+import com.google.gerrit.server.git.MergeException;
 
 import java.util.List;
 
@@ -33,7 +36,7 @@
 
     while (!toMerge.isEmpty()) {
       final CodeReviewCommit n = toMerge.remove(0);
-      n.statusCode = CommitMergeStatus.NOT_FAST_FORWARD;
+      n.setStatusCode(CommitMergeStatus.NOT_FAST_FORWARD);
     }
 
     final PatchSetApproval submitApproval =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
similarity index 92%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
index f729135..dc0d721 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.git.strategy;
 
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.MergeException;
 
 import java.util.List;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
similarity index 93%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
index d764db7..601c6f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.git.strategy;
 
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.MergeException;
 
 import java.util.List;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
similarity index 75%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index 598d9d6..a97b3fd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -12,17 +12,21 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.git;
-
-import static com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
+package com.google.gerrit.server.git.strategy;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy;
 import com.google.gerrit.server.changedetail.PathConflictException;
 import com.google.gerrit.server.changedetail.RebaseChange;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CommitMergeStatus;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.RebaseSorter;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
@@ -36,14 +40,17 @@
 import java.util.Map;
 
 public class RebaseIfNecessary extends SubmitStrategy {
-
+  private final PatchSetInfoFactory patchSetInfoFactory;
   private final RebaseChange rebaseChange;
   private final Map<Change.Id, CodeReviewCommit> newCommits;
   private final PersonIdent committerIdent;
 
-  RebaseIfNecessary(final SubmitStrategy.Arguments args,
-      final RebaseChange rebaseChange, PersonIdent committerIdent) {
+  RebaseIfNecessary(SubmitStrategy.Arguments args,
+      PatchSetInfoFactory patchSetInfoFactory,
+      RebaseChange rebaseChange,
+      PersonIdent committerIdent) {
     super(args);
+    this.patchSetInfoFactory = patchSetInfoFactory;
     this.rebaseChange = rebaseChange;
     this.newCommits = new HashMap<Change.Id, CodeReviewCommit>();
     this.committerIdent = committerIdent;
@@ -63,32 +70,33 @@
         // create the branch.
         //
         newMergeTip = n;
-        n.statusCode = CommitMergeStatus.CLEAN_MERGE;
+        n.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
 
       } else if (n.getParentCount() == 0) {
         // Refuse to merge a root commit into an existing branch,
         // we cannot obtain a delta for the rebase to apply.
         //
-        n.statusCode = CommitMergeStatus.CANNOT_REBASE_ROOT;
+        n.setStatusCode(CommitMergeStatus.CANNOT_REBASE_ROOT);
 
       } else if (n.getParentCount() == 1) {
         if (args.mergeUtil.canFastForward(
             args.mergeSorter, newMergeTip, args.rw, n)) {
           newMergeTip = n;
-          n.statusCode = CommitMergeStatus.CLEAN_MERGE;
+          n.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
 
         } else {
           try {
-            final IdentifiedUser uploader =
-                args.identifiedUserFactory.create(
-                    args.mergeUtil.getSubmitter(n.patchsetId).getAccountId());
+            final IdentifiedUser uploader = args.identifiedUserFactory.create(
+                args.mergeUtil.getSubmitter(n).getAccountId());
             final PatchSet newPatchSet =
                 rebaseChange.rebase(args.repo, args.rw, args.inserter,
-                    n.patchsetId, n.change, uploader,
+                    n.getPatchsetId(), n.change(), uploader,
                     newMergeTip, args.mergeUtil, committerIdent,
                     false, false, ValidatePolicy.NONE);
+
             List<PatchSetApproval> approvals = Lists.newArrayList();
-            for (PatchSetApproval a : args.mergeUtil.getApprovalsForCommit(n)) {
+            for (PatchSetApproval a : args.approvalsUtil.byPatchSet(
+                args.db, n.notes(), n.getPatchsetId())) {
               approvals.add(new PatchSetApproval(newPatchSet.getId(), a));
             }
             // rebaseChange.rebase() may already have copied some approvals,
@@ -97,22 +105,18 @@
             newMergeTip =
                 (CodeReviewCommit) args.rw.parseCommit(ObjectId
                     .fromString(newPatchSet.getRevision().get()));
+            n.change().setCurrentPatchSet(
+                patchSetInfoFactory.get(newMergeTip, newPatchSet.getId()));
             newMergeTip.copyFrom(n);
-            newMergeTip.patchsetId = newPatchSet.getId();
-            newMergeTip.change =
-                args.db.changes().get(newPatchSet.getId().getParentKey());
-            newMergeTip.statusCode = CommitMergeStatus.CLEAN_REBASE;
+            newMergeTip.setControl(args.changeControlFactory.controlFor(n.change(), uploader));
+            newMergeTip.setPatchsetId(newPatchSet.getId());
+            newMergeTip.setStatusCode(CommitMergeStatus.CLEAN_REBASE);
             newCommits.put(newPatchSet.getId().getParentKey(), newMergeTip);
-            setRefLogIdent(args.mergeUtil.getSubmitter(n.patchsetId));
+            setRefLogIdent(args.mergeUtil.getSubmitter(n));
           } catch (PathConflictException e) {
-            n.statusCode = CommitMergeStatus.PATH_CONFLICT;
-          } catch (NoSuchChangeException e) {
-            throw new MergeException("Cannot rebase " + n.name(), e);
-          } catch (OrmException e) {
-            throw new MergeException("Cannot rebase " + n.name(), e);
-          } catch (IOException e) {
-            throw new MergeException("Cannot rebase " + n.name(), e);
-          } catch (InvalidChangeOperationException e) {
+            n.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
+          } catch (NoSuchChangeException | OrmException | IOException
+              | InvalidChangeOperationException e) {
             throw new MergeException("Cannot rebase " + n.name(), e);
           }
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
similarity index 81%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index 9626e23..a0cd5fd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -12,19 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.git.strategy;
 
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeSorter;
+import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.project.ChangeControl;
 
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
@@ -48,6 +54,7 @@
     protected final IdentifiedUser.GenericFactory identifiedUserFactory;
     protected final PersonIdent myIdent;
     protected final ReviewDb db;
+    protected final ChangeControl.GenericFactory changeControlFactory;
 
     protected final Repository repo;
     protected final RevWalk rw;
@@ -55,19 +62,22 @@
     protected final RevFlag canMergeFlag;
     protected final Set<RevCommit> alreadyAccepted;
     protected final Branch.NameKey destBranch;
+    protected final ApprovalsUtil approvalsUtil;
     protected final MergeUtil mergeUtil;
     protected final ChangeIndexer indexer;
     protected final MergeSorter mergeSorter;
 
     Arguments(final IdentifiedUser.GenericFactory identifiedUserFactory,
-        final PersonIdent myIdent, final ReviewDb db, final Repository repo,
-        final RevWalk rw, final ObjectInserter inserter,
+        final PersonIdent myIdent, final ReviewDb db,
+        final ChangeControl.GenericFactory changeControlFactory,
+        final Repository repo, final RevWalk rw, final ObjectInserter inserter,
         final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
-        final Branch.NameKey destBranch, final MergeUtil mergeUtil,
-        final ChangeIndexer indexer) {
+        final Branch.NameKey destBranch, final ApprovalsUtil approvalsUtil,
+        final MergeUtil mergeUtil, final ChangeIndexer indexer) {
       this.identifiedUserFactory = identifiedUserFactory;
       this.myIdent = myIdent;
       this.db = db;
+      this.changeControlFactory = changeControlFactory;
 
       this.repo = repo;
       this.rw = rw;
@@ -75,6 +85,7 @@
       this.canMergeFlag = canMergeFlag;
       this.alreadyAccepted = alreadyAccepted;
       this.destBranch = destBranch;
+      this.approvalsUtil = approvalsUtil;
       this.mergeUtil = mergeUtil;
       this.indexer = indexer;
       this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
@@ -125,8 +136,8 @@
    * @param mergeTip the mergeTip
    * @param toMerge the commit for which it should be checked whether it can be
    *        merged or not
-   * @return <code>true</code> if the given commit can be merged, otherwise
-   *         <code>false</code>
+   * @return {@code true} if the given commit can be merged, otherwise
+   *         {@code false}
    * @throws MergeException
    */
   public abstract boolean dryRun(CodeReviewCommit mergeTip,
@@ -140,7 +151,7 @@
    * Do only call this method after the {@link #run(CodeReviewCommit, List)}
    * method has been invoked.
    *
-   * @return the ref log identity, may be <code>null</code>
+   * @return the ref log identity, may be {@code null}
    */
   public final PersonIdent getRefLogIdent() {
     return refLogIdent;
@@ -164,13 +175,13 @@
 
   /**
    * Returns whether a merge that failed with
-   * {@link RefUpdate.Result#LOCK_FAILURE} should be retried.
+   * {@link Result#LOCK_FAILURE} should be retried.
    *
    * May be overwritten by subclasses.
    *
-   * @return <code>true</code> if a merge that failed with
-   *         {@link RefUpdate.Result#LOCK_FAILURE} should be retried, otherwise
-   *         <code>false</code>
+   * @return {@code true} if a merge that failed with
+   *         {@link Result#LOCK_FAILURE} should be retried, otherwise
+   *         {@code false}
    */
   public boolean retryOnLockFailure() {
     return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
similarity index 84%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index 701a925..d23cc82 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -12,24 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.git;
+package com.google.gerrit.server.git.strategy;
 
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.changedetail.RebaseChange;
-import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -49,10 +50,12 @@
 
   private final IdentifiedUser.GenericFactory identifiedUserFactory;
   private final PersonIdent myIdent;
+  private final ChangeControl.GenericFactory changeControlFactory;
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final GitReferenceUpdated gitRefUpdated;
   private final RebaseChange rebaseChange;
   private final ProjectCache projectCache;
+  private final ApprovalsUtil approvalsUtil;
   private final MergeUtil.Factory mergeUtilFactory;
   private final ChangeIndexer indexer;
 
@@ -60,18 +63,21 @@
   SubmitStrategyFactory(
       final IdentifiedUser.GenericFactory identifiedUserFactory,
       @GerritPersonIdent final PersonIdent myIdent,
+      final ChangeControl.GenericFactory changeControlFactory,
       final PatchSetInfoFactory patchSetInfoFactory,
-      @CanonicalWebUrl @Nullable final Provider<String> urlProvider,
       final GitReferenceUpdated gitRefUpdated, final RebaseChange rebaseChange,
       final ProjectCache projectCache,
+      final ApprovalsUtil approvalsUtil,
       final MergeUtil.Factory mergeUtilFactory,
       final ChangeIndexer indexer) {
     this.identifiedUserFactory = identifiedUserFactory;
     this.myIdent = myIdent;
+    this.changeControlFactory = changeControlFactory;
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.gitRefUpdated = gitRefUpdated;
     this.rebaseChange = rebaseChange;
     this.projectCache = projectCache;
+    this.approvalsUtil = approvalsUtil;
     this.mergeUtilFactory = mergeUtilFactory;
     this.indexer = indexer;
   }
@@ -83,8 +89,9 @@
       throws MergeException, NoSuchProjectException {
     ProjectState project = getProject(destBranch);
     final SubmitStrategy.Arguments args =
-        new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db, repo,
-            rw, inserter, canMergeFlag, alreadyAccepted, destBranch,
+        new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db,
+            changeControlFactory, repo, rw, inserter, canMergeFlag,
+            alreadyAccepted, destBranch,approvalsUtil,
             mergeUtilFactory.create(project), indexer);
     switch (submitType) {
       case CHERRY_PICK:
@@ -96,7 +103,8 @@
       case MERGE_IF_NECESSARY:
         return new MergeIfNecessary(args);
       case REBASE_IF_NECESSARY:
-        return new RebaseIfNecessary(args, rebaseChange, myIdent);
+        return new RebaseIfNecessary(
+            args, patchSetInfoFactory, rebaseChange, myIdent);
       default:
         final String errorMsg = "No submit strategy for: " + submitType;
         log.error(errorMsg);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationException.java
index 45278f9..029096e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidationException.java
@@ -14,10 +14,12 @@
 
 package com.google.gerrit.server.git.validators;
 
+import com.google.gerrit.server.validators.ValidationException;
+
 import java.util.Collections;
 import java.util.List;
 
-public class CommitValidationException extends Exception {
+public class CommitValidationException extends ValidationException {
   private static final long serialVersionUID = 1L;
   private final List<CommitValidationMessage> messages;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 342a615..1bf754a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -18,12 +18,12 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.events.CommitReceivedEvent;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.git.ReceiveCommits;
 import com.google.gerrit.server.git.ValidationError;
@@ -186,10 +186,19 @@
 
       if (idList.isEmpty()) {
         if (projectControl.getProjectState().isRequireChangeID()) {
-          String errMsg = "missing Change-Id in commit message footer";
-          messages.add(getFixedCommitMsgWithChangeId(
-              errMsg, receiveEvent.commit));
-          throw new CommitValidationException(errMsg, messages);
+          String shortMsg = receiveEvent.commit.getShortMessage();
+          String changeIdPrefix = CHANGE_ID.getName() + ":";
+          if (shortMsg.startsWith(changeIdPrefix)
+              && shortMsg.substring(changeIdPrefix.length()).trim()
+                  .matches("^I[0-9a-f]{8,}.*$")) {
+            throw new CommitValidationException(
+                "missing subject; Change-Id must be in commit message footer");
+          } else {
+            String errMsg = "missing Change-Id in commit message footer";
+            messages.add(getFixedCommitMsgWithChangeId(
+                errMsg, receiveEvent.commit));
+            throw new CommitValidationException(errMsg, messages);
+          }
         }
       } else if (idList.size() > 1) {
         throw new CommitValidationException(
@@ -204,7 +213,7 @@
           throw new CommitValidationException(errMsg, messages);
         }
       }
-      return Collections.<CommitValidationMessage>emptyList();
+      return Collections.emptyList();
     }
 
     /**
@@ -311,7 +320,7 @@
         CommitReceivedEvent receiveEvent) throws CommitValidationException {
       IdentifiedUser currentUser = (IdentifiedUser) refControl.getCurrentUser();
 
-      if (GitRepositoryManager.REF_CONFIG.equals(refControl.getRefName())) {
+      if (RefNames.REFS_CONFIG.equals(refControl.getRefName())) {
         List<CommitValidationMessage> messages =
             new LinkedList<CommitValidationMessage>();
 
@@ -335,7 +344,7 @@
               messages);
         }
       }
-      return Collections.<CommitValidationMessage>emptyList();
+      return Collections.emptyList();
     }
   }
 
@@ -355,7 +364,7 @@
           && !refControl.canUploadMerges()) {
         throw new CommitValidationException("you are not allowed to upload merges");
       }
-      return Collections.<CommitValidationMessage>emptyList();
+      return Collections.emptyList();
     }
   }
 
@@ -420,7 +429,7 @@
               "not Signed-off-by author/committer/uploader in commit message footer");
         }
       }
-      return Collections.<CommitValidationMessage>emptyList();
+      return Collections.emptyList();
     }
   }
 
@@ -450,7 +459,7 @@
             currentUser, canonicalWebUrl));
         throw new CommitValidationException("invalid author", messages);
       }
-      return Collections.<CommitValidationMessage>emptyList();
+      return Collections.emptyList();
     }
   }
 
@@ -480,7 +489,7 @@
             currentUser, canonicalWebUrl));
         throw new CommitValidationException("invalid committer", messages);
       }
-      return Collections.<CommitValidationMessage>emptyList();
+      return Collections.emptyList();
     }
   }
 
@@ -511,7 +520,7 @@
           && !refControl.canForgeGerritServerIdentity()) {
         throw new CommitValidationException("do not amend merges not made by you");
       }
-      return Collections.<CommitValidationMessage>emptyList();
+      return Collections.emptyList();
     }
   }
 
@@ -519,9 +528,9 @@
       PersonIdent who, IdentifiedUser currentUser, String canonicalWebUrl) {
     StringBuilder sb = new StringBuilder();
     sb.append("\n");
-    sb.append("ERROR:  In commit " + c.name() + "\n");
-    sb.append("ERROR:  " + type + " email address " + who.getEmailAddress()
-        + "\n");
+    sb.append("ERROR:  In commit ").append(c.name()).append("\n");
+    sb.append("ERROR:  ").append(type).append(" email address ")
+      .append(who.getEmailAddress()).append("\n");
     sb.append("ERROR:  does not match your user account.\n");
     sb.append("ERROR:\n");
     if (currentUser.getEmailAddresses().isEmpty()) {
@@ -529,14 +538,14 @@
     } else {
       sb.append("ERROR:  The following addresses are currently registered:\n");
       for (String address : currentUser.getEmailAddresses()) {
-        sb.append("ERROR:    " + address + "\n");
+        sb.append("ERROR:    ").append(address).append("\n");
       }
     }
     sb.append("ERROR:\n");
     if (canonicalWebUrl != null) {
       sb.append("ERROR:  To register an email address, please visit:\n");
-      sb.append("ERROR:  " + canonicalWebUrl + "#" + PageLinks.SETTINGS_CONTACT
-          + "\n");
+      sb.append("ERROR:  ").append(canonicalWebUrl).append("#")
+        .append(PageLinks.SETTINGS_CONTACT).append("\n");
     }
     sb.append("\n");
     return new CommitValidationMessage(sb.toString(), false);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationException.java
index 78819a8..bfad0e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidationException.java
@@ -15,8 +15,9 @@
 package com.google.gerrit.server.git.validators;
 
 import com.google.gerrit.server.git.CommitMergeStatus;
+import com.google.gerrit.server.validators.ValidationException;
 
-public class MergeValidationException extends Exception {
+public class MergeValidationException extends ValidationException {
   private static final long serialVersionUID = 1L;
   private final CommitMergeStatus status;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
index 6eed909..76998b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -14,27 +14,32 @@
 
 package com.google.gerrit.server.git.validators;
 
-import static com.google.gerrit.server.git.MergeUtil.getSubmitter;
-
 import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.DynamicMap.Entry;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.CommitMergeStatus;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Repository;
 
+import java.io.IOException;
 import java.util.List;
 
 public class MergeValidators {
@@ -74,6 +79,8 @@
     private final ReviewDb db;
     private final ProjectCache projectCache;
     private final IdentifiedUser.GenericFactory identifiedUserFactory;
+    private final ApprovalsUtil approvalsUtil;
+    private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
 
     public interface Factory {
       ProjectConfigValidator create();
@@ -82,11 +89,15 @@
     @Inject
     public ProjectConfigValidator(AllProjectsName allProjectsName,
         ReviewDb db, ProjectCache projectCache,
-        IdentifiedUser.GenericFactory iuf) {
+        IdentifiedUser.GenericFactory iuf,
+        ApprovalsUtil approvalsUtil,
+        DynamicMap<ProjectConfigEntry> pluginConfigEntries) {
       this.allProjectsName = allProjectsName;
       this.db = db;
       this.projectCache = projectCache;
       this.identifiedUserFactory = iuf;
+      this.approvalsUtil = approvalsUtil;
+      this.pluginConfigEntries = pluginConfigEntries;
     }
 
     @Override
@@ -96,44 +107,67 @@
         final Branch.NameKey destBranch,
         final PatchSet.Id patchSetId)
         throws MergeValidationException {
-      if (GitRepositoryManager.REF_CONFIG.equals(destBranch.get())) {
+      if (RefNames.REFS_CONFIG.equals(destBranch.get())) {
         final Project.NameKey newParent;
         try {
           ProjectConfig cfg =
               new ProjectConfig(destProject.getProject().getNameKey());
           cfg.load(repo, commit);
           newParent = cfg.getProject().getParent(allProjectsName);
-        } catch (Exception e) {
-          throw new MergeValidationException(CommitMergeStatus.
-              INVALID_PROJECT_CONFIGURATION);
-        }
-        final Project.NameKey oldParent =
-            destProject.getProject().getParent(allProjectsName);
-        if (oldParent == null) {
-          // update of the 'All-Projects' project
-          if (newParent != null) {
-            throw new MergeValidationException(CommitMergeStatus.
-                INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT);
-          }
-        } else {
-          if (!oldParent.equals(newParent)) {
-            final PatchSetApproval psa = getSubmitter(db, patchSetId);
-            if (psa == null) {
+          final Project.NameKey oldParent =
+              destProject.getProject().getParent(allProjectsName);
+          if (oldParent == null) {
+            // update of the 'All-Projects' project
+            if (newParent != null) {
               throw new MergeValidationException(CommitMergeStatus.
-                  SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN);
+                  INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT);
             }
-            final IdentifiedUser submitter =
-                identifiedUserFactory.create(psa.getAccountId());
-            if (!submitter.getCapabilities().canAdministrateServer()) {
+          } else {
+            if (!oldParent.equals(newParent)) {
+              PatchSetApproval psa =
+                  approvalsUtil.getSubmitter(db, commit.notes(), patchSetId);
+              if (psa == null) {
+                throw new MergeValidationException(CommitMergeStatus.
+                    SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN);
+              }
+              final IdentifiedUser submitter =
+                  identifiedUserFactory.create(psa.getAccountId());
+              if (!submitter.getCapabilities().canAdministrateServer()) {
+                throw new MergeValidationException(CommitMergeStatus.
+                    SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN);
+              }
+
+              if (projectCache.get(newParent) == null) {
+                throw new MergeValidationException(CommitMergeStatus.
+                    INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND);
+              }
+            }
+          }
+
+          for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+            PluginConfig pluginCfg = cfg.getPluginConfig(e.getPluginName());
+            ProjectConfigEntry configEntry = e.getProvider().get();
+
+            String value = pluginCfg.getString(e.getExportName());
+            String oldValue = destProject.getConfig()
+                .getPluginConfig(e.getPluginName())
+                .getString(e.getExportName());
+
+            if ((value == null ? oldValue != null : !value.equals(oldValue)) &&
+                !configEntry.isEditable(destProject)) {
               throw new MergeValidationException(CommitMergeStatus.
-                  SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN);
+                  INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_EDITABLE);
             }
 
-            if (projectCache.get(newParent) == null) {
+            if (ProjectConfigEntry.Type.LIST.equals(configEntry.getType())
+                && value != null && !configEntry.getPermittedValues().contains(value)) {
               throw new MergeValidationException(CommitMergeStatus.
-                  INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND);
+                  INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_PERMITTED);
             }
           }
+        } catch (ConfigInvalidException | IOException e) {
+          throw new MergeValidationException(CommitMergeStatus.
+              INVALID_PROJECT_CONFIGURATION);
         }
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
index 195ea1c..c5273ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
@@ -20,7 +20,6 @@
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -48,7 +47,7 @@
     @DefaultInput
     String _oneGroup;
 
-    List<String> groups;
+    public List<String> groups;
 
     public static Input fromGroups(List<String> groups) {
       Input in = new Input();
@@ -87,7 +86,7 @@
 
   @Override
   public List<GroupInfo> apply(GroupResource resource, Input input)
-      throws MethodNotAllowedException, AuthException, BadRequestException,
+      throws MethodNotAllowedException, AuthException,
       UnprocessableEntityException, OrmException {
     AccountGroup group = resource.toAccountGroup();
     if (group == null) {
@@ -149,7 +148,7 @@
 
     @Override
     public GroupInfo apply(GroupResource resource, Input input)
-        throws MethodNotAllowedException, AuthException, BadRequestException,
+        throws AuthException, MethodNotAllowedException,
         UnprocessableEntityException, OrmException {
       AddIncludedGroups.Input in = new AddIncludedGroups.Input();
       in.groups = ImmutableList.of(id);
@@ -173,8 +172,8 @@
     }
 
     @Override
-    public Object apply(IncludedGroupResource resource,
-        PutIncludedGroup.Input input) throws MethodNotAllowedException, OrmException {
+    public GroupInfo apply(IncludedGroupResource resource,
+        PutIncludedGroup.Input input) throws OrmException {
       // Do nothing, the group is already included.
       return get.get().apply(resource);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
index 5ebf504..8ec6a3f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
@@ -200,7 +200,7 @@
     }
 
     @Override
-    public Object apply(GroupResource resource, PutMember.Input input)
+    public AccountInfo apply(GroupResource resource, PutMember.Input input)
         throws AuthException, MethodNotAllowedException,
         UnprocessableEntityException, OrmException {
       AddMembers.Input in = new AddMembers.Input();
@@ -225,7 +225,7 @@
     }
 
     @Override
-    public Object apply(MemberResource resource, PutMember.Input input)
+    public AccountInfo apply(MemberResource resource, PutMember.Input input)
         throws OrmException {
       // Do nothing, the user is already a member.
       return get.get().apply(resource);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
index 6914cf2..4ca0200 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
@@ -22,8 +22,10 @@
 import com.google.gerrit.common.errors.NameAlreadyUsedException;
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
@@ -31,10 +33,13 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.CreateGroupArgs;
 import com.google.gerrit.server.account.PerformCreateGroup;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.group.CreateGroup.Input;
 import com.google.gerrit.server.group.GroupJson.GroupInfo;
+import com.google.gerrit.server.validators.GroupCreationValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -45,15 +50,15 @@
 import java.util.Collections;
 
 @RequiresCapability(GlobalCapability.CREATE_GROUP)
-class CreateGroup implements RestModifyView<TopLevelResource, Input> {
-  static class Input {
-    String name;
-    String description;
-    Boolean visibleToAll;
-    String ownerId;
+public class CreateGroup implements RestModifyView<TopLevelResource, Input> {
+  public static class Input {
+    public String name;
+    public String description;
+    public Boolean visibleToAll;
+    public String ownerId;
   }
 
-  static interface Factory {
+  public static interface Factory {
     CreateGroup create(@Assisted String name);
   }
 
@@ -61,17 +66,20 @@
   private final GroupsCollection groups;
   private final PerformCreateGroup.Factory op;
   private final GroupJson json;
+  private final DynamicSet<GroupCreationValidationListener> groupCreationValidationListeners;
   private final boolean defaultVisibleToAll;
   private final String name;
 
   @Inject
   CreateGroup(Provider<IdentifiedUser> self, GroupsCollection groups,
       PerformCreateGroup.Factory performCreateGroupFactory, GroupJson json,
+      DynamicSet<GroupCreationValidationListener> groupCreationValidationListeners,
       @GerritServerConfig Config cfg, @Assisted String name) {
     this.self = self;
     this.groups = groups;
     this.op = performCreateGroupFactory;
     this.json = json;
+    this.groupCreationValidationListeners = groupCreationValidationListeners;
     this.defaultVisibleToAll = cfg.getBoolean("groups", "newGroupsVisibleToAll", false);
     this.name = name;
   }
@@ -79,7 +87,7 @@
   @Override
   public GroupInfo apply(TopLevelResource resource, Input input)
       throws AuthException, BadRequestException, UnprocessableEntityException,
-      NameAlreadyUsedException, OrmException {
+      ResourceConflictException, OrmException {
     if (input == null) {
       input = new Input();
     }
@@ -90,17 +98,28 @@
     AccountGroup.Id ownerId = owner(input);
     AccountGroup group;
     try {
-      group = op.create().createGroup(
-          name,
-          Strings.emptyToNull(input.description),
-          Objects.firstNonNull(input.visibleToAll, defaultVisibleToAll),
-          ownerId,
-          ownerId == null
-            ? Collections.singleton(self.get().getAccountId())
-            : Collections.<Account.Id> emptySet(),
-          null);
+      CreateGroupArgs args = new CreateGroupArgs();
+      args.setGroupName(name);
+      args.groupDescription = Strings.emptyToNull(input.description);
+      args.visibleToAll = Objects.firstNonNull(input.visibleToAll, defaultVisibleToAll);
+      args.ownerGroupId = ownerId;
+      args.initialMembers = ownerId == null
+          ? Collections.singleton(self.get().getAccountId())
+          : Collections.<Account.Id> emptySet();
+
+      for (GroupCreationValidationListener l : groupCreationValidationListeners) {
+        try {
+          l.validateNewGroup(args);
+        } catch (ValidationException e) {
+          throw new ResourceConflictException(e.getMessage(), e);
+        }
+      }
+
+      group = op.create(args).createGroup();
     } catch (PermissionDeniedException e) {
       throw new AuthException(e.getMessage());
+    } catch (NameAlreadyUsedException e) {
+      throw new ResourceConflictException(e.getMessage());
     }
     return json.format(GroupDescriptions.forAccountGroup(group));
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
index c5d95b0..04dcda3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -59,8 +58,8 @@
   }
 
   @Override
-  public Object apply(GroupResource resource, Input input)
-      throws MethodNotAllowedException, AuthException, BadRequestException,
+  public Response<?> apply(GroupResource resource, Input input)
+      throws AuthException, MethodNotAllowedException,
       UnprocessableEntityException, OrmException {
     AccountGroup internalGroup = resource.toAccountGroup();
     if (internalGroup == null) {
@@ -143,8 +142,8 @@
     }
 
     @Override
-    public Object apply(IncludedGroupResource resource, Input input)
-        throws MethodNotAllowedException, AuthException, BadRequestException,
+    public Response<?> apply(IncludedGroupResource resource, Input input)
+        throws AuthException, MethodNotAllowedException,
         UnprocessableEntityException, OrmException {
       AddIncludedGroups.Input in = new AddIncludedGroups.Input();
       in.groups = ImmutableList.of(resource.getMember().get());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
index 186d4b6..fd1b8f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
@@ -16,7 +16,6 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -57,7 +56,7 @@
   }
 
   @Override
-  public Object apply(GroupResource resource, Input input)
+  public Response<?> apply(GroupResource resource, Input input)
       throws AuthException, MethodNotAllowedException,
       UnprocessableEntityException, OrmException {
     AccountGroup internalGroup = resource.toAccountGroup();
@@ -141,9 +140,9 @@
     }
 
     @Override
-    public Object apply(MemberResource resource, Input input)
+    public Response<?> apply(MemberResource resource, Input input)
         throws AuthException, MethodNotAllowedException,
-        UnprocessableEntityException, OrmException, NoSuchGroupException {
+        UnprocessableEntityException, OrmException {
       AddMembers.Input in = new AddMembers.Input();
       in._oneMember = resource.getMember().getAccountId().toString();
       return delete.get().apply(resource, in);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupModule.java
new file mode 100644
index 0000000..7fb5f58
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupModule.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.group;
+
+import static com.google.inject.Scopes.SINGLETON;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.InternalUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.IncludingGroupMembership;
+import com.google.gerrit.server.account.InternalGroupBackend;
+import com.google.gerrit.server.account.UniversalGroupBackend;
+import com.google.gerrit.server.config.FactoryModule;
+
+public class GroupModule extends FactoryModule {
+
+  @Override
+  protected void configure() {
+    factory(InternalUser.Factory.class);
+    factory(IncludingGroupMembership.Factory.class);
+
+    bind(GroupBackend.class).to(UniversalGroupBackend.class).in(SINGLETON);
+    DynamicSet.setOf(binder(), GroupBackend.class);
+
+    bind(InternalGroupBackend.class).in(SINGLETON);
+    DynamicSet.bind(binder(), GroupBackend.class).to(SystemGroupBackend.class);
+    DynamicSet.bind(binder(), GroupBackend.class).to(InternalGroupBackend.class);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupsCollection.java
index 91da748..7f9fe77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupsCollection.java
@@ -84,7 +84,7 @@
       throw new ResourceNotFoundException(id);
     }
 
-    GroupDescription.Basic group = _parse(id.get());
+    GroupDescription.Basic group = parseId(id.get());
     if (group == null) {
       throw new ResourceNotFoundException(id.get());
     }
@@ -106,7 +106,7 @@
    */
   public GroupDescription.Basic parse(String id)
       throws UnprocessableEntityException {
-    GroupDescription.Basic group = _parse(id);
+    GroupDescription.Basic group = parseId(id);
     if (group == null || !groupControlFactory.controlFor(group).isVisible()) {
       throw new UnprocessableEntityException(String.format(
           "Group Not Found: %s", id));
@@ -135,7 +135,15 @@
     return group;
   }
 
-  private GroupDescription.Basic _parse(String id) {
+  /**
+   * Parses a group ID and returns the group without making any permission
+   * check whether the current user can see the group.
+   *
+   * @param id ID of the group, can be a group UUID, a group name or a legacy
+   *        group ID
+   * @return the group, null if no group is found for the given group ID
+   */
+  public GroupDescription.Basic parseId(String id) {
     AccountGroup.UUID uuid = new AccountGroup.UUID(id);
     if (groupBackend.handles(uuid)) {
       GroupDescription.Basic d = groupBackend.get(uuid);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
index 4764e41..f6b7187 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -23,9 +23,6 @@
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.common.groups.ListGroupsOption;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.extensions.restapi.Url;
@@ -75,9 +72,6 @@
   @Option(name = "--visible-to-all", usage = "to list only groups that are visible to all registered users")
   private boolean visibleToAll;
 
-  @Option(name = "--type", usage = "type of group")
-  private AccountGroup.Type groupType;
-
   @Option(name = "--user", aliases = {"-u"},
       usage = "user for which the groups should be listed")
   private Account.Id user;
@@ -102,7 +96,7 @@
   @Option(name = "-m", metaVar = "MATCH", usage = "match group substring")
   private String matchSubstring;
 
-  @Option(name = "-o", multiValued = true, usage = "Output options per group")
+  @Option(name = "-o", usage = "Output options per group")
   public void addOption(ListGroupsOption o) {
     options.add(o);
   }
@@ -138,8 +132,7 @@
   }
 
   @Override
-  public Object apply(TopLevelResource resource) throws AuthException,
-      BadRequestException, ResourceConflictException, Exception {
+  public Object apply(TopLevelResource resource) throws OrmException {
     final Map<String, GroupInfo> output = Maps.newTreeMap();
     for (GroupInfo info : get()) {
       output.put(Objects.firstNonNull(
@@ -240,8 +233,7 @@
           continue;
         }
       }
-      if ((visibleToAll && !group.isVisibleToAll())
-          || (groupType != null && !groupType.equals(group.getType()))) {
+      if (visibleToAll && !group.isVisibleToAll()) {
         continue;
       }
       if (!groupsToInspect.isEmpty()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
index e150ceb..c4b2ae2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
@@ -57,6 +57,10 @@
     this.accountLoader = accountLoaderFactory.create(true);
   }
 
+  public void setRecursive(boolean recursive) {
+    this.recursive = recursive;
+  }
+
   @Override
   public List<AccountInfo> apply(final GroupResource resource)
       throws MethodNotAllowedException, OrmException {
@@ -83,7 +87,7 @@
         return ComparisonChain.start()
             .compare(a.name, b.name, Ordering.natural().nullsFirst())
             .compare(a.email, b.email, Ordering.natural().nullsFirst())
-            .compare(a._account_id, b._account_id, Ordering.natural().nullsFirst()).result();
+            .compare(a._accountId, b._accountId, Ordering.natural().nullsFirst()).result();
       }
     });
     return memberInfos;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutDescription.java
index dcc0a76..663eb9d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutDescription.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.group;
 
 import com.google.common.base.Strings;
-import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -32,9 +31,9 @@
 import java.util.Collections;
 
 public class PutDescription implements RestModifyView<GroupResource, Input> {
-  static class Input {
+  public static class Input {
     @DefaultInput
-    String description;
+    public String description;
   }
 
   private final GroupCache groupCache;
@@ -47,8 +46,8 @@
   }
 
   @Override
-  public Object apply(GroupResource resource, Input input)
-      throws MethodNotAllowedException, AuthException, NoSuchGroupException,
+  public Response<String> apply(GroupResource resource, Input input)
+      throws AuthException, MethodNotAllowedException,
       ResourceNotFoundException, OrmException {
     if (input == null) {
       input = new Input(); // Delete would set description to null.
@@ -71,7 +70,7 @@
     groupCache.evict(group);
 
     return Strings.isNullOrEmpty(input.description)
-        ? Response.none()
-        : input.description;
+        ? Response.<String>none()
+        : Response.ok(input.description);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutGroup.java
index 9cbed6c..4d3d6be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutGroup.java
@@ -15,12 +15,13 @@
 package com.google.gerrit.server.group;
 
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.server.group.CreateGroup.Input;
 
 public class PutGroup implements RestModifyView<GroupResource, Input> {
   @Override
-  public Object apply(GroupResource resource, Input input)
+  public Response<?> apply(GroupResource resource, Input input)
       throws ResourceConflictException {
     throw new ResourceConflictException("Group already exists");
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java
index e18337a..447b666 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java
@@ -31,9 +31,9 @@
 import com.google.inject.Inject;
 
 public class PutName implements RestModifyView<GroupResource, Input> {
-  static class Input {
+  public static class Input {
     @DefaultInput
-    String name;
+    public String name;
   }
 
   private final PerformRenameGroup.Factory performRenameGroupFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOptions.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOptions.java
index 74060de..5139c34 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOptions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOptions.java
@@ -29,8 +29,8 @@
 import java.util.Collections;
 
 public class PutOptions implements RestModifyView<GroupResource, Input> {
-  static class Input {
-    Boolean visibleToAll;
+  public static class Input {
+    public Boolean visibleToAll;
   }
 
   private final GroupCache groupCache;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOwner.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOwner.java
index ca6c06f..737a74a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOwner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOwner.java
@@ -35,9 +35,9 @@
 import java.util.Collections;
 
 public class PutOwner implements RestModifyView<GroupResource, Input> {
-  static class Input {
+  public static class Input {
     @DefaultInput
-    String owner;
+    public String owner;
   }
 
   private final Provider<GroupsCollection> groupsCollection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
new file mode 100644
index 0000000..c888f73
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java
@@ -0,0 +1,155 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.group;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.account.ListGroupMembership;
+import com.google.gerrit.server.project.ProjectControl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+public class SystemGroupBackend implements GroupBackend {
+  /** Common UUID assigned to the "Anonymous Users" group. */
+  public static final AccountGroup.UUID ANONYMOUS_USERS =
+      new AccountGroup.UUID("global:Anonymous-Users");
+
+  /** Common UUID assigned to the "Registered Users" group. */
+  public static final AccountGroup.UUID REGISTERED_USERS =
+      new AccountGroup.UUID("global:Registered-Users");
+
+  /** Common UUID assigned to the "Project Owners" placeholder group. */
+  public static final AccountGroup.UUID PROJECT_OWNERS =
+      new AccountGroup.UUID("global:Project-Owners");
+
+  /** Common UUID assigned to the "Change Owner" placeholder group. */
+  public static final AccountGroup.UUID CHANGE_OWNER =
+      new AccountGroup.UUID("global:Change-Owner");
+
+  private static final SortedMap<String, GroupReference> names;
+  private static final ImmutableMap<AccountGroup.UUID, GroupReference> uuids;
+
+  static {
+    SortedMap<String, GroupReference> n = new TreeMap<>();
+    ImmutableMap.Builder<AccountGroup.UUID, GroupReference> u =
+        ImmutableMap.builder();
+    AccountGroup.UUID[] all = {
+        ANONYMOUS_USERS,
+        REGISTERED_USERS,
+        PROJECT_OWNERS,
+        CHANGE_OWNER,
+    };
+    for (AccountGroup.UUID uuid : all) {
+      int c = uuid.get().indexOf(':');
+      String name = uuid.get().substring(c + 1).replace('-', ' ');
+      GroupReference ref = new GroupReference(uuid, name);
+      n.put(ref.getName().toLowerCase(Locale.US), ref);
+      u.put(ref.getUUID(), ref);
+    }
+    names = Collections.unmodifiableSortedMap(n);
+    uuids = u.build();
+  }
+
+  public static boolean isSystemGroup(AccountGroup.UUID uuid) {
+    return uuid.get().startsWith("global:");
+  }
+
+  public static boolean isAnonymousOrRegistered(GroupReference ref) {
+    return isAnonymousOrRegistered(ref.getUUID());
+  }
+
+  public static boolean isAnonymousOrRegistered(AccountGroup.UUID uuid) {
+    return ANONYMOUS_USERS.equals(uuid) || REGISTERED_USERS.equals(uuid);
+  }
+
+  public static GroupReference getGroup(AccountGroup.UUID uuid) {
+    return checkNotNull(uuids.get(uuid), "group %s not found", uuid.get());
+  }
+
+  @Override
+  public boolean handles(AccountGroup.UUID uuid) {
+    return isSystemGroup(uuid);
+  }
+
+  @Override
+  public GroupDescription.Basic get(AccountGroup.UUID uuid) {
+    final GroupReference ref = getGroup(uuid);
+    if (ref != null) {
+      return new GroupDescription.Basic() {
+        @Override
+        public String getName() {
+          return ref.getName();
+        }
+
+        @Override
+        public AccountGroup.UUID getGroupUUID() {
+          return ref.getUUID();
+        }
+
+        @Override
+        public String getUrl() {
+          return null;
+        }
+
+        @Override
+        public String getEmailAddress() {
+          return null;
+        }
+      };
+    }
+    return null;
+  }
+
+  @Override
+  public Collection<GroupReference> suggest(String name, ProjectControl project) {
+    String nameLC = name.toLowerCase(Locale.US);
+    SortedMap<String, GroupReference> matches = names.tailMap(nameLC);
+    if (matches.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    List<GroupReference> r = new ArrayList<>(matches.size());
+    for (Map.Entry<String, GroupReference> e : matches.entrySet()) {
+      if (e.getKey().startsWith(nameLC)) {
+        r.add(e.getValue());
+      } else {
+        break;
+      }
+    }
+    return r;
+  }
+
+  @Override
+  public GroupMembership membershipsOf(IdentifiedUser user) {
+    return new ListGroupMembership(ImmutableSet.of(
+        ANONYMOUS_USERS,
+        REGISTERED_USERS));
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
index 710d26e..5ee240b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
@@ -27,16 +27,18 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.change.MergeabilityChecker;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MultiProgressMonitor;
 import com.google.gerrit.server.git.MultiProgressMonitor.Task;
 import com.google.gerrit.server.patch.PatchListLoader;
 import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.diff.DiffFormatter;
@@ -104,20 +106,26 @@
     }
   }
 
-  private final Provider<ReviewDb> db;
+  private final SchemaFactory<ReviewDb> schemaFactory;
+  private final ChangeData.Factory changeDataFactory;
   private final GitRepositoryManager repoManager;
   private final ListeningExecutorService executor;
   private final ChangeIndexer.Factory indexerFactory;
+  private final MergeabilityChecker mergeabilityChecker;
 
   @Inject
-  ChangeBatchIndexer(Provider<ReviewDb> db,
+  ChangeBatchIndexer(SchemaFactory<ReviewDb> schemaFactory,
+      ChangeData.Factory changeDataFactory,
       GitRepositoryManager repoManager,
       @IndexExecutor ListeningExecutorService executor,
-      ChangeIndexer.Factory indexerFactory) {
-    this.db = db;
+      ChangeIndexer.Factory indexerFactory,
+      @Nullable MergeabilityChecker mergeabilityChecker) {
+    this.schemaFactory = schemaFactory;
+    this.changeDataFactory = changeDataFactory;
     this.repoManager = repoManager;
     this.executor = executor;
     this.indexerFactory = indexerFactory;
+    this.mergeabilityChecker = mergeabilityChecker;
   }
 
   public Result indexAll(ChangeIndex index, Iterable<Project.NameKey> projects,
@@ -142,6 +150,9 @@
     final AtomicBoolean ok = new AtomicBoolean(true);
 
     for (final Project.NameKey project : projects) {
+      if (!updateMergeable(project)) {
+        ok.set(false);
+      }
       final ListenableFuture<?> future = executor.submit(reindexProject(
           indexerFactory.create(index), project, doneTask, failedTask,
           verboseWriter));
@@ -197,6 +208,18 @@
     return new Result(sw, ok.get(), doneTask.getCount(), failedTask.getCount());
   }
 
+  private boolean updateMergeable(Project.NameKey project) {
+    if (mergeabilityChecker != null) {
+      try {
+        mergeabilityChecker.newCheck().addProject(project).run();
+      } catch (IOException e) {
+        log.error("Error in mergeability checker", e);
+        return false;
+      }
+    }
+    return true;
+  }
+
   private Callable<Void> reindexProject(final ChangeIndexer indexer,
       final Project.NameKey project, final Task done, final Task failed,
       final PrintWriter verboseWriter) {
@@ -205,20 +228,25 @@
       public Void call() throws Exception {
         Multimap<ObjectId, ChangeData> byId = ArrayListMultimap.create();
         Repository repo = null;
+        ReviewDb db = null;
         try {
           repo = repoManager.openRepository(project);
           Map<String, Ref> refs = repo.getRefDatabase().getRefs(ALL);
-          for (Change c : db.get().changes().byProject(project)) {
+          db = schemaFactory.open();
+          for (Change c : db.changes().byProject(project)) {
             Ref r = refs.get(c.currentPatchSetId().toRefName());
             if (r != null) {
-              byId.put(r.getObjectId(), new ChangeData(c));
+              byId.put(r.getObjectId(), changeDataFactory.create(db, c));
             }
           }
           new ProjectIndexer(indexer, byId, repo, done, failed, verboseWriter)
               .call();
-        } catch(RepositoryNotFoundException rnfe) {
+        } catch (RepositoryNotFoundException rnfe) {
           log.error(rnfe.getMessage());
         } finally {
+          if (db != null) {
+            db.close();
+          }
           if (repo != null) {
             repo.close();
           }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index 290bd31..afa516f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.index;
 
+import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Account;
@@ -23,6 +24,7 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.ChangeStatusPredicate;
@@ -64,7 +66,7 @@
         @Override
         public String get(ChangeData input, FillArgs args)
             throws OrmException {
-          return input.change(args.db).getKey().get();
+          return input.change().getKey().get();
         }
       };
 
@@ -76,7 +78,7 @@
         public String get(ChangeData input, FillArgs args)
             throws OrmException {
           return ChangeStatusPredicate.VALUES.get(
-              input.change(args.db).getStatus());
+              input.change().getStatus());
         }
       };
 
@@ -87,7 +89,7 @@
         @Override
         public String get(ChangeData input, FillArgs args)
             throws OrmException {
-          return input.change(args.db).getProject().get();
+          return input.change().getProject().get();
         }
       };
 
@@ -98,7 +100,7 @@
         @Override
         public String get(ChangeData input, FillArgs args)
             throws OrmException {
-          return input.change(args.db).getDest().get();
+          return input.change().getDest().get();
         }
       };
 
@@ -109,18 +111,32 @@
         @Override
         public String get(ChangeData input, FillArgs args)
             throws OrmException {
-          return input.change(args.db).getTopic();
+          return input.change().getTopic();
         }
       };
 
+  // Same value as UPDATED, but implementations truncated to minutes.
+  @Deprecated
   /** Last update time since January 1, 1970. */
-  public static final FieldDef<ChangeData, Timestamp> UPDATED =
+  public static final FieldDef<ChangeData, Timestamp> LEGACY_UPDATED =
       new FieldDef.Single<ChangeData, Timestamp>(
           "updated", FieldType.TIMESTAMP, true) {
         @Override
         public Timestamp get(ChangeData input, FillArgs args)
             throws OrmException {
-          return input.change(args.db).getLastUpdatedOn();
+          return input.change().getLastUpdatedOn();
+        }
+      };
+
+
+  /** Last update time since January 1, 1970. */
+  public static final FieldDef<ChangeData, Timestamp> UPDATED =
+      new FieldDef.Single<ChangeData, Timestamp>(
+          "updated2", FieldType.TIMESTAMP, true) {
+        @Override
+        public Timestamp get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.change().getLastUpdatedOn();
         }
       };
 
@@ -140,7 +156,7 @@
         @Override
         public Long get(ChangeData input, FillArgs args)
             throws OrmException {
-          return legacyParseSortKey(input.change(args.db).getSortKey());
+          return legacyParseSortKey(input.change().getSortKey());
         }
       };
 
@@ -150,24 +166,48 @@
    * Redundant with {@link #UPDATED} and {@link #LEGACY_ID}, but secondary index
    * implementations may not be able to search over tuples of values.
    */
+  @Deprecated
   public static final FieldDef<ChangeData, Long> SORTKEY =
       new FieldDef.Single<ChangeData, Long>(
           "sortkey2", FieldType.LONG, true) {
         @Override
         public Long get(ChangeData input, FillArgs args)
             throws OrmException {
-          return ChangeUtil.parseSortKey(input.change(args.db).getSortKey());
+          return ChangeUtil.parseSortKey(input.change().getSortKey());
         }
       };
 
-  /** List of filenames modified in the current patch set. */
-  public static final FieldDef<ChangeData, Iterable<String>> FILE =
+  /** List of full file paths modified in the current patch set. */
+  public static final FieldDef<ChangeData, Iterable<String>> PATH =
       new FieldDef.Repeatable<ChangeData, String>(
-          ChangeQueryBuilder.FIELD_FILE, FieldType.EXACT, false) {
+          // Named for backwards compatibility.
+          "file", FieldType.EXACT, false) {
         @Override
         public Iterable<String> get(ChangeData input, FillArgs args)
             throws OrmException {
-          return input.currentFilePaths(args.db, args.patchListCache);
+          return input.currentFilePaths();
+        }
+      };
+
+  public static Set<String> getFileParts(ChangeData cd) throws OrmException {
+    Splitter s = Splitter.on('/').omitEmptyStrings();
+    Set<String> r = Sets.newHashSet();
+    for (String path : cd.currentFilePaths()) {
+      for (String part : s.split(path)) {
+        r.add(part);
+      }
+    }
+    return r;
+  }
+
+  /** Components of each file path modified in the current patch set. */
+  public static final FieldDef<ChangeData, Iterable<String>> FILE_PART =
+      new FieldDef.Repeatable<ChangeData, String>(
+          "filepart", FieldType.EXACT, false) {
+        @Override
+        public Iterable<String> get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return getFileParts(input);
         }
       };
 
@@ -178,7 +218,7 @@
         @Override
         public Integer get(ChangeData input, FillArgs args)
             throws OrmException {
-          return input.change(args.db).getOwner().get();
+          return input.change().getOwner().get();
         }
       };
 
@@ -190,7 +230,11 @@
         public Iterable<Integer> get(ChangeData input, FillArgs args)
             throws OrmException {
           Set<Integer> r = Sets.newHashSet();
-          for (PatchSetApproval a : input.allApprovals(args.db)) {
+          if (!args.allowsDrafts &&
+              input.change().getStatus() == Change.Status.DRAFT) {
+            return r;
+          }
+          for (PatchSetApproval a : input.approvals().values()) {
             r.add(a.getAccountId().get());
           }
           return r;
@@ -205,7 +249,7 @@
         public Iterable<String> get(ChangeData input, FillArgs args)
             throws OrmException {
           Set<String> revisions = Sets.newHashSet();
-          for (PatchSet ps : input.patches(args.db)) {
+          for (PatchSet ps : input.patches()) {
             if (ps.getRevision() != null) {
               revisions.add(ps.getRevision().get());
             }
@@ -222,9 +266,9 @@
         public Iterable<String> get(ChangeData input, FillArgs args)
             throws OrmException {
           try {
-            return args.trackingFooters.extract(
-                input.commitFooters(args.repoManager, args.db));
-          } catch (IOException e) {
+            return Sets.newHashSet(args.trackingFooters.extract(
+                input.commitFooters()).values());
+          } catch (NoSuchChangeException | IOException e) {
             throw new OrmException(e);
           }
         }
@@ -239,8 +283,8 @@
             throws OrmException {
           Set<String> allApprovals = Sets.newHashSet();
           Set<String> distinctApprovals = Sets.newHashSet();
-          for (PatchSetApproval a : input.currentApprovals(args.db)) {
-            if (a.getValue() != 0) {
+          for (PatchSetApproval a : input.currentApprovals()) {
+            if (a.getValue() != 0 && !a.isSubmit()) {
               allApprovals.add(formatLabel(a.getLabel(), a.getValue(),
                   a.getAccountId()));
               distinctApprovals.add(formatLabel(a.getLabel(), a.getValue()));
@@ -258,7 +302,7 @@
         @Override
         public String get(ChangeData input, FillArgs args)
             throws OrmException {
-          for (PatchSetApproval a : input.currentApprovals(args.db)) {
+          for (PatchSetApproval a : input.currentApprovals()) {
             if (a.getValue() != 0) {
               return "1";
             }
@@ -278,7 +322,7 @@
     @Override
     public byte[] get(ChangeData input, FieldDef.FillArgs args)
         throws OrmException {
-      return CODEC.encodeToByteArray(input.change(args.db));
+      return CODEC.encodeToByteArray(input.change());
     }
   }
 
@@ -297,7 +341,7 @@
     @Override
     public Iterable<byte[]> get(ChangeData input, FillArgs args)
         throws OrmException {
-      return toProtos(CODEC, input.currentApprovals(args.db));
+      return toProtos(CODEC, input.currentApprovals());
     }
   }
 
@@ -324,8 +368,8 @@
         @Override
         public String get(ChangeData input, FillArgs args) throws OrmException {
           try {
-            return input.commitMessage(args.repoManager, args.db);
-          } catch (IOException e) {
+            return input.commitMessage();
+          } catch (NoSuchChangeException | IOException e) {
             throw new OrmException(e);
           }
         }
@@ -339,16 +383,27 @@
         public Iterable<String> get(ChangeData input, FillArgs args)
             throws OrmException {
           Set<String> r = Sets.newHashSet();
-          for (PatchLineComment c : input.comments(args.db)) {
+          for (PatchLineComment c : input.comments()) {
             r.add(c.getMessage());
           }
-          for (ChangeMessage m : input.messages(args.db)) {
+          for (ChangeMessage m : input.messages()) {
             r.add(m.getMessage());
           }
           return r;
         }
       };
 
+  /** Whether the change is mergeable. */
+  public static final FieldDef<ChangeData, String> MERGEABLE =
+      new FieldDef.Single<ChangeData, String>(
+          ChangeQueryBuilder.FIELD_MERGEABLE, FieldType.EXACT, false) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.change().isMergeable() ? "1" : null;
+        }
+      };
+
   private static <T> List<byte[]> toProtos(ProtobufCodec<T> codec, Collection<T> objs)
       throws OrmException {
     List<byte[]> result = Lists.newArrayListWithCapacity(objs.size());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
index 397b044..a710a10 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndex.java
@@ -32,49 +32,6 @@
  * appropriate.
  */
 public interface ChangeIndex {
-  /** Instance indicating secondary index is disabled. */
-  public static final ChangeIndex DISABLED = new ChangeIndex() {
-    @Override
-    public Schema<ChangeData> getSchema() {
-      return null;
-    }
-
-    @Override
-    public void insert(ChangeData cd) throws IOException {
-      // Do nothing.
-    }
-
-    @Override
-    public void replace(ChangeData cd) throws IOException {
-      // Do nothing.
-    }
-
-    @Override
-    public void delete(ChangeData cd) throws IOException {
-      // Do nothing.
-    }
-
-    @Override
-    public void deleteAll() throws IOException {
-      // Do nothing.
-    }
-
-    @Override
-    public ChangeDataSource getSource(Predicate<ChangeData> p, int limit) {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void close() {
-      // Do nothing.
-    }
-
-    @Override
-    public void markReady(boolean ready) {
-      throw new UnsupportedOperationException();
-    }
-  };
-
   /** @return the schema version used by this index. */
   public Schema<ChangeData> getSchema();
 
@@ -109,7 +66,7 @@
   /**
    * Delete a change document from the index.
    *
-   * @param cd change document.
+   * @param cd change document
    *
    * @throws IOException
    */
@@ -134,6 +91,7 @@
    * @param p the predicate to match. Must be a tree containing only AND, OR,
    *     or NOT predicates as internal nodes, and {@link IndexPredicate}s as
    *     leaves.
+   * @param start offset in results list at which to start returning results.
    * @param limit maximum number of results to return.
    * @return a source of documents matching the predicate. Documents must be
    *     returned in descending sort key order, unless a {@code sortkey_after}
@@ -144,8 +102,8 @@
    * @throws QueryParseException if the predicate could not be converted to an
    *     indexed data source.
    */
-  public ChangeDataSource getSource(Predicate<ChangeData> p, int limit)
-      throws QueryParseException;
+  public ChangeDataSource getSource(Predicate<ChangeData> p, int start,
+      int limit) throws QueryParseException;
 
   /**
    * Mark whether this index is up-to-date and ready to serve reads.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
index ac9373d..437f559 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
@@ -15,16 +15,34 @@
 package com.google.gerrit.server.index;
 
 import com.google.common.base.Function;
-import com.google.common.util.concurrent.Callables;
+import com.google.common.util.concurrent.Atomics;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.OutOfScopeException;
+import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import com.google.inject.util.Providers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Helper for (re)indexing a change document.
@@ -32,30 +50,15 @@
  * Indexing is run in the background, as it may require substantial work to
  * compute some of the fields and/or update the index.
  */
-public abstract class ChangeIndexer {
+public class ChangeIndexer {
+  private static final Logger log =
+      LoggerFactory.getLogger(ChangeIndexer.class);
+
   public interface Factory {
     ChangeIndexer create(ChangeIndex index);
     ChangeIndexer create(IndexCollection indexes);
   }
 
-  /** Instance indicating secondary index is disabled. */
-  public static final ChangeIndexer DISABLED = new ChangeIndexer(null) {
-    @Override
-    public CheckedFuture<?, IOException> indexAsync(ChangeData cd) {
-      return Futures.immediateCheckedFuture(null);
-    }
-
-    @Override
-    protected Callable<?> indexTask(ChangeData cd) {
-      return Callables.returning(null);
-    }
-
-    @Override
-    protected Callable<?> deleteTask(ChangeData cd) {
-      return Callables.returning(null);
-    }
-  };
-
   private static final Function<Exception, IOException> MAPPER =
       new Function<Exception, IOException>() {
     @Override
@@ -71,121 +74,184 @@
     }
   };
 
+  private final IndexCollection indexes;
+  private final ChangeIndex index;
+  private final SchemaFactory<ReviewDb> schemaFactory;
+  private final ChangeData.Factory changeDataFactory;
+  private final ThreadLocalRequestContext context;
   private final ListeningExecutorService executor;
 
-  protected ChangeIndexer(ListeningExecutorService executor) {
+  @AssistedInject
+  ChangeIndexer(@IndexExecutor ListeningExecutorService executor,
+      SchemaFactory<ReviewDb> schemaFactory,
+      ChangeData.Factory changeDataFactory,
+      ThreadLocalRequestContext context,
+      @Assisted ChangeIndex index) {
     this.executor = executor;
+    this.schemaFactory = schemaFactory;
+    this.changeDataFactory = changeDataFactory;
+    this.context = context;
+    this.index = index;
+    this.indexes = null;
+  }
+
+  @AssistedInject
+  ChangeIndexer(@IndexExecutor ListeningExecutorService executor,
+      SchemaFactory<ReviewDb> schemaFactory,
+      ChangeData.Factory changeDataFactory,
+      ThreadLocalRequestContext context,
+      @Assisted IndexCollection indexes) {
+    this.executor = executor;
+    this.schemaFactory = schemaFactory;
+    this.changeDataFactory = changeDataFactory;
+    this.context = context;
+    this.index = null;
+    this.indexes = indexes;
   }
 
   /**
    * Start indexing a change.
    *
-   * @param change change to index.
+   * @param id change to index.
    * @return future for the indexing task.
    */
-  public CheckedFuture<?, IOException> indexAsync(Change change) {
-    return indexAsync(new ChangeData(change));
-  }
-
-  /**
-   * Start indexing a change.
-   *
-   * @param cd change to index.
-   * @return future for the indexing task.
-   */
-  public CheckedFuture<?, IOException> indexAsync(ChangeData cd) {
+  public CheckedFuture<?, IOException> indexAsync(Change.Id id) {
     return executor != null
-        ? submit(indexTask(cd))
+        ? submit(new Task(id, false))
         : Futures.<Object, IOException> immediateCheckedFuture(null);
   }
 
   /**
    * Synchronously index a change.
    *
-   * @param change change to index.
-   */
-  public void index(Change change) throws IOException {
-    index(new ChangeData(change));
-  }
-
-  /**
-   * Synchronously index a change.
-   *
    * @param cd change to index.
    */
   public void index(ChangeData cd) throws IOException {
-    try {
-      indexTask(cd).call();
-    } catch (RuntimeException e) {
-      throw e;
-    } catch (Exception e) {
-      throw MAPPER.apply(e);
+    for (ChangeIndex i : getWriteIndexes()) {
+      i.replace(cd);
     }
   }
 
   /**
-   * Create a runnable to index a change.
+   * Synchronously index a change.
    *
-   * @param cd change to index.
-   * @return unstarted runnable to index the change.
+   * @param change change to index.
+   * @param db review database.
    */
-  protected abstract Callable<?> indexTask(ChangeData cd);
-
-  /**
-   * Start deleting a change.
-   *
-   * @param change change to delete.
-   * @return future for the deleting task.
-   */
-  public CheckedFuture<?, IOException> deleteAsync(Change change) {
-    return deleteAsync(new ChangeData(change));
+  public void index(ReviewDb db, Change change) throws IOException {
+    index(changeDataFactory.create(db, change));
   }
 
   /**
    * Start deleting a change.
    *
-   * @param cd change to delete.
+   * @param id change to delete.
    * @return future for the deleting task.
    */
-  public CheckedFuture<?, IOException> deleteAsync(ChangeData cd) {
+  public CheckedFuture<?, IOException> deleteAsync(Change.Id id) {
     return executor != null
-        ? submit(deleteTask(cd))
+        ? submit(new Task(id, true))
         : Futures.<Object, IOException> immediateCheckedFuture(null);
   }
 
   /**
    * Synchronously delete a change.
    *
-   * @param change change to delete.
-   */
-  public void delete(Change change) throws IOException {
-    delete(new ChangeData(change));
-  }
-
-  /**
-   * Synchronously delete a change.
-   *
    * @param cd change to delete.
    */
   public void delete(ChangeData cd) throws IOException {
-    try {
-      deleteTask(cd).call();
-    } catch (RuntimeException e) {
-      throw e;
-    } catch (Exception e) {
-      throw MAPPER.apply(e);
+    for (ChangeIndex i : getWriteIndexes()) {
+      i.delete(cd);
     }
   }
 
   /**
-   * Create a runnable to delete a change.
+   * Synchronously delete a change.
    *
-   * @param cd change to delete.
-   * @return unstarted runnable to delete the change.
+   * @param change change to delete.
+   * @param db review database.
    */
-  protected abstract Callable<?> deleteTask(ChangeData cd);
+  public void delete(ReviewDb db, Change change) throws IOException {
+    delete(changeDataFactory.create(db, change));
+  }
+
+  private Collection<ChangeIndex> getWriteIndexes() {
+    return indexes != null
+        ? indexes.getWriteIndexes()
+        : Collections.singleton(index);
+  }
 
   private CheckedFuture<?, IOException> submit(Callable<?> task) {
     return Futures.makeChecked(executor.submit(task), MAPPER);
   }
+
+  private class Task implements Callable<Void> {
+    private final Change.Id id;
+    private final boolean delete;
+
+    private Task(Change.Id id, boolean delete) {
+      this.id = id;
+      this.delete = delete;
+    }
+
+    @Override
+    public Void call() throws Exception {
+      try {
+        final AtomicReference<Provider<ReviewDb>> dbRef =
+            Atomics.newReference();
+        RequestContext newCtx = new RequestContext() {
+          @Override
+          public Provider<ReviewDb> getReviewDbProvider() {
+            Provider<ReviewDb> db = dbRef.get();
+            if (db == null) {
+              try {
+                db = Providers.of(schemaFactory.open());
+              } catch (OrmException e) {
+                ProvisionException pe =
+                    new ProvisionException("error opening ReviewDb");
+                pe.initCause(e);
+                throw pe;
+              }
+              dbRef.set(db);
+            }
+            return db;
+          }
+
+          @Override
+          public CurrentUser getCurrentUser() {
+            throw new OutOfScopeException("No user during ChangeIndexer");
+          }
+        };
+        RequestContext oldCtx = context.setContext(newCtx);
+        try {
+          ChangeData cd = changeDataFactory.create(
+              newCtx.getReviewDbProvider().get(), id);
+          if (delete) {
+            for (ChangeIndex i : getWriteIndexes()) {
+              i.delete(cd);
+            }
+          } else {
+            for (ChangeIndex i : getWriteIndexes()) {
+              i.replace(cd);
+            }
+          }
+          return null;
+        } finally  {
+          context.setContext(oldCtx);
+          Provider<ReviewDb> db = dbRef.get();
+          if (db != null) {
+            db.get().close();
+          }
+        }
+      } catch (Exception e) {
+        log.error(String.format("Failed to index change %d", id.get()), e);
+        throw e;
+      }
+    }
+
+    @Override
+    public String toString() {
+      return "index-change-" + id.get();
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexerImpl.java
deleted file mode 100644
index 93d3f9f..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexerImpl.java
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.index;
-
-import com.google.common.util.concurrent.Atomics;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.util.RequestContext;
-import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.OutOfScopeException;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-import com.google.inject.assistedinject.Assisted;
-import com.google.inject.assistedinject.AssistedInject;
-import com.google.inject.util.Providers;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.concurrent.Callable;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Helper for (re)indexing a change document.
- * <p>
- * Indexing is run in the background, as it may require substantial work to
- * compute some of the fields and/or update the index.
- */
-public class ChangeIndexerImpl extends ChangeIndexer {
-  private static final Logger log =
-      LoggerFactory.getLogger(ChangeIndexerImpl.class);
-
-  private final IndexCollection indexes;
-  private final ChangeIndex index;
-  private final SchemaFactory<ReviewDb> schemaFactory;
-  private final ThreadLocalRequestContext context;
-
-  @AssistedInject
-  ChangeIndexerImpl(@IndexExecutor ListeningExecutorService executor,
-      SchemaFactory<ReviewDb> schemaFactory,
-      ThreadLocalRequestContext context,
-      @Assisted ChangeIndex index) {
-    super(executor);
-    this.schemaFactory = schemaFactory;
-    this.context = context;
-    this.index = index;
-    this.indexes = null;
-  }
-
-  @AssistedInject
-  ChangeIndexerImpl(@IndexExecutor ListeningExecutorService executor,
-      SchemaFactory<ReviewDb> schemaFactory,
-      ThreadLocalRequestContext context,
-      @Assisted IndexCollection indexes) {
-    super(executor);
-    this.schemaFactory = schemaFactory;
-    this.context = context;
-    this.index = null;
-    this.indexes = indexes;
-  }
-
-  @Override
-  protected Callable<Void> indexTask(ChangeData cd) {
-    return new Task(cd, false);
-  }
-
-  @Override
-  protected Callable<Void> deleteTask(ChangeData cd) {
-    return new Task(cd, true);
-  }
-
-  private class Task implements Callable<Void> {
-    private final ChangeData cd;
-    private final boolean delete;
-
-    private Task(ChangeData cd, boolean delete) {
-      this.cd = cd;
-      this.delete = delete;
-    }
-
-    @Override
-    public Void call() throws Exception {
-      try {
-        final AtomicReference<Provider<ReviewDb>> dbRef =
-            Atomics.newReference();
-        RequestContext oldCtx = context.setContext(new RequestContext() {
-          @Override
-          public Provider<ReviewDb> getReviewDbProvider() {
-            Provider<ReviewDb> db = dbRef.get();
-            if (db == null) {
-              try {
-                db = Providers.of(schemaFactory.open());
-              } catch (OrmException e) {
-                ProvisionException pe =
-                    new ProvisionException("error opening ReviewDb");
-                pe.initCause(e);
-                throw pe;
-              }
-              dbRef.set(db);
-            }
-            return db;
-          }
-
-          @Override
-          public CurrentUser getCurrentUser() {
-            throw new OutOfScopeException("No user during ChangeIndexer");
-          }
-        });
-        try {
-          if (indexes != null) {
-            for (ChangeIndex i : indexes.getWriteIndexes()) {
-              apply(i, cd);
-            }
-          } else {
-            apply(index, cd);
-          }
-          return null;
-        } finally  {
-          context.setContext(oldCtx);
-          Provider<ReviewDb> db = dbRef.get();
-          if (db != null) {
-            db.get().close();
-          }
-        }
-      } catch (Exception e) {
-        log.error(String.format(
-            "Failed to index change %d in %s",
-            cd.getId().get(), cd.getChange().getProject().get()), e);
-        throw e;
-      }
-    }
-
-    private void apply(ChangeIndex i, ChangeData cd) throws IOException {
-      if (delete) {
-        i.delete(cd);
-      } else {
-        i.replace(cd);
-      }
-    }
-
-    @Override
-    public String toString() {
-      return "index-change-" + cd.getId().get();
-    }
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index 0654e80..a46b22d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -30,7 +30,7 @@
 
 /** Secondary index schemas for changes. */
 public class ChangeSchemas {
-  @SuppressWarnings({"unchecked", "deprecation"})
+  @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V1 = release(
         ChangeField.LEGACY_ID,
         ChangeField.ID,
@@ -38,9 +38,9 @@
         ChangeField.PROJECT,
         ChangeField.REF,
         ChangeField.TOPIC,
-        ChangeField.UPDATED,
+        ChangeField.LEGACY_UPDATED,
         ChangeField.LEGACY_SORTKEY,
-        ChangeField.FILE,
+        ChangeField.PATH,
         ChangeField.OWNER,
         ChangeField.REVIEWER,
         ChangeField.COMMIT,
@@ -50,7 +50,7 @@
         ChangeField.COMMIT_MESSAGE,
         ChangeField.COMMENT);
 
-  @SuppressWarnings({"unchecked", "deprecation"})
+  @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V2 = release(
         ChangeField.LEGACY_ID,
         ChangeField.ID,
@@ -58,9 +58,9 @@
         ChangeField.PROJECT,
         ChangeField.REF,
         ChangeField.TOPIC,
-        ChangeField.UPDATED,
+        ChangeField.LEGACY_UPDATED,
         ChangeField.LEGACY_SORTKEY,
-        ChangeField.FILE,
+        ChangeField.PATH,
         ChangeField.OWNER,
         ChangeField.REVIEWER,
         ChangeField.COMMIT,
@@ -72,7 +72,7 @@
         ChangeField.CHANGE,
         ChangeField.APPROVAL);
 
-  @SuppressWarnings("unchecked")
+  @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V3 = release(
         ChangeField.LEGACY_ID,
         ChangeField.ID,
@@ -80,9 +80,9 @@
         ChangeField.PROJECT,
         ChangeField.REF,
         ChangeField.TOPIC,
-        ChangeField.UPDATED,
+        ChangeField.LEGACY_UPDATED,
         ChangeField.SORTKEY,
-        ChangeField.FILE,
+        ChangeField.PATH,
         ChangeField.OWNER,
         ChangeField.REVIEWER,
         ChangeField.COMMIT,
@@ -97,14 +97,89 @@
   // For upgrade to Lucene 4.4.0 index format only.
   static final Schema<ChangeData> V4 = release(V3.getFields().values());
 
+  @SuppressWarnings("deprecation")
+  static final Schema<ChangeData> V5 = release(
+        ChangeField.LEGACY_ID,
+        ChangeField.ID,
+        ChangeField.STATUS,
+        ChangeField.PROJECT,
+        ChangeField.REF,
+        ChangeField.TOPIC,
+        ChangeField.LEGACY_UPDATED,
+        ChangeField.SORTKEY,
+        ChangeField.PATH,
+        ChangeField.OWNER,
+        ChangeField.REVIEWER,
+        ChangeField.COMMIT,
+        ChangeField.TR,
+        ChangeField.LABEL,
+        ChangeField.REVIEWED,
+        ChangeField.COMMIT_MESSAGE,
+        ChangeField.COMMENT,
+        ChangeField.CHANGE,
+        ChangeField.APPROVAL,
+        ChangeField.MERGEABLE);
+
+  // For upgrade to Lucene 4.6.0 index format only.
+  static final Schema<ChangeData> V6 = release(V5.getFields().values());
+
+  @SuppressWarnings("deprecation")
+  static final Schema<ChangeData> V7 = release(
+        ChangeField.LEGACY_ID,
+        ChangeField.ID,
+        ChangeField.STATUS,
+        ChangeField.PROJECT,
+        ChangeField.REF,
+        ChangeField.TOPIC,
+        ChangeField.LEGACY_UPDATED,
+        ChangeField.SORTKEY,
+        ChangeField.FILE_PART,
+        ChangeField.PATH,
+        ChangeField.OWNER,
+        ChangeField.REVIEWER,
+        ChangeField.COMMIT,
+        ChangeField.TR,
+        ChangeField.LABEL,
+        ChangeField.REVIEWED,
+        ChangeField.COMMIT_MESSAGE,
+        ChangeField.COMMENT,
+        ChangeField.CHANGE,
+        ChangeField.APPROVAL,
+        ChangeField.MERGEABLE);
+
+  static final Schema<ChangeData> V8 = release(
+        ChangeField.LEGACY_ID,
+        ChangeField.ID,
+        ChangeField.STATUS,
+        ChangeField.PROJECT,
+        ChangeField.REF,
+        ChangeField.TOPIC,
+        ChangeField.UPDATED,
+        ChangeField.FILE_PART,
+        ChangeField.PATH,
+        ChangeField.OWNER,
+        ChangeField.REVIEWER,
+        ChangeField.COMMIT,
+        ChangeField.TR,
+        ChangeField.LABEL,
+        ChangeField.REVIEWED,
+        ChangeField.COMMIT_MESSAGE,
+        ChangeField.COMMENT,
+        ChangeField.CHANGE,
+        ChangeField.APPROVAL,
+        ChangeField.MERGEABLE);
+
+
   private static Schema<ChangeData> release(Collection<FieldDef<ChangeData, ?>> fields) {
     return new Schema<ChangeData>(true, fields);
   }
 
+  @SafeVarargs
   private static Schema<ChangeData> release(FieldDef<ChangeData, ?>... fields) {
     return release(Arrays.asList(fields));
   }
 
+  @SafeVarargs
   @SuppressWarnings("unused")
   private static Schema<ChangeData> developer(FieldDef<ChangeData, ?>... fields) {
     return new Schema<ChangeData>(false, Arrays.asList(fields));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
index 7bec5a5..d4f9966 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
@@ -14,13 +14,12 @@
 
 package com.google.gerrit.server.index;
 
-import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.TrackingFooters;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.Config;
 
 /**
  * Definition of a field stored in the secondary index.
@@ -58,20 +57,16 @@
 
   /** Arguments needed to fill in missing data in the input object. */
   public static class FillArgs {
-    final Provider<ReviewDb> db;
-    final GitRepositoryManager repoManager;
     final TrackingFooters trackingFooters;
-    final PatchListCache patchListCache;
+    final boolean allowsDrafts;
 
     @Inject
-    FillArgs(Provider<ReviewDb> db,
-        GitRepositoryManager repoManager,
-        TrackingFooters trackingFooters,
-        PatchListCache patchListCache) {
-      this.db = db;
-      this.repoManager = repoManager;
+    FillArgs(TrackingFooters trackingFooters,
+        @GerritServerConfig Config cfg) {
       this.trackingFooters = trackingFooters;
-      this.patchListCache = patchListCache;
+      this.allowsDrafts = cfg == null
+          ? true
+          : cfg.getBoolean("change", "allowDrafts", true);
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java
index 0c90e32..2558cb4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java
@@ -16,7 +16,6 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -39,18 +38,14 @@
     this.searchIndex = new AtomicReference<ChangeIndex>();
   }
 
-  /**
-   * @return the current search index version, or null if the secondary index is
-   *     disabled.
-   */
-  @Nullable
+  /** @return the current search index version. */
   public ChangeIndex getSearchIndex() {
     return searchIndex.get();
   }
 
   public void setSearchIndex(ChangeIndex index) {
     ChangeIndex old = searchIndex.getAndSet(index);
-    if (old != null && old != index) {
+    if (old != null && old != index && !writeIndexes.contains(old)) {
       old.close();
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index 2cfc93d..67d0fef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -19,13 +19,13 @@
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.query.change.BasicChangeRewrites;
 import com.google.gerrit.server.query.change.ChangeQueryRewriter;
 import com.google.inject.AbstractModule;
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
-import com.google.inject.assistedinject.FactoryModuleBuilder;
 
 import org.eclipse.jgit.lib.Config;
 
@@ -37,14 +37,14 @@
  */
 public class IndexModule extends LifecycleModule {
   public enum IndexType {
-    SQL, LUCENE, SOLR;
+    LUCENE, SOLR
   }
 
   /** Type of secondary index. */
   public static IndexType getIndexType(Injector injector) {
     Config cfg = injector.getInstance(
         Key.get(Config.class, GerritServerConfig.class));
-    return cfg.getEnum("index", null, "type", IndexType.SQL);
+    return cfg.getEnum("index", null, "type", IndexType.LUCENE);
   }
 
   private final int threads;
@@ -63,12 +63,10 @@
   @Override
   protected void configure() {
     bind(ChangeQueryRewriter.class).to(IndexRewriteImpl.class);
-    bind(IndexRewriteImpl.BasicRewritesImpl.class);
+    bind(BasicChangeRewrites.class);
     bind(IndexCollection.class);
     listener().to(IndexCollection.class);
-    install(new FactoryModuleBuilder()
-        .implement(ChangeIndexer.class, ChangeIndexerImpl.class)
-        .build(ChangeIndexer.Factory.class));
+    factory(ChangeIndexer.Factory.class);
 
     if (indexExecutor != null) {
       bind(ListeningExecutorService.class)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
index 2ba869d..345f5b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriteImpl.java
@@ -15,17 +15,16 @@
 package com.google.gerrit.server.index;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.query.AndPredicate;
 import com.google.gerrit.server.query.NotPredicate;
 import com.google.gerrit.server.query.OrPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryRewriter;
 import com.google.gerrit.server.query.change.AndSource;
 import com.google.gerrit.server.query.change.BasicChangeRewrites;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -33,9 +32,7 @@
 import com.google.gerrit.server.query.change.ChangeQueryRewriter;
 import com.google.gerrit.server.query.change.ChangeStatusPredicate;
 import com.google.gerrit.server.query.change.OrSource;
-import com.google.gerrit.server.query.change.SqlRewriterImpl;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 import java.util.BitSet;
 import java.util.EnumSet;
@@ -124,36 +121,31 @@
   }
 
   private final IndexCollection indexes;
-  private final Provider<ReviewDb> db;
-  private final BasicRewritesImpl basicRewrites;
-  private final SqlRewriterImpl sqlRewriter;
+  private final BasicChangeRewrites basicRewrites;
 
   @Inject
   IndexRewriteImpl(IndexCollection indexes,
-      Provider<ReviewDb> db,
-      BasicRewritesImpl basicRewrites,
-      SqlRewriterImpl sqlRewriter) {
+      BasicChangeRewrites basicRewrites) {
     this.indexes = indexes;
-    this.db = db;
     this.basicRewrites = basicRewrites;
-    this.sqlRewriter = sqlRewriter;
   }
 
   @Override
-  public Predicate<ChangeData> rewrite(Predicate<ChangeData> in)
+  public Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
       throws QueryParseException {
     ChangeIndex index = indexes.getSearchIndex();
-    if (index == null) {
-      return sqlRewriter.rewrite(in);
-    }
     in = basicRewrites.rewrite(in);
-    int limit = Math.max(1, ChangeQueryBuilder.hasLimit(in)
-        ? ChangeQueryBuilder.getLimit(in)
-        : MAX_LIMIT);
+    int limit =
+        Objects.firstNonNull(ChangeQueryBuilder.getLimit(in), MAX_LIMIT);
+    // Increase the limit rather than skipping, since we don't know how many
+    // skipped results would have been filtered out by the enclosing AndSource.
+    limit += start;
+    limit = Math.max(limit, 1);
+    limit = Math.min(limit, MAX_LIMIT);
 
     Predicate<ChangeData> out = rewriteImpl(in, index, limit);
     if (in == out || out instanceof IndexPredicate) {
-      return new IndexedChangeQuery(db, index, out, limit);
+      return new IndexedChangeQuery(index, out, limit);
     } else if (out == null /* cannot rewrite */) {
       return in;
     } else {
@@ -230,7 +222,7 @@
     if (isIndexed.cardinality() == 1) {
       int i = isIndexed.nextSetBit(0);
       newChildren.add(
-          0, new IndexedChangeQuery(db, index, newChildren.remove(i), limit));
+          0, new IndexedChangeQuery(index, newChildren.remove(i), limit));
       return copy(in, newChildren);
     }
 
@@ -250,7 +242,7 @@
         all.add(c);
       }
     }
-    all.add(0, new IndexedChangeQuery(db, index, in.copy(indexed), limit));
+    all.add(0, new IndexedChangeQuery(index, in.copy(indexed), limit));
     return copy(in, all);
   }
 
@@ -258,7 +250,7 @@
       Predicate<ChangeData> in,
       List<Predicate<ChangeData>> all) {
     if (in instanceof AndPredicate) {
-      return new AndSource(db, all);
+      return new AndSource(all);
     } else if (in instanceof OrPredicate) {
       return new OrSource(all);
     }
@@ -271,14 +263,4 @@
         || p instanceof OrPredicate
         || p instanceof NotPredicate);
   }
-
-  static class BasicRewritesImpl extends BasicChangeRewrites {
-    private static final QueryRewriter.Definition<ChangeData, BasicRewritesImpl> mydef =
-        new QueryRewriter.Definition<ChangeData, BasicRewritesImpl>(
-            BasicRewritesImpl.class, SqlRewriterImpl.BUILDER);
-    @Inject
-    BasicRewritesImpl(Provider<ReviewDb> db, IndexCollection indexes) {
-      super(mydef, db, indexes);
-    }
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
index 27b2f4b..09d66e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedChangeQuery.java
@@ -20,7 +20,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -29,7 +28,6 @@
 import com.google.gerrit.server.query.change.SortKeyPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
 
 import java.util.Collection;
 import java.util.Iterator;
@@ -80,20 +78,18 @@
     }
   }
 
-  private final Provider<ReviewDb> db;
   private final ChangeIndex index;
   private final int limit;
 
   private Predicate<ChangeData> pred;
   private ChangeDataSource source;
 
-  public IndexedChangeQuery(Provider<ReviewDb> db, ChangeIndex index,
-      Predicate<ChangeData> pred, int limit) throws QueryParseException {
-    this.db = db;
+  public IndexedChangeQuery(ChangeIndex index, Predicate<ChangeData> pred,
+      int limit) throws QueryParseException {
     this.index = index;
     this.limit = limit;
     this.pred = pred;
-    this.source = index.getSource(pred, limit);
+    this.source = index.getSource(pred, 0, limit);
   }
 
   @Override
@@ -168,9 +164,22 @@
 
   @Override
   public ResultSet<ChangeData> restart(ChangeData last) throws OrmException {
-    pred = replaceSortKeyPredicates(pred, last.change(db).getSortKey());
+    pred = replaceSortKeyPredicates(pred, last.change().getSortKey());
     try {
-      source = index.getSource(pred, limit);
+      source = index.getSource(pred, 0, limit);
+    } catch (QueryParseException e) {
+      // Don't need to show this exception to the user; the only thing that
+      // changed about pred was its SortKeyPredicates, and any other QPEs
+      // that might happen should have already thrown from the constructor.
+      throw new OrmException(e);
+    }
+    return read();
+  }
+
+  @Override
+  public ResultSet<ChangeData> restart(int start) throws OrmException {
+    try {
+      source = index.getSource(pred, start, limit);
     } catch (QueryParseException e) {
       // Don't need to show this exception to the user; the only thing that
       // changed about pred was its SortKeyPredicates, and any other QPEs
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/NoIndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/NoIndexModule.java
deleted file mode 100644
index 8c552d8..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/NoIndexModule.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.index;
-
-import com.google.gerrit.server.query.change.ChangeQueryRewriter;
-import com.google.gerrit.server.query.change.SqlRewriterImpl;
-import com.google.inject.AbstractModule;
-
-public class NoIndexModule extends AbstractModule {
-  // TODO(dborowitz): This module should go away when the index becomes
-  // obligatory, as should the interfaces that exist only to support the
-  // non-index case.
-
-  @Override
-  protected void configure() {
-    bind(ChangeIndex.class).toInstance(ChangeIndex.DISABLED);
-    bind(ChangeIndexer.class).toInstance(ChangeIndexer.DISABLED);
-    bind(ChangeQueryRewriter.class).to(SqlRewriterImpl.class);
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
index 198c7b0..b73674d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
@@ -18,4 +18,8 @@
   protected RegexPredicate(FieldDef<I, ?> def, String value) {
     super(def, value);
   }
+
+  protected RegexPredicate(FieldDef<I, ?> def, String name, String value) {
+    super(def, name, value);
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
index 62fba12..9277f6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
@@ -14,14 +14,53 @@
 
 package com.google.gerrit.server.index;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.gerrit.server.index.ChangeField.UPDATED;
+
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtjsonrpc.common.JavaSqlTimestampHelper;
+
 import java.sql.Timestamp;
+import java.util.Date;
 
 public abstract class TimestampRangePredicate<I> extends IndexPredicate<I> {
+  @SuppressWarnings({"deprecation", "unchecked"})
+  protected static FieldDef<ChangeData, Timestamp> updatedField(
+      Schema<ChangeData> schema) {
+    if (schema == null) {
+      return ChangeField.LEGACY_UPDATED;
+    }
+    FieldDef<ChangeData, ?> f = schema.getFields().get(UPDATED.getName());
+    if (f == null) {
+      f = schema.getFields().get(ChangeField.LEGACY_UPDATED.getName());
+      checkNotNull(f, "schema missing updated field, found: %s", schema);
+    }
+    checkArgument(f.getType() == FieldType.TIMESTAMP,
+        "expected %s to be TIMESTAMP, found %s", f.getName(), f.getType());
+    return (FieldDef<ChangeData, Timestamp>) f;
+  }
+
+  protected static Timestamp parse(String value) throws QueryParseException {
+    try {
+      return JavaSqlTimestampHelper.parseTimestamp(value);
+    } catch (IllegalArgumentException e) {
+      // parseTimestamp's errors are specific and helpful, so preserve them.
+      throw new QueryParseException(e.getMessage(), e);
+    }
+  }
+
   protected TimestampRangePredicate(FieldDef<I, Timestamp> def,
       String name, String value) {
     super(def, name, value);
   }
 
-  public abstract Timestamp getMinTimestamp();
-  public abstract Timestamp getMaxTimestamp();
+  public abstract Date getMinTimestamp();
+  public abstract Date getMaxTimestamp();
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 39d0ff8..4e51a73 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -22,11 +22,11 @@
 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.StarredChange;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.mail.ProjectWatch.Watchers;
+import com.google.gerrit.server.notedb.ReviewerState;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListEntry;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
@@ -56,19 +56,19 @@
   private static final Logger log = LoggerFactory.getLogger(ChangeEmail.class);
 
   protected final Change change;
+  protected final ChangeData changeData;
   protected PatchSet patchSet;
   protected PatchSetInfo patchSetInfo;
   protected ChangeMessage changeMessage;
 
   protected ProjectState projectState;
-  protected ChangeData changeData;
   protected Set<Account.Id> authors;
   protected boolean emailOnlyAuthors;
 
   protected ChangeEmail(EmailArguments ea, Change c, String mc) {
     super(ea, mc, c.getProject(), c.getDest());
     change = c;
-    changeData = new ChangeData(change);
+    changeData = ea.changeDataFactory.create(ea.db.get(), c);
     emailOnlyAuthors = false;
   }
 
@@ -98,14 +98,8 @@
     formatChange();
     appendText(velocifyFile("ChangeFooter.vm"));
     try {
-      HashSet<Account.Id> reviewers = new HashSet<Account.Id>();
-      for (PatchSetApproval p : args.db.get().patchSetApprovals().byChange(
-          change.getId())) {
-        reviewers.add(p.getAccountId());
-      }
-
       TreeSet<String> names = new TreeSet<String>();
-      for (Account.Id who : reviewers) {
+      for (Account.Id who : changeData.reviewers().values()) {
         names.add(getNameEmailFor(who));
       }
 
@@ -226,9 +220,9 @@
       StringBuilder detail = new StringBuilder();
 
       if (patchSetInfo != null) {
-        detail.append(patchSetInfo.getMessage().trim() + "\n");
+        detail.append(patchSetInfo.getMessage().trim()).append("\n");
       } else {
-        detail.append(change.getSubject().trim() + "\n");
+        detail.append(change.getSubject().trim()).append("\n");
       }
 
       if (patchSet != null) {
@@ -238,7 +232,8 @@
           if (Patch.COMMIT_MSG.equals(p.getNewName())) {
             continue;
           }
-          detail.append(p.getChangeType().getCode() + " " + p.getNewName() + "\n");
+          detail.append(p.getChangeType().getCode())
+                .append(" ").append(p.getNewName()).append("\n");
         }
         detail.append(MessageFormat.format("" //
             + "{0,choice,0#0 files|1#1 file|1<{0} files} changed, " //
@@ -309,31 +304,23 @@
 
   /** Any user who has published comments on this change. */
   protected void ccAllApprovals() {
-    ccApprovals(true);
+    try {
+      for (Account.Id id : changeData.reviewers().values()) {
+        add(RecipientType.CC, id);
+      }
+    } catch (OrmException err) {
+      log.warn("Cannot CC users that reviewed updated change", err);
+    }
   }
 
   /** Users who have non-zero approval codes on the change. */
   protected void ccExistingReviewers() {
-    ccApprovals(false);
-  }
-
-  private void ccApprovals(final boolean includeZero) {
     try {
-      // CC anyone else who has posted an approval mark on this change
-      //
-      for (PatchSetApproval ap : args.db.get().patchSetApprovals().byChange(
-          change.getId())) {
-        if (!includeZero && ap.getValue() == 0) {
-          continue;
-        }
-        add(RecipientType.CC, ap.getAccountId());
+      for (Account.Id id : changeData.reviewers().get(ReviewerState.REVIEWER)) {
+        add(RecipientType.CC, id);
       }
     } catch (OrmException err) {
-      if (includeZero) {
-        log.warn("Cannot CC users that commented on updated change", err);
-      } else {
-        log.warn("Cannot CC users that reviewed updated change", err);
-      }
+      log.warn("Cannot CC users that commented on updated change", err);
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index cd6409e..cb92b0f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -17,13 +17,13 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Ordering;
 import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.CommentRange;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.change.PostReview.NotifyHandling;
 import com.google.gerrit.server.patch.PatchFile;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommitMessageEditedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommitMessageEditedSender.java
deleted file mode 100644
index e388f16..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommitMessageEditedSender.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.mail;
-
-import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-public class CommitMessageEditedSender extends ReplacePatchSetSender {
-  public static interface Factory {
-    CommitMessageEditedSender create(Change change);
-  }
-
-  @Inject
-  public CommitMessageEditedSender(EmailArguments ea, @Assisted Change c) {
-    super(ea, c);
-  }
-
-  @Override
-  protected void formatChange() throws EmailException {
-    appendText(velocifyFile("CommitMessageEdited.vm"));
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
index d7dfd3d..66ea1e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.IdentifiedUser.GenericFactory;
 import com.google.gerrit.server.account.AccountCache;
@@ -27,9 +28,11 @@
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
 import com.google.inject.Inject;
@@ -46,11 +49,13 @@
   final GroupIncludeCache groupIncludes;
   final AccountCache accountCache;
   final PatchListCache patchListCache;
+  final ApprovalsUtil approvalsUtil;
   final FromAddressGenerator fromAddressGenerator;
   final EmailSender emailSender;
   final PatchSetInfoFactory patchSetInfoFactory;
   final IdentifiedUser.GenericFactory identifiedUserFactory;
   final CapabilityControl.Factory capabilityControlFactory;
+  final ChangeNotes.Factory changeNotesFactory;
   final AnonymousUser anonymousUser;
   final String anonymousCowardName;
   final Provider<String> urlProvider;
@@ -59,6 +64,7 @@
 
   final ChangeQueryBuilder.Factory queryBuilder;
   final Provider<ReviewDb> db;
+  final ChangeData.Factory changeDataFactory;
   final RuntimeInstance velocityRuntime;
   final EmailSettings settings;
 
@@ -66,16 +72,20 @@
   EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
       GroupBackend groupBackend, GroupIncludeCache groupIncludes,
       AccountCache accountCache,
-      PatchListCache patchListCache, FromAddressGenerator fromAddressGenerator,
+      PatchListCache patchListCache,
+      ApprovalsUtil approvalsUtil,
+      FromAddressGenerator fromAddressGenerator,
       EmailSender emailSender, PatchSetInfoFactory patchSetInfoFactory,
       GenericFactory identifiedUserFactory,
       CapabilityControl.Factory capabilityControlFactory,
+      ChangeNotes.Factory changeNotesFactory,
       AnonymousUser anonymousUser,
       @AnonymousCowardName String anonymousCowardName,
       @CanonicalWebUrl @Nullable Provider<String> urlProvider,
       AllProjectsName allProjectsName,
       ChangeQueryBuilder.Factory queryBuilder,
       Provider<ReviewDb> db,
+      ChangeData.Factory changeDataFactory,
       RuntimeInstance velocityRuntime,
       EmailSettings settings,
       @SshAdvertisedAddresses List<String> sshAddresses) {
@@ -85,17 +95,20 @@
     this.groupIncludes = groupIncludes;
     this.accountCache = accountCache;
     this.patchListCache = patchListCache;
+    this.approvalsUtil = approvalsUtil;
     this.fromAddressGenerator = fromAddressGenerator;
     this.emailSender = emailSender;
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.identifiedUserFactory = identifiedUserFactory;
     this.capabilityControlFactory = capabilityControlFactory;
+    this.changeNotesFactory = changeNotesFactory;
     this.anonymousUser = anonymousUser;
     this.anonymousCowardName = anonymousCowardName;
     this.urlProvider = urlProvider;
     this.allProjectsName = allProjectsName;
     this.queryBuilder = queryBuilder;
     this.db = db;
+    this.changeDataFactory = changeDataFactory;
     this.velocityRuntime = velocityRuntime;
     this.settings = settings;
     this.sshAddresses = sshAddresses;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
index ce8c8f9..6492a5e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
@@ -70,8 +70,7 @@
     final byte[] encoded = value.getBytes("UTF-8");
 
     r.append("=?UTF-8?Q?");
-    for (int i = 0; i < encoded.length; i++) {
-      byte b = encoded[i];
+    for (byte b : encoded) {
       if (b == ' ') {
         r.append('_');
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
index 998fc3b..058bbc8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
@@ -14,7 +14,8 @@
 
 package com.google.gerrit.server.mail;
 
-import com.google.common.base.Charsets;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.gerrit.common.data.ParameterizedString;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.GerritPersonIdent;
@@ -179,7 +180,7 @@
   private static String hashOf(String data) {
     try {
       MessageDigest hash = MessageDigest.getInstance("MD5");
-      byte[] bytes = hash.digest(data.getBytes(Charsets.UTF_8));
+      byte[] bytes = hash.digest(data.getBytes(UTF_8));
       return Base64.encodeBase64URLSafeString(bytes);
     } catch (NoSuchAlgorithmException e) {
       throw new RuntimeException("No MD5 available", e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
index f22c6e4..ac367ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailUtil.java
@@ -14,11 +14,12 @@
 
 package com.google.gerrit.server.mail;
 
+import com.google.common.collect.Multimap;
 import com.google.gerrit.common.errors.NoSuchAccountException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.notedb.ReviewerState;
 import com.google.gwtorm.server.OrmException;
 
 import org.eclipse.jgit.revwalk.FooterKey;
@@ -55,16 +56,11 @@
     return recipients;
   }
 
-  public static MailRecipients getRecipientsFromApprovals(
-      final List<PatchSetApproval> approvals) {
-    final MailRecipients recipients = new MailRecipients();
-    for (PatchSetApproval a : approvals) {
-      if (a.getValue() != 0) {
-        recipients.reviewers.add(a.getAccountId());
-      } else {
-        recipients.cc.add(a.getAccountId());
-      }
-    }
+  public static MailRecipients getRecipientsFromReviewers(
+      Multimap<ReviewerState, Account.Id> reviewers) {
+    MailRecipients recipients = new MailRecipients();
+    recipients.reviewers.addAll(reviewers.get(ReviewerState.REVIEWER));
+    recipients.cc.addAll(reviewers.get(ReviewerState.CC));
     return recipients;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
index 37d800d..07a5f9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -61,8 +61,8 @@
     try {
       Table<Account.Id, String, PatchSetApproval> pos = HashBasedTable.create();
       Table<Account.Id, String, PatchSetApproval> neg = HashBasedTable.create();
-      for (PatchSetApproval ca : args.db.get().patchSetApprovals()
-          .byPatchSet(patchSet.getId())) {
+      for (PatchSetApproval ca : args.approvalsUtil.byPatchSet(
+            args.db.get(), changeData.notes(), patchSet.getId())) {
         LabelType lt = labelTypes.byLabel(ca.getLabelId());
         if (lt == null) {
           continue;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
index 3484dd8..93fc063 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.mail;
 
-import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromApprovals;
 import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
 
 import com.google.gerrit.common.ChangeHooks;
@@ -23,7 +22,6 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 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.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
@@ -33,6 +31,8 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.MailUtil.MailRecipients;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gwtorm.server.OrmException;
@@ -81,9 +81,10 @@
     this.replacePatchSetFactory = replacePatchSetFactory;
   }
 
-  public void send(final boolean newChange,
-      final IdentifiedUser currentUser, final Change updatedChange,
-      final PatchSet updatedPatchSet, final LabelTypes labelTypes)
+  public void send(final ChangeNotes notes, final ChangeUpdate update,
+      final boolean newChange, final IdentifiedUser currentUser,
+      final Change updatedChange, final PatchSet updatedPatchSet,
+      final LabelTypes labelTypes)
       throws OrmException, IOException, PatchSetInfoNotAvailableException {
     final Repository git = repoManager.openRepository(updatedChange.getProject());
     try {
@@ -103,9 +104,9 @@
       recipients.remove(me);
 
       if (newChange) {
-        approvalsUtil.addReviewers(db, labelTypes,
-            updatedChange, updatedPatchSet, info,
-            recipients.getReviewers(), Collections.<Account.Id> emptySet());
+        approvalsUtil.addReviewers(db, update, labelTypes, updatedChange,
+            updatedPatchSet, info, recipients.getReviewers(),
+            Collections.<Account.Id> emptySet());
         try {
           CreateChangeSender cm = createChangeSenderFactory.create(updatedChange);
           cm.setFrom(me);
@@ -117,14 +118,9 @@
           log.error("Cannot send email for new change " + updatedChange.getId(), e);
         }
       } else {
-        final List<PatchSetApproval> patchSetApprovals =
-            db.patchSetApprovals().byChange(
-                updatedChange.getId()).toList();
-        final MailRecipients oldRecipients =
-            getRecipientsFromApprovals(patchSetApprovals);
-        approvalsUtil.addReviewers(db, labelTypes, updatedChange,
+        approvalsUtil.addReviewers(db, update, labelTypes, updatedChange,
             updatedPatchSet, info, recipients.getReviewers(),
-            oldRecipients.getAll());
+            approvalsUtil.getReviewers(db, notes).values());
         final ChangeMessage msg =
             new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
                 ChangeUtil.messageUUID(db)), me,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
index 757562c..04c3f9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
@@ -188,7 +188,6 @@
     }
   }
 
-  @SuppressWarnings("unchecked")
   private boolean filterMatch(CurrentUser user, String filter)
       throws OrmException, QueryParseException {
     ChangeQueryBuilder qb;
@@ -202,7 +201,6 @@
     }
 
     if (filter != null) {
-      qb.setAllowFileRegex(true);
       Predicate<ChangeData> filterPredicate = qb.parse(filter);
       if (p == null) {
         p = filterPredicate;
@@ -210,6 +208,6 @@
         p = Predicate.and(filterPredicate, p);
       }
     }
-    return p == null ? true : p.match(changeData);
+    return p == null || p.match(changeData);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
index 48e2d03..f16cb5a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
@@ -56,7 +56,7 @@
   }
 
   public static enum Encryption {
-    NONE, SSL, TLS;
+    NONE, SSL, TLS
   }
 
   private final boolean enabled;
@@ -187,8 +187,9 @@
         for (Address addr : rcpt) {
           if (!client.addRecipient(addr.email)) {
             String error = client.getReplyString();
-            rejected.append("Server " + smtpHost + " rejected recipient "
-                + addr + ": " + error);
+            rejected.append("Server ").append(smtpHost)
+                    .append(" rejected recipient ").append(addr)
+                    .append(": ").append(error);
           }
         }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
new file mode 100644
index 0000000..7204735
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.RefNames;
+
+import org.eclipse.jgit.revwalk.FooterKey;
+
+public class ChangeNoteUtil {
+  static final String GERRIT_PLACEHOLDER_HOST = "gerrit";
+
+  static final FooterKey FOOTER_LABEL = new FooterKey("Label");
+  static final FooterKey FOOTER_PATCH_SET = new FooterKey("Patch-set");
+  static final FooterKey FOOTER_STATUS = new FooterKey("Status");
+  static final FooterKey FOOTER_SUBMITTED_WITH =
+      new FooterKey("Submitted-with");
+
+  public static String changeRefName(Change.Id id) {
+    StringBuilder r = new StringBuilder();
+    r.append(RefNames.REFS_CHANGES);
+    int n = id.get();
+    int m = n % 100;
+    if (m < 10) {
+      r.append('0');
+    }
+    r.append(m);
+    r.append('/');
+    r.append(n);
+    r.append("/meta");
+    return r.toString();
+  }
+
+  private ChangeNoteUtil() {
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
new file mode 100644
index 0000000..286bf4ad
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -0,0 +1,447 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Enums;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Ordering;
+import com.google.common.collect.Table;
+import com.google.common.collect.Tables;
+import com.google.common.primitives.Ints;
+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.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.VersionedMetaData;
+import com.google.gerrit.server.util.LabelVote;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterKey;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/** View of a single {@link Change} based on the log of its notes branch. */
+public class ChangeNotes extends VersionedMetaData {
+  private static final Ordering<PatchSetApproval> PSA_BY_TIME =
+      Ordering.natural().onResultOf(
+        new Function<PatchSetApproval, Timestamp>() {
+          @Override
+          public Timestamp apply(PatchSetApproval input) {
+            return input.getGranted();
+          }
+        });
+
+  @Singleton
+  public static class Factory {
+    private final GitRepositoryManager repoManager;
+
+    @VisibleForTesting
+    @Inject
+    public Factory(GitRepositoryManager repoManager) {
+      this.repoManager = repoManager;
+    }
+
+    public ChangeNotes create(Change change) {
+      return new ChangeNotes(repoManager, change);
+    }
+  }
+
+  private static class Parser {
+    private final Change.Id changeId;
+    private final ObjectId tip;
+    private final RevWalk walk;
+    private final Map<PatchSet.Id,
+        Table<Account.Id, String, Optional<PatchSetApproval>>> approvals;
+    private final Map<Account.Id, ReviewerState> reviewers;
+    private final List<SubmitRecord> submitRecords;
+    private Change.Status status;
+
+    private Parser(Change.Id changeId, ObjectId tip, RevWalk walk) {
+      this.changeId = changeId;
+      this.tip = tip;
+      this.walk = walk;
+      approvals = Maps.newHashMap();
+      reviewers = Maps.newLinkedHashMap();
+      submitRecords = Lists.newArrayListWithExpectedSize(1);
+    }
+
+    private void parseAll() throws ConfigInvalidException, IOException {
+      walk.markStart(walk.parseCommit(tip));
+      for (RevCommit commit : walk) {
+        parse(commit);
+      }
+      pruneReviewers();
+    }
+
+    private ImmutableListMultimap<PatchSet.Id, PatchSetApproval>
+        buildApprovals() {
+      Multimap<PatchSet.Id, PatchSetApproval> result =
+          ArrayListMultimap.create(approvals.keySet().size(), 3);
+      for (Table<?, ?, Optional<PatchSetApproval>> curr
+          : approvals.values()) {
+        for (PatchSetApproval psa : Optional.presentInstances(curr.values())) {
+          result.put(psa.getPatchSetId(), psa);
+        }
+      }
+      for (Collection<PatchSetApproval> v : result.asMap().values()) {
+        Collections.sort((List<PatchSetApproval>) v, PSA_BY_TIME);
+      }
+      return ImmutableListMultimap.copyOf(result);
+    }
+
+    private void parse(RevCommit commit) throws ConfigInvalidException {
+      if (status == null) {
+        status = parseStatus(commit);
+      }
+      PatchSet.Id psId = parsePatchSetId(commit);
+      Account.Id accountId = parseIdent(commit);
+
+      if (submitRecords.isEmpty()) {
+        // Only parse the most recent set of submit records; any older ones are
+        // still there, but not currently used.
+        parseSubmitRecords(commit.getFooterLines(FOOTER_SUBMITTED_WITH));
+      }
+
+      for (String line : commit.getFooterLines(FOOTER_LABEL)) {
+        parseApproval(psId, accountId, commit, line);
+      }
+
+      for (ReviewerState state : ReviewerState.values()) {
+        for (String line : commit.getFooterLines(state.getFooterKey())) {
+          parseReviewer(state, line);
+        }
+      }
+    }
+
+    private Change.Status parseStatus(RevCommit commit)
+        throws ConfigInvalidException {
+      List<String> statusLines = commit.getFooterLines(FOOTER_STATUS);
+      if (statusLines.isEmpty()) {
+        return null;
+      } else if (statusLines.size() > 1) {
+        throw expectedOneFooter(FOOTER_STATUS, statusLines);
+      }
+      Optional<Change.Status> status = Enums.getIfPresent(
+          Change.Status.class, statusLines.get(0).toUpperCase());
+      if (!status.isPresent()) {
+        throw invalidFooter(FOOTER_STATUS, statusLines.get(0));
+      }
+      return status.get();
+    }
+
+    private PatchSet.Id parsePatchSetId(RevCommit commit)
+        throws ConfigInvalidException {
+      List<String> psIdLines = commit.getFooterLines(FOOTER_PATCH_SET);
+      if (psIdLines.size() != 1) {
+        throw expectedOneFooter(FOOTER_PATCH_SET, psIdLines);
+      }
+      Integer psId = Ints.tryParse(psIdLines.get(0));
+      if (psId == null) {
+        throw invalidFooter(FOOTER_PATCH_SET, psIdLines.get(0));
+      }
+      return new PatchSet.Id(changeId, psId);
+    }
+
+    private void parseApproval(PatchSet.Id psId, Account.Id accountId,
+        RevCommit commit, String line) throws ConfigInvalidException {
+      Table<Account.Id, String, Optional<PatchSetApproval>> curr =
+          approvals.get(psId);
+      if (curr == null) {
+        curr = Tables.newCustomTable(
+            Maps.<Account.Id, Map<String, Optional<PatchSetApproval>>>
+                newHashMapWithExpectedSize(2),
+            new Supplier<Map<String, Optional<PatchSetApproval>>>() {
+              @Override
+              public Map<String, Optional<PatchSetApproval>> get() {
+                return Maps.newLinkedHashMap();
+              }
+            });
+        approvals.put(psId, curr);
+      }
+
+      if (line.startsWith("-")) {
+        String label = line.substring(1);
+        if (!curr.contains(accountId, label)) {
+          curr.put(accountId, label, Optional.<PatchSetApproval> absent());
+        }
+      } else {
+        LabelVote l;
+        try {
+          l = LabelVote.parseWithEquals(line);
+        } catch (IllegalArgumentException e) {
+          ConfigInvalidException pe =
+              parseException("invalid %s: %s", FOOTER_LABEL, line);
+          pe.initCause(e);
+          throw pe;
+        }
+        if (!curr.contains(accountId, l.getLabel())) {
+          curr.put(accountId, l.getLabel(), Optional.of(new PatchSetApproval(
+              new PatchSetApproval.Key(
+                  psId,
+                  accountId,
+                  new LabelId(l.getLabel())),
+              l.getValue(),
+              new Timestamp(commit.getCommitterIdent().getWhen().getTime()))));
+        }
+      }
+    }
+
+    private void parseSubmitRecords(List<String> lines)
+        throws ConfigInvalidException {
+      SubmitRecord rec = null;
+
+      for (String line : lines) {
+        int c = line.indexOf(": ");
+        if (c < 0) {
+          rec = new SubmitRecord();
+          submitRecords.add(rec);
+          int s = line.indexOf(' ');
+          String statusStr = s >= 0 ? line.substring(0, s) : line;
+          Optional<SubmitRecord.Status> status =
+              Enums.getIfPresent(SubmitRecord.Status.class, statusStr);
+          checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
+          rec.status = status.get();
+          if (s >= 0) {
+            rec.errorMessage = line.substring(s);
+          }
+        } else {
+          checkFooter(rec != null, FOOTER_SUBMITTED_WITH, line);
+          SubmitRecord.Label label = new SubmitRecord.Label();
+          if (rec.labels == null) {
+            rec.labels = Lists.newArrayList();
+          }
+          rec.labels.add(label);
+
+          Optional<SubmitRecord.Label.Status> status = Enums.getIfPresent(
+              SubmitRecord.Label.Status.class, line.substring(0, c));
+          checkFooter(status.isPresent(), FOOTER_SUBMITTED_WITH, line);
+          label.status = status.get();
+          int c2 = line.indexOf(": ", c + 2);
+          if (c2 >= 0) {
+            label.label = line.substring(c + 2, c2);
+            PersonIdent ident =
+                RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
+            checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
+            label.appliedBy = parseIdent(ident);
+          } else {
+            label.label = line.substring(c + 2);
+          }
+        }
+      }
+    }
+
+    private Account.Id parseIdent(RevCommit commit)
+        throws ConfigInvalidException {
+      return parseIdent(commit.getAuthorIdent());
+    }
+
+    private Account.Id parseIdent(PersonIdent ident)
+        throws ConfigInvalidException {
+      String email = ident.getEmailAddress();
+      int at = email.indexOf('@');
+      if (at >= 0) {
+        String host = email.substring(at + 1, email.length());
+        Integer id = Ints.tryParse(email.substring(0, at));
+        if (id != null && host.equals(GERRIT_PLACEHOLDER_HOST)) {
+          return new Account.Id(id);
+        }
+      }
+      throw parseException("invalid identity, expected <id>@%s: %s",
+        GERRIT_PLACEHOLDER_HOST, email);
+    }
+
+    private void parseReviewer(ReviewerState state, String line)
+        throws ConfigInvalidException {
+      PersonIdent ident = RawParseUtils.parsePersonIdent(line);
+      if (ident == null) {
+        throw invalidFooter(state.getFooterKey(), line);
+      }
+      Account.Id accountId = parseIdent(ident);
+      if (!reviewers.containsKey(accountId)) {
+        reviewers.put(accountId, state);
+      }
+    }
+
+    private void pruneReviewers() {
+      Iterator<Map.Entry<Account.Id, ReviewerState>> rit =
+          reviewers.entrySet().iterator();
+      while (rit.hasNext()) {
+        Map.Entry<Account.Id, ReviewerState> e = rit.next();
+        if (e.getValue() == ReviewerState.REMOVED) {
+          rit.remove();
+          for (Table<Account.Id, ?, ?> curr : approvals.values()) {
+            curr.rowKeySet().remove(e.getKey());
+          }
+        }
+      }
+    }
+
+    private ConfigInvalidException parseException(String fmt, Object... args) {
+      return new ConfigInvalidException("Change " + changeId + ": "
+          + String.format(fmt, args));
+    }
+
+    private ConfigInvalidException expectedOneFooter(FooterKey footer,
+        List<String> actual) {
+      return parseException("missing or multiple %s: %s",
+          footer.getName(), actual);
+    }
+
+    private ConfigInvalidException invalidFooter(FooterKey footer,
+        String actual) {
+      return parseException("invalid %s: %s", footer.getName(), actual);
+    }
+
+    private void checkFooter(boolean expr, FooterKey footer, String actual)
+        throws ConfigInvalidException {
+      if (!expr) {
+        throw invalidFooter(footer, actual);
+      }
+    }
+  }
+
+  private final GitRepositoryManager repoManager;
+  private final Change change;
+  private boolean loaded;
+  private ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals;
+  private ImmutableSetMultimap<ReviewerState, Account.Id> reviewers;
+  private ImmutableList<SubmitRecord> submitRecords;
+
+  @VisibleForTesting
+  ChangeNotes(GitRepositoryManager repoManager, Change change) {
+    this.repoManager = repoManager;
+    this.change = new Change(change);
+  }
+
+  // TODO(dborowitz): Wrap fewer exceptions if/when we kill gwtorm.
+  public ChangeNotes load() throws OrmException {
+    if (!loaded) {
+      Repository repo;
+      try {
+        repo = repoManager.openRepository(change.getProject());
+      } catch (IOException e) {
+        throw new OrmException(e);
+      }
+      try {
+        load(repo);
+        loaded = true;
+      } catch (ConfigInvalidException | IOException e) {
+        throw new OrmException(e);
+      } finally {
+        repo.close();
+      }
+    }
+    return this;
+  }
+
+  public Change.Id getChangeId() {
+    return change.getId();
+  }
+
+  public Change getChange() {
+    return change;
+  }
+
+  public ImmutableListMultimap<PatchSet.Id, PatchSetApproval> getApprovals() {
+    return approvals;
+  }
+
+  public ImmutableSetMultimap<ReviewerState, Account.Id> getReviewers() {
+    return reviewers;
+  }
+
+  /**
+   * @return submit records stored during the most recent submit; only for
+   *     changes that were actually submitted.
+   */
+  public ImmutableList<SubmitRecord> getSubmitRecords() {
+    return submitRecords;
+  }
+
+  @Override
+  protected String getRefName() {
+    return ChangeNoteUtil.changeRefName(change.getId());
+  }
+
+  @Override
+  protected void onLoad() throws IOException, ConfigInvalidException {
+    ObjectId rev = getRevision();
+    if (rev == null) {
+      return;
+    }
+    RevWalk walk = new RevWalk(reader);
+    try {
+      Parser parser = new Parser(change.getId(), rev, walk);
+      parser.parseAll();
+
+      if (parser.status != null) {
+        change.setStatus(parser.status);
+      }
+      approvals = parser.buildApprovals();
+
+      ImmutableSetMultimap.Builder<ReviewerState, Account.Id> reviewers =
+          ImmutableSetMultimap.builder();
+      for (Map.Entry<Account.Id, ReviewerState> e
+          : parser.reviewers.entrySet()) {
+        reviewers.put(e.getValue(), e.getKey());
+      }
+      this.reviewers = reviewers.build();
+      submitRecords = ImmutableList.copyOf(parser.submitRecords);
+    } finally {
+      walk.release();
+    }
+  }
+
+  @Override
+  protected boolean onSave(CommitBuilder commit) {
+    throw new UnsupportedOperationException(
+        getClass().getSimpleName() + " is read-only");
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
new file mode 100644
index 0000000..74ca198
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -0,0 +1,379 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+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.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.VersionedMetaData;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.util.LabelVote;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.FooterKey;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A single delta to apply atomically to a change.
+ * <p>
+ * This delta becomes a single commit on the notes branch, so there are
+ * limitations on the set of modifications that can be handled in a single
+ * update. In particular, there is a single author and timestamp for each
+ * update.
+ * <p>
+ * This class is not thread-safe.
+ */
+public class ChangeUpdate extends VersionedMetaData {
+  public interface Factory {
+    ChangeUpdate create(ChangeControl ctl);
+    ChangeUpdate create(ChangeControl ctl, Date when);
+    @VisibleForTesting
+    ChangeUpdate create(ChangeControl ctl, Date when,
+        Comparator<String> labelNameComparator);
+  }
+
+  private final NotesMigration migration;
+  private final GitRepositoryManager repoManager;
+  private final AccountCache accountCache;
+  private final MetaDataUpdate.User updateFactory;
+  private final ChangeControl ctl;
+  private final PersonIdent serverIdent;
+  private final Date when;
+  private final Map<String, Optional<Short>> approvals;
+  private final Map<Account.Id, ReviewerState> reviewers;
+  private Change.Status status;
+  private String subject;
+  private PatchSet.Id psId;
+  private List<SubmitRecord> submitRecords;
+
+  @AssistedInject
+  private ChangeUpdate(
+      @GerritPersonIdent PersonIdent serverIdent,
+      GitRepositoryManager repoManager,
+      NotesMigration migration,
+      AccountCache accountCache,
+      MetaDataUpdate.User updateFactory,
+      ProjectCache projectCache,
+      IdentifiedUser user,
+      @Assisted ChangeControl ctl) {
+    this(serverIdent, repoManager, migration, accountCache, updateFactory,
+        projectCache, ctl, serverIdent.getWhen());
+  }
+
+  @AssistedInject
+  private ChangeUpdate(
+      @GerritPersonIdent PersonIdent serverIdent,
+      GitRepositoryManager repoManager,
+      NotesMigration migration,
+      AccountCache accountCache,
+      MetaDataUpdate.User updateFactory,
+      ProjectCache projectCache,
+      @Assisted ChangeControl ctl,
+      @Assisted Date when) {
+    this(serverIdent, repoManager, migration, accountCache, updateFactory,
+        ctl, when,
+        projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator());
+  }
+
+  private static Project.NameKey getProjectName(ChangeControl ctl) {
+    return ctl.getChange().getDest().getParentKey();
+  }
+
+  @AssistedInject
+  private ChangeUpdate(
+      @GerritPersonIdent PersonIdent serverIdent,
+      GitRepositoryManager repoManager,
+      NotesMigration migration,
+      AccountCache accountCache,
+      MetaDataUpdate.User updateFactory,
+      @Assisted ChangeControl ctl,
+      @Assisted Date when,
+      @Assisted Comparator<String> labelNameComparator) {
+    this.repoManager = repoManager;
+    this.migration = migration;
+    this.accountCache = accountCache;
+    this.updateFactory = updateFactory;
+    this.ctl = ctl;
+    this.when = when;
+    this.serverIdent = serverIdent;
+    this.approvals = Maps.newTreeMap(labelNameComparator);
+    this.reviewers = Maps.newLinkedHashMap();
+  }
+
+  public Change getChange() {
+    return ctl.getChange();
+  }
+
+  public IdentifiedUser getUser() {
+    return (IdentifiedUser) ctl.getCurrentUser();
+  }
+
+  public Date getWhen() {
+    return when;
+  }
+
+  public void setStatus(Change.Status status) {
+    checkArgument(status != Change.Status.SUBMITTED,
+        "use submit(Iterable<PatchSetApproval>)");
+    this.status = status;
+  }
+
+  public void putApproval(String label, short value) {
+    approvals.put(label, Optional.of(value));
+  }
+
+  public void removeApproval(String label) {
+    approvals.put(label, Optional.<Short> absent());
+  }
+
+  public void submit(Iterable<SubmitRecord> submitRecords) {
+    status = Change.Status.SUBMITTED;
+    this.submitRecords = ImmutableList.copyOf(submitRecords);
+    checkArgument(!this.submitRecords.isEmpty(),
+        "no submit records specified at submit time");
+  }
+
+  public void setSubject(String subject) {
+    this.subject = subject;
+  }
+
+  public void setPatchSetId(PatchSet.Id psId) {
+    checkArgument(psId == null
+        || psId.getParentKey().equals(getChange().getKey()));
+    this.psId = psId;
+  }
+
+  public void putReviewer(Account.Id reviewer, ReviewerState type) {
+    checkArgument(type != ReviewerState.REMOVED, "invalid ReviewerType");
+    reviewers.put(reviewer, type);
+  }
+
+  public void removeReviewer(Account.Id reviewer) {
+    reviewers.put(reviewer, ReviewerState.REMOVED);
+  }
+
+  private void load() throws IOException {
+    if (migration.write() && getRevision() == null) {
+      Repository repo = repoManager.openRepository(getChange().getProject());
+      try {
+        load(repo);
+      } catch (ConfigInvalidException e) {
+        throw new IOException(e);
+      } finally {
+        repo.close();
+      }
+    }
+  }
+
+  @Override
+  public RevCommit commit(MetaDataUpdate md) throws IOException {
+    throw new UnsupportedOperationException("use commit()");
+  }
+
+  public RevCommit commit() throws IOException {
+    BatchMetaDataUpdate batch = openUpdate();
+    try {
+      batch.write(new CommitBuilder());
+      return batch.commit();
+    } finally {
+      batch.close();
+    }
+  }
+
+  private PersonIdent newIdent(Account author, Date when) {
+    return new PersonIdent(
+        author.getFullName(),
+        author.getId().get() + "@" + GERRIT_PLACEHOLDER_HOST,
+        when, serverIdent.getTimeZone());
+  }
+
+  @Override
+  public BatchMetaDataUpdate openUpdate(MetaDataUpdate update) throws IOException {
+    throw new UnsupportedOperationException("use openUpdate()");
+  }
+
+  public BatchMetaDataUpdate openUpdate() throws IOException {
+    if (migration.write()) {
+      load();
+      MetaDataUpdate md =
+          updateFactory.create(getChange().getProject(), getUser());
+      md.setAllowEmpty(true);
+      return super.openUpdate(md);
+    }
+    return new BatchMetaDataUpdate() {
+      @Override
+      public void write(CommitBuilder commit) {
+        // Do nothing.
+      }
+
+      @Override
+      public void write(VersionedMetaData config, CommitBuilder commit) {
+        // Do nothing.
+      }
+
+      @Override
+      public RevCommit createRef(String refName) {
+        return null;
+      }
+
+      @Override
+      public RevCommit commit() {
+        return null;
+      }
+
+      @Override
+      public RevCommit commitAt(ObjectId revision) {
+        return null;
+      }
+
+      @Override
+      public void close() {
+        // Do nothing.
+      }
+    };
+  }
+
+  @Override
+  protected String getRefName() {
+    return ChangeNoteUtil.changeRefName(getChange().getId());
+  }
+
+  @Override
+  protected boolean onSave(CommitBuilder commit) {
+    if (isEmpty()) {
+      return false;
+    }
+    commit.setAuthor(newIdent(getUser().getAccount(), when));
+    commit.setCommitter(new PersonIdent(serverIdent, when));
+
+    int ps = psId != null ? psId.get() : getChange().currentPatchSetId().get();
+    StringBuilder msg = new StringBuilder();
+    if (subject != null) {
+      msg.append(subject);
+    } else {
+      msg.append("Update patch set ").append(ps);
+    }
+    msg.append("\n\n");
+    addFooter(msg, FOOTER_PATCH_SET, ps);
+    if (status != null) {
+      addFooter(msg, FOOTER_STATUS, status.name().toLowerCase());
+    }
+
+    for (Map.Entry<Account.Id, ReviewerState> e : reviewers.entrySet()) {
+      Account account = accountCache.get(e.getKey()).getAccount();
+      PersonIdent ident = newIdent(account, when);
+      addFooter(msg, e.getValue().getFooterKey())
+          .append(ident.getName())
+          .append(" <").append(ident.getEmailAddress()).append(">\n");
+    }
+
+    for (Map.Entry<String, Optional<Short>> e : approvals.entrySet()) {
+      if (!e.getValue().isPresent()) {
+        addFooter(msg, FOOTER_LABEL, '-', e.getKey());
+      } else {
+        addFooter(msg, FOOTER_LABEL,
+            new LabelVote(e.getKey(), e.getValue().get()).formatWithEquals());
+      }
+    }
+
+    if (submitRecords != null) {
+      for (SubmitRecord rec : submitRecords) {
+        addFooter(msg, FOOTER_SUBMITTED_WITH)
+            .append(rec.status);
+        if (rec.errorMessage != null) {
+          msg.append(' ').append(sanitizeFooter(rec.errorMessage));
+        }
+        msg.append('\n');
+
+        if (rec.labels != null) {
+          for (SubmitRecord.Label label : rec.labels) {
+            addFooter(msg, FOOTER_SUBMITTED_WITH)
+                .append(label.status).append(": ").append(label.label);
+            if (label.appliedBy != null) {
+              PersonIdent ident =
+                  newIdent(accountCache.get(label.appliedBy).getAccount(), when);
+              msg.append(": ").append(ident.getName())
+                  .append(" <").append(ident.getEmailAddress()).append('>');
+            }
+            msg.append('\n');
+          }
+        }
+      }
+    }
+
+    commit.setMessage(msg.toString());
+    return true;
+  }
+
+  private boolean isEmpty() {
+    return approvals.isEmpty()
+        && reviewers.isEmpty()
+        && status == null
+        && submitRecords == null;
+  }
+
+  private static StringBuilder addFooter(StringBuilder sb, FooterKey footer) {
+    return sb.append(footer.getName()).append(": ");
+  }
+
+  private static void addFooter(StringBuilder sb, FooterKey footer,
+      Object... values) {
+    addFooter(sb, footer);
+    for (Object value : values) {
+      sb.append(value);
+    }
+    sb.append('\n');
+  }
+
+  private static String sanitizeFooter(String value) {
+    return value.replace('\n', ' ').replace('\0', ' ');
+  }
+
+  @Override
+  protected void onLoad() throws IOException, ConfigInvalidException {
+    // Do nothing; just reads current revision.
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
similarity index 73%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
index 2e209d1..174997c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java
@@ -12,16 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.project;
+package com.google.gerrit.server.notedb;
 
-public class ProjectInfo {
-  public String id;
-  public String name;
-  public String parent;
-  public String description;
+import com.google.gerrit.server.config.FactoryModule;
 
+public class NoteDbModule extends FactoryModule {
   @Override
-  public String toString() {
-    return name;
+  public void configure() {
+    factory(ChangeUpdate.Factory.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
new file mode 100644
index 0000000..c9ac8de
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Holds the current state of the notes DB migration.
+ * <p>
+ * During a transitional period, different subsets of the former gwtorm DB
+ * functionality may be enabled on the site, possibly only for reading or
+ * writing.
+ */
+@Singleton
+public class NotesMigration {
+  @VisibleForTesting
+  static NotesMigration allEnabled() {
+    Config cfg = new Config();
+    cfg.setBoolean("notedb", null, "write", true);
+    //cfg.setBoolean("notedb", "patchSetApprovals", "read", true);
+    return new NotesMigration(cfg);
+  }
+
+  private final boolean write;
+  private final boolean readPatchSetApprovals;
+
+  @Inject
+  NotesMigration(@GerritServerConfig Config cfg) {
+    write = cfg.getBoolean("notedb", null, "write", false);
+    readPatchSetApprovals =
+        cfg.getBoolean("notedb", "patchSetApprovals", "read", false);
+    checkArgument(!readPatchSetApprovals,
+        "notedb.readPatchSetApprovals not yet supported");
+  }
+
+  public boolean write() {
+    return write;
+  }
+
+  public boolean readPatchSetApprovals() {
+    return readPatchSetApprovals;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ReviewerState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ReviewerState.java
new file mode 100644
index 0000000..b829a69
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ReviewerState.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import org.eclipse.jgit.revwalk.FooterKey;
+
+/** State of a reviewer on a change. */
+public enum ReviewerState {
+  /** The user has contributed at least one nonzero vote on the change. */
+  REVIEWER(new FooterKey("Reviewer")),
+
+  /** The reviewer was added to the change, but has not voted. */
+  CC(new FooterKey("CC")),
+
+  /** The user was previously a reviewer on the change, but was removed. */
+  REMOVED(new FooterKey("Removed"));
+
+  private final FooterKey footerKey;
+
+  private ReviewerState(FooterKey footerKey) {
+    this.footerKey = footerKey;
+  }
+
+  FooterKey getFooterKey() {
+    return footerKey;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index e1294e0..2cda334 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -18,6 +18,7 @@
 import com.google.common.cache.CacheLoader;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 
@@ -170,19 +171,19 @@
 
     hdr.append("diff --git");
     if (aCommit != null) {
-      hdr.append(" a/" + Patch.COMMIT_MSG);
+      hdr.append(" a/").append(Patch.COMMIT_MSG);
     } else {
-      hdr.append(" " + FileHeader.DEV_NULL);
+      hdr.append(" ").append(FileHeader.DEV_NULL);
     }
-    hdr.append(" b/" + Patch.COMMIT_MSG);
+    hdr.append(" b/").append(Patch.COMMIT_MSG);
     hdr.append("\n");
 
     if (aCommit != null) {
-      hdr.append("--- a/" + Patch.COMMIT_MSG + "\n");
+      hdr.append("--- a/").append(Patch.COMMIT_MSG).append("\n");
     } else {
-      hdr.append("--- " + FileHeader.DEV_NULL + "\n");
+      hdr.append("--- ").append(FileHeader.DEV_NULL).append("\n");
     }
-    hdr.append("+++ b/" + Patch.COMMIT_MSG + "\n");
+    hdr.append("+++ b/").append(Patch.COMMIT_MSG).append("\n");
 
     Text aText =
         aCommit != null ? Text.forCommit(db, reader, aCommit) : Text.EMPTY;
@@ -249,7 +250,7 @@
   public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b,
       boolean save) throws IOException {
     String hash = b.name();
-    String refName = GitRepositoryManager.REFS_CACHE_AUTOMERGE
+    String refName = RefNames.REFS_CACHE_AUTOMERGE
         + hash.substring(0, 2)
         + "/"
         + hash.substring(2);
@@ -279,7 +280,7 @@
         }
       });
 
-      boolean couldMerge = false;
+      boolean couldMerge;
       try {
         couldMerge = m.merge(b.getParents());
       } catch (IOException e) {
@@ -311,8 +312,8 @@
         MergeFormatter fmt = new MergeFormatter();
         Map<String, MergeResult<? extends Sequence>> r = m.getMergeResults();
         Map<String, ObjectId> resolved = new HashMap<String, ObjectId>();
-        for (String path : r.keySet()) {
-          MergeResult<? extends Sequence> p = r.get(path);
+        for (Map.Entry<String, MergeResult<? extends Sequence>> entry : r.entrySet()) {
+          MergeResult<? extends Sequence> p = entry.getValue();
           TemporaryBuffer buf = new TemporaryBuffer.LocalFile(10 * 1024 * 1024);
           try {
             fmt.formatMerge(buf, p, "BASE", oursName, theirsName, "UTF-8");
@@ -320,7 +321,7 @@
 
             InputStream in = buf.openInputStream();
             try {
-              resolved.put(path, ins.insert(Constants.OBJ_BLOB, buf.length(), in));
+              resolved.put(entry.getKey(), ins.insert(Constants.OBJ_BLOB, buf.length(), in));
             } finally {
               in.close();
             }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/RemoveReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/RemoveReviewer.java
deleted file mode 100644
index af9bb8c..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/RemoveReviewer.java
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-package com.google.gerrit.server.patch;
-
-import com.google.gerrit.common.data.ReviewerResult;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-public class RemoveReviewer implements Callable<ReviewerResult> {
-  private static final Logger log =
-      LoggerFactory.getLogger(RemoveReviewer.class);
-
-  public interface Factory {
-    RemoveReviewer create(Change.Id changeId, Set<Account.Id> reviewerId);
-  }
-
-  private final ChangeControl.Factory changeControlFactory;
-  private final ReviewDb db;
-  private final AccountCache accountCache;
-  private final Change.Id changeId;
-  private final Set<Account.Id> ids;
-
-  @Inject
-  RemoveReviewer(ReviewDb db, ChangeControl.Factory changeControlFactory,
-      AccountCache accountCache, @Assisted Change.Id changeId,
-      @Assisted Set<Account.Id> ids) {
-    this.db = db;
-    this.changeControlFactory = changeControlFactory;
-    this.accountCache = accountCache;
-    this.changeId = changeId;
-    this.ids = ids;
-  }
-
-  @Override
-  public ReviewerResult call() throws Exception {
-    ReviewerResult result = new ReviewerResult();
-    ChangeControl ctl = changeControlFactory.validateFor(changeId);
-    Set<Account.Id> rejected = new HashSet<Account.Id>();
-
-    List<PatchSetApproval> current = db.patchSetApprovals().byChange(changeId).toList();
-    for (PatchSetApproval psa : current) {
-      Account.Id who = psa.getAccountId();
-      if (ids.contains(who) && !ctl.canRemoveReviewer(psa) && rejected.add(who)) {
-        result.addError(new ReviewerResult.Error(
-            ReviewerResult.Error.Type.REMOVE_NOT_PERMITTED,
-            formatUser(who)));
-      }
-    }
-
-    List<PatchSetApproval> toDelete = new ArrayList<PatchSetApproval>();
-    for (PatchSetApproval psa : current) {
-      Account.Id who = psa.getAccountId();
-      if (ids.contains(who) && !rejected.contains(who)) {
-        toDelete.add(psa);
-      }
-    }
-
-    try {
-      db.patchSetApprovals().delete(toDelete);
-    } catch (OrmException err) {
-      log.warn("Cannot remove reviewers from change "+changeId, err);
-      Set<Account.Id> failed = new HashSet<Account.Id>();
-      for (PatchSetApproval psa : toDelete) {
-        failed.add(psa.getAccountId());
-      }
-      for (Account.Id who : failed) {
-        result.addError(new ReviewerResult.Error(
-            ReviewerResult.Error.Type.COULD_NOT_REMOVE,
-            formatUser(who)));
-      }
-    }
-
-    return result;
-  }
-
-  private String formatUser(Account.Id who) {
-    AccountState state = accountCache.get(who);
-    if (state != null) {
-      return formatUser(state.getAccount(), who);
-    } else {
-      return who.toString();
-    }
-  }
-
-  static String formatUser(Account a, Object fallback) {
-    if (a.getFullName() != null && !a.getFullName().isEmpty()) {
-      return a.getFullName();
-    }
-
-    if (a.getPreferredEmail() != null && !a.getPreferredEmail().isEmpty()) {
-      return a.getPreferredEmail();
-    }
-
-    if (a.getUserName() != null && a.getUserName().isEmpty()) {
-      return a.getUserName();
-    }
-
-    return fallback.toString();
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
index 97f53fc..f12b02b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
@@ -89,7 +89,7 @@
   private static void appendPersonIdent(StringBuilder b, String field,
       PersonIdent person) {
     if (person != null) {
-      b.append(field + ":    ");
+      b.append(field).append(":    ");
       if (person.getName() != null) {
         b.append(" ");
         b.append(person.getName());
@@ -103,7 +103,7 @@
 
       SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ZZZ");
       sdf.setTimeZone(person.getTimeZone());
-      b.append(field + "Date: ");
+      b.append(field).append("Date: ");
       b.append(sdf.format(person.getWhen()));
       b.append("\n");
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
index 6062ae9..bd8a524 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
@@ -23,34 +23,20 @@
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
 import com.google.gerrit.extensions.annotations.Listen;
+import com.google.gerrit.server.plugins.JarScanner.ExtensionMetaData;
 import com.google.inject.AbstractModule;
 import com.google.inject.Module;
 import com.google.inject.Scopes;
 import com.google.inject.TypeLiteral;
 
-import org.eclipse.jgit.util.IO;
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.Attribute;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-import java.io.IOException;
-import java.io.InputStream;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.ParameterizedType;
-import java.util.Enumeration;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.Set;
-import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 
 class AutoRegisterModules {
-  private static final int SKIP_ALL = ClassReader.SKIP_CODE
-      | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
   private final String pluginName;
   private final PluginGuiceEnvironment env;
   private final JarFile jarFile;
@@ -124,60 +110,31 @@
   }
 
   private void scan() throws InvalidPluginException {
-    Enumeration<JarEntry> e = jarFile.entries();
-    while (e.hasMoreElements()) {
-      JarEntry entry = e.nextElement();
-      if (skip(entry)) {
-        continue;
-      }
-
-      ClassData def = new ClassData();
-      try {
-        new ClassReader(read(entry)).accept(def, SKIP_ALL);
-      } catch (IOException err) {
-        throw new InvalidPluginException("Cannot auto-register", err);
-      } catch (RuntimeException err) {
-        PluginLoader.log.warn(String.format(
-            "Plugin %s has invaild class file %s inside of %s",
-            pluginName, entry.getName(), jarFile.getName()), err);
-        continue;
-      }
-
-      if (def.exportedAsName != null) {
-        if (def.isConcrete()) {
-          export(def);
-        } else {
-          PluginLoader.log.warn(String.format(
-              "Plugin %s tries to @Export(\"%s\") abstract class %s",
-              pluginName, def.exportedAsName, def.className));
-        }
-      } else if (def.listen) {
-        if (def.isConcrete()) {
-          listen(def);
-        } else {
-          PluginLoader.log.warn(String.format(
-              "Plugin %s tries to @Listen abstract class %s",
-              pluginName, def.className));
-        }
-      }
+    Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> extensions =
+        JarScanner.scan(jarFile, pluginName, Arrays.asList(Export.class, Listen.class));
+    for (ExtensionMetaData export : extensions.get(Export.class)) {
+      export(export);
+    }
+    for (ExtensionMetaData listener : extensions.get(Listen.class)) {
+      listen(listener);
     }
   }
 
-  private void export(ClassData def) throws InvalidPluginException {
+  private void export(ExtensionMetaData def) throws InvalidPluginException {
     Class<?> clazz;
     try {
-      clazz = Class.forName(def.className, false, classLoader);
+      clazz = Class.forName(def.getClassName(), false, classLoader);
     } catch (ClassNotFoundException err) {
       throw new InvalidPluginException(String.format(
           "Cannot load %s with @Export(\"%s\")",
-          def.className, def.exportedAsName), err);
+          def.getClassName(), def.getAnnotationValue()), err);
     }
 
     Export export = clazz.getAnnotation(Export.class);
     if (export == null) {
       PluginLoader.log.warn(String.format(
           "In plugin %s asm incorrectly parsed %s with @Export(\"%s\")",
-          pluginName, clazz.getName(), def.exportedAsName));
+          pluginName, clazz.getName(), def.getAnnotationValue()));
       return;
     }
 
@@ -202,14 +159,14 @@
     }
   }
 
-  private void listen(ClassData def) throws InvalidPluginException {
+  private void listen(ExtensionMetaData def) throws InvalidPluginException {
     Class<?> clazz;
     try {
-      clazz = Class.forName(def.className, false, classLoader);
+      clazz = Class.forName(def.getClassName(), false, classLoader);
     } catch (ClassNotFoundException err) {
       throw new InvalidPluginException(String.format(
           "Cannot load %s with @Listen",
-          def.className), err);
+          def.getClassName()), err);
     }
 
     Listen listen = clazz.getAnnotation(Listen.class);
@@ -274,128 +231,4 @@
       type = rawType.getGenericSuperclass();
     }
   }
-
-  private static boolean skip(JarEntry entry) {
-    if (!entry.getName().endsWith(".class")) {
-      return true; // Avoid non-class resources.
-    }
-    if (entry.getSize() <= 0) {
-      return true; // Directories have 0 size.
-    }
-    if (entry.getSize() >= 1024 * 1024) {
-      return true; // Do not scan huge class files.
-    }
-    return false;
-  }
-
-  private byte[] read(JarEntry entry) throws IOException {
-    byte[] data = new byte[(int) entry.getSize()];
-    InputStream in = jarFile.getInputStream(entry);
-    try {
-      IO.readFully(in, data, 0, data.length);
-    } finally {
-      in.close();
-    }
-    return data;
-  }
-
-  private static class ClassData extends ClassVisitor {
-    private static final String EXPORT = Type.getType(Export.class).getDescriptor();
-    private static final String LISTEN = Type.getType(Listen.class).getDescriptor();
-
-    String className;
-    int access;
-    String exportedAsName;
-    boolean listen;
-
-    ClassData() {
-      super(Opcodes.ASM4);
-    }
-
-    boolean isConcrete() {
-      return (access & Opcodes.ACC_ABSTRACT) == 0
-          && (access & Opcodes.ACC_INTERFACE) == 0;
-    }
-
-    @Override
-    public void visit(int version, int access, String name, String signature,
-        String superName, String[] interfaces) {
-      this.className = Type.getObjectType(name).getClassName();
-      this.access = access;
-    }
-
-    @Override
-    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
-      if (visible && EXPORT.equals(desc)) {
-        return new AbstractAnnotationVisitor() {
-          @Override
-          public void visit(String name, Object value) {
-            exportedAsName = (String) value;
-          }
-        };
-      }
-      if (visible && LISTEN.equals(desc)) {
-        listen = true;
-        return null;
-      }
-      return null;
-    }
-
-    @Override
-    public void visitSource(String arg0, String arg1) {
-    }
-
-    @Override
-    public void visitOuterClass(String arg0, String arg1, String arg2) {
-    }
-
-    @Override
-    public MethodVisitor visitMethod(int arg0, String arg1, String arg2,
-        String arg3, String[] arg4) {
-      return null;
-    }
-
-    @Override
-    public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
-    }
-
-    @Override
-    public FieldVisitor visitField(int arg0, String arg1, String arg2,
-        String arg3, Object arg4) {
-      return null;
-    }
-
-    @Override
-    public void visitEnd() {
-    }
-
-    @Override
-    public void visitAttribute(Attribute arg0) {
-    }
-  }
-
-  private static abstract class AbstractAnnotationVisitor extends
-      AnnotationVisitor {
-    AbstractAnnotationVisitor() {
-      super(Opcodes.ASM4);
-    }
-
-    @Override
-    public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
-      return null;
-    }
-
-    @Override
-    public AnnotationVisitor visitArray(String arg0) {
-      return null;
-    }
-
-    @Override
-    public void visitEnum(String arg0, String arg1, String arg2) {
-    }
-
-    @Override
-    public void visitEnd() {
-    }
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
index cae5ce6..3a1915a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
@@ -17,8 +17,10 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.server.plugins.DisablePlugin.Input;
+import com.google.gerrit.server.plugins.ListPlugins.PluginInfo;
 import com.google.inject.Inject;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@@ -34,9 +36,14 @@
   }
 
   @Override
-  public Object apply(PluginResource resource, Input input) {
+  public PluginInfo apply(PluginResource resource, Input input)
+      throws MethodNotAllowedException {
+    if (!loader.isRemoteAdminEnabled()) {
+      throw new MethodNotAllowedException(
+          "remote plugin administration is disabled");
+    }
     String name = resource.getName();
     loader.disablePlugins(ImmutableSet.of(name));
-    return new ListPlugins.PluginInfo(loader.get(name));
+    return new PluginInfo(loader.get(name));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
index f33d814..f7745cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
@@ -17,9 +17,11 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.server.plugins.EnablePlugin.Input;
+import com.google.gerrit.server.plugins.ListPlugins.PluginInfo;
 import com.google.inject.Inject;
 
 import java.io.PrintWriter;
@@ -38,8 +40,12 @@
   }
 
   @Override
-  public Object apply(PluginResource resource, Input input)
-      throws ResourceConflictException {
+  public PluginInfo apply(PluginResource resource, Input input)
+      throws ResourceConflictException, MethodNotAllowedException {
+    if (!loader.isRemoteAdminEnabled()) {
+      throw new MethodNotAllowedException(
+          "remote plugin administration is disabled");
+    }
     String name = resource.getName();
     try {
       loader.enablePlugins(ImmutableSet.of(name));
@@ -51,6 +57,6 @@
       pw.flush();
       throw new ResourceConflictException(buf.toString());
     }
-    return new ListPlugins.PluginInfo(loader.get(name));
+    return new PluginInfo(loader.get(name));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/GetStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/GetStatus.java
index 2207d34..7651506 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/GetStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/GetStatus.java
@@ -15,10 +15,11 @@
 package com.google.gerrit.server.plugins;
 
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.plugins.ListPlugins.PluginInfo;
 
 class GetStatus implements RestReadView<PluginResource> {
   @Override
-  public Object apply(PluginResource resource) {
-    return new ListPlugins.PluginInfo(resource.getPlugin());
+  public PluginInfo apply(PluginResource resource) {
+    return new PluginInfo(resource.getPlugin());
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
index 6684bfb..33e0d12 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
@@ -18,11 +18,13 @@
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RawInput;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.server.plugins.InstallPlugin.Input;
+import com.google.gerrit.server.plugins.ListPlugins.PluginInfo;
 import com.google.inject.Inject;
 
 import java.io.IOException;
@@ -52,8 +54,11 @@
   }
 
   @Override
-  public Response<ListPlugins.PluginInfo> apply(TopLevelResource resource,
-      Input input) throws BadRequestException, IOException {
+  public Response<PluginInfo> apply(TopLevelResource resource, Input input)
+      throws BadRequestException, MethodNotAllowedException, IOException {
+    if (!loader.isRemoteAdminEnabled()) {
+      throw new MethodNotAllowedException("remote installation is disabled");
+    }
     try {
       InputStream in;
       if (input.raw != null) {
@@ -101,8 +106,8 @@
     }
 
     @Override
-    public Response<ListPlugins.PluginInfo> apply(PluginResource resource,
-        Input input) throws BadRequestException, IOException {
+    public Response<PluginInfo> apply(PluginResource resource, Input input)
+        throws BadRequestException, MethodNotAllowedException, IOException {
       return new InstallPlugin(loader, resource.getName(), false)
         .apply(TopLevelResource.INSTANCE, input);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java
index 6adc677..1568997 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl;
 import com.google.gerrit.extensions.annotations.PluginData;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
@@ -59,6 +60,7 @@
   private final JarFile jarFile;
   private final Manifest manifest;
   private final File dataDir;
+  private final String pluginCanonicalWebUrl;
   private final ClassLoader classLoader;
   private Class<? extends Module> sysModule;
   private Class<? extends Module> sshModule;
@@ -71,6 +73,7 @@
   private List<ReloadableRegistrationHandle<?>> reloadableHandles;
 
   public JarPlugin(String name,
+      String pluginCanonicalWebUrl,
       PluginUser pluginUser,
       File srcJar,
       FileSnapshot snapshot,
@@ -83,6 +86,7 @@
       @Nullable Class<? extends Module> sshModule,
       @Nullable Class<? extends Module> httpModule) {
     super(name, srcJar, pluginUser, snapshot, apiType);
+    this.pluginCanonicalWebUrl = pluginCanonicalWebUrl;
     this.jarFile = jarFile;
     this.manifest = manifest;
     this.dataDir = dataDir;
@@ -193,6 +197,9 @@
         bind(String.class)
           .annotatedWith(PluginName.class)
           .toInstance(getName());
+        bind(String.class)
+          .annotatedWith(PluginCanonicalWebUrl.class)
+          .toInstance(pluginCanonicalWebUrl);
 
         bind(File.class)
           .annotatedWith(PluginData.class)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
new file mode 100644
index 0000000..2f25ed0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -0,0 +1,282 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.plugins;
+
+import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.collect.Iterables.transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import org.eclipse.jgit.util.IO;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+public class JarScanner {
+  private static final int SKIP_ALL = ClassReader.SKIP_CODE
+      | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
+  private static final Function<ClassData, ExtensionMetaData> CLASS_DATA_TO_EXTENSION_META_DATA =
+      new Function<ClassData, ExtensionMetaData>() {
+        @Override
+        public ExtensionMetaData apply(ClassData classData) {
+          return new ExtensionMetaData(classData.className,
+              classData.annotationValue, classData.interfaces);
+        }
+      };
+  private static final Function<String, String> TO_JAVA_QUALIFIED_CLASS_NAME =
+      new Function<String, String>() {
+        @Override
+        public String apply(String in) {
+          return in.replace("/", ".");
+        }
+      };
+
+  public static class ExtensionMetaData {
+    private final String className;
+    private final String annotationValue;
+    private final Iterable<String> interfaces;
+
+    private ExtensionMetaData(String className, String annotationValue, Iterable<String> interfaces) {
+      this.className = className;
+      this.annotationValue = annotationValue;
+      this.interfaces = interfaces;
+    }
+
+    public String getAnnotationValue() {
+      return annotationValue;
+    }
+
+    public String getClassName() {
+      return className;
+    }
+
+    public Iterable<String> getInterfaces() {
+      return interfaces;
+    }
+  }
+
+  public static Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
+      JarFile jarFile, String pluginName,
+      Iterable<Class<? extends Annotation>> annotations)
+      throws InvalidPluginException {
+    Set<String> descriptors = Sets.newHashSet();
+    Multimap<String, JarScanner.ClassData> rawMap = ArrayListMultimap.create();
+    Map<Class<? extends Annotation>, String> classObjToClassDescr =
+        Maps.newHashMap();
+
+    for (Class<? extends Annotation> annotation : annotations) {
+      String descriptor = Type.getType(annotation).getDescriptor();
+      descriptors.add(descriptor);
+      classObjToClassDescr.put(annotation, descriptor);
+    }
+
+    Enumeration<JarEntry> e = jarFile.entries();
+    while (e.hasMoreElements()) {
+      JarEntry entry = e.nextElement();
+      if (skip(entry)) {
+        continue;
+      }
+
+      ClassData def = new ClassData(descriptors);
+      try {
+        new ClassReader(read(jarFile, entry)).accept(def, SKIP_ALL);
+      } catch (IOException err) {
+        throw new InvalidPluginException("Cannot auto-register", err);
+      } catch (RuntimeException err) {
+        PluginLoader.log.warn(String.format(
+            "Plugin %s has invaild class file %s inside of %s", pluginName,
+            entry.getName(), jarFile.getName()), err);
+        continue;
+      }
+
+      if (def.isConcrete()) {
+        if (!Strings.isNullOrEmpty(def.annotationName)) {
+          rawMap.put(def.annotationName, def);
+        }
+      } else {
+        PluginLoader.log.warn(String.format(
+            "Plugin %s tries to @%s(\"%s\") abstract class %s", pluginName,
+            def.annotationName, def.annotationValue, def.className));
+      }
+    }
+
+    ImmutableMap.Builder<Class<? extends Annotation>, Iterable<ExtensionMetaData>> result =
+        ImmutableMap.builder();
+
+    for (Class<? extends Annotation> annotoation : annotations) {
+      String descr = classObjToClassDescr.get(annotoation);
+      Collection<ClassData> discoverdData = rawMap.get(descr);
+      Collection<ClassData> values =
+          firstNonNull(discoverdData, Collections.<ClassData> emptySet());
+
+      result.put(annotoation,
+          transform(values, CLASS_DATA_TO_EXTENSION_META_DATA));
+    }
+
+    return result.build();
+  }
+
+  private static boolean skip(JarEntry entry) {
+    if (!entry.getName().endsWith(".class")) {
+      return true; // Avoid non-class resources.
+    }
+    if (entry.getSize() <= 0) {
+      return true; // Directories have 0 size.
+    }
+    if (entry.getSize() >= 1024 * 1024) {
+      return true; // Do not scan huge class files.
+    }
+    return false;
+  }
+
+  private static byte[] read(JarFile jarFile, JarEntry entry)
+      throws IOException {
+    byte[] data = new byte[(int) entry.getSize()];
+    InputStream in = jarFile.getInputStream(entry);
+    try {
+      IO.readFully(in, data, 0, data.length);
+    } finally {
+      in.close();
+    }
+    return data;
+  }
+
+  public static class ClassData extends ClassVisitor {
+    int access;
+    String className;
+    String annotationName;
+    String annotationValue;
+    Iterable<String> interfaces;
+    Iterable<String> exports;
+
+    private ClassData(Iterable<String> exports) {
+      super(Opcodes.ASM4);
+      this.exports = exports;
+    }
+
+    boolean isConcrete() {
+      return (access & Opcodes.ACC_ABSTRACT) == 0
+          && (access & Opcodes.ACC_INTERFACE) == 0;
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature,
+        String superName, String[] interfaces) {
+      this.interfaces =
+          Iterables.transform(Sets.newHashSet(interfaces),
+              TO_JAVA_QUALIFIED_CLASS_NAME);
+      this.className = Type.getObjectType(name).getClassName();
+      this.access = access;
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+      Optional<String> found =
+          Iterables.tryFind(exports, Predicates.equalTo(desc));
+      if (visible && found.isPresent()) {
+        annotationName = desc;
+        return new AbstractAnnotationVisitor() {
+          @Override
+          public void visit(String name, Object value) {
+            annotationValue = (String) value;
+          }
+        };
+      }
+      return null;
+    }
+
+    @Override
+    public void visitSource(String arg0, String arg1) {
+    }
+
+    @Override
+    public void visitOuterClass(String arg0, String arg1, String arg2) {
+    }
+
+    @Override
+    public MethodVisitor visitMethod(int arg0, String arg1, String arg2,
+        String arg3, String[] arg4) {
+      return null;
+    }
+
+    @Override
+    public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
+    }
+
+    @Override
+    public FieldVisitor visitField(int arg0, String arg1, String arg2,
+        String arg3, Object arg4) {
+      return null;
+    }
+
+    @Override
+    public void visitEnd() {
+    }
+
+    @Override
+    public void visitAttribute(Attribute arg0) {
+    }
+  }
+
+  private static abstract class AbstractAnnotationVisitor extends
+      AnnotationVisitor {
+    AbstractAnnotationVisitor() {
+      super(Opcodes.ASM4);
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
+      return null;
+    }
+
+    @Override
+    public AnnotationVisitor visitArray(String arg0) {
+      return null;
+    }
+
+    @Override
+    public void visitEnum(String arg0, String arg1, String arg2) {
+    }
+
+    @Override
+    public void visitEnd() {
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
new file mode 100644
index 0000000..ff72576
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
@@ -0,0 +1,112 @@
+// 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.plugins;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.server.PluginUser;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import org.eclipse.jgit.internal.storage.file.FileSnapshot;
+
+import java.io.File;
+import java.util.jar.JarFile;
+
+class JsPlugin extends Plugin {
+  private Injector httpInjector;
+
+  JsPlugin(String name, File srcFile, PluginUser pluginUser,
+      FileSnapshot snapshot) {
+    super(name, srcFile, pluginUser, snapshot, ApiType.JS);
+  }
+
+  @Override
+  @Nullable
+  public String getVersion() {
+    String fileName = getSrcFile().getName();
+    int firstDash = fileName.indexOf("-");
+    if (firstDash > 0) {
+      return fileName.substring(firstDash + 1, fileName.lastIndexOf(".js"));
+    }
+    return "";
+  }
+
+  @Override
+  public void start(PluginGuiceEnvironment env) throws Exception {
+    manager = new LifecycleManager();
+    String fileName = getSrcFile().getName();
+    httpInjector =
+        Guice.createInjector(new StandaloneJsPluginModule(getName(), fileName));
+    manager.start();
+  }
+
+  @Override
+  void stop(PluginGuiceEnvironment env) {
+    if (manager != null) {
+      manager.stop();
+      httpInjector = null;
+    }
+  }
+
+  @Override
+  public JarFile getJarFile() {
+    return null;
+  }
+
+  @Override
+  public Injector getSysInjector() {
+    return null;
+  }
+
+  @Override
+  @Nullable
+  public Injector getSshInjector() {
+    return null;
+  }
+
+  @Override
+  @Nullable
+  public Injector getHttpInjector() {
+    return httpInjector;
+  }
+
+  @Override
+  boolean canReload() {
+    return true;
+  }
+
+  private static final class StandaloneJsPluginModule extends AbstractModule {
+    private final String fileName;
+    private final String pluginName;
+
+    StandaloneJsPluginModule(String pluginName, String fileName) {
+      this.pluginName = pluginName;
+      this.fileName = fileName;
+    }
+
+    @Override
+    protected void configure() {
+      bind(String.class).annotatedWith(PluginName.class).toInstance(pluginName);
+      DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(
+          new JavaScriptPlugin(fileName));
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index ca0f7ae..a2348f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -19,9 +19,6 @@
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.extensions.restapi.Url;
@@ -69,8 +66,8 @@
   }
 
   @Override
-  public Object apply(TopLevelResource resource) throws AuthException,
-      BadRequestException, ResourceConflictException, Exception {
+  public Object apply(TopLevelResource resource)
+      throws UnsupportedEncodingException {
     format = OutputFormat.JSON;
     return display(null);
   }
@@ -85,6 +82,9 @@
       } catch (UnsupportedEncodingException e) {
         throw new RuntimeException("JVM lacks UTF-8 encoding", e);
       }
+    } else if (!format.isJson()) {
+      throw new IllegalStateException(
+          "Text output requires that a display OutputStream is provided.");
     }
 
     Map<String, PluginInfo> output = Maps.newTreeMap();
@@ -130,12 +130,17 @@
     final String kind = "gerritcodereview#plugin";
     String id;
     String version;
+    String indexUrl;
     Boolean disabled;
 
     PluginInfo(Plugin p) {
       id = Url.encode(p.getName());
       version = p.getVersion();
       disabled = p.isDisabled() ? true : null;
+
+      if (p.getJarFile() != null) {
+        indexUrl = String.format("plugins/%s/", p.getName());
+      }
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
index d9a2c0f..988669a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
@@ -34,7 +34,7 @@
 
 public abstract class Plugin {
   public static enum ApiType {
-    EXTENSION, PLUGIN, JS;
+    EXTENSION, PLUGIN, JS
   }
 
   /** Unique key that changes whenever a plugin reloads. */
@@ -93,14 +93,14 @@
     this.disabled = srcFile.getName().endsWith(".disabled");
   }
 
-  File getSrcFile() {
-    return srcFile;
-  }
-
   PluginUser getPluginUser() {
     return pluginUser;
   }
 
+  public File getSrcFile() {
+    return srcFile;
+  }
+
   public String getName() {
     return name;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginInstallException.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginInstallException.java
index 77fa702..d681adb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginInstallException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginInstallException.java
@@ -17,6 +17,10 @@
 public class PluginInstallException extends Exception {
   private static final long serialVersionUID = 1L;
 
+  public PluginInstallException(String msg) {
+    super(msg);
+  }
+
   public PluginInstallException(Throwable why) {
     super(why.getMessage(), why);
   }
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 7569744..732da4b 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
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.plugins;
 
+import com.google.common.base.CharMatcher;
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
@@ -30,6 +31,7 @@
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
 import com.google.gerrit.extensions.webui.JavaScriptPlugin;
 import com.google.gerrit.server.PluginUser;
+import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
@@ -49,6 +51,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.text.SimpleDateFormat;
@@ -86,6 +89,8 @@
   private final Queue<Plugin> toCleanup;
   private final Provider<PluginCleanerTask> cleaner;
   private final PluginScannerThread scanner;
+  private final Provider<String> urlProvider;
+  private final boolean remoteAdmin;
 
   @Inject
   public PluginLoader(SitePaths sitePaths,
@@ -93,7 +98,8 @@
       ServerInformationImpl sii,
       PluginUser.Factory puf,
       Provider<PluginCleanerTask> pct,
-      @GerritServerConfig Config cfg) {
+      @GerritServerConfig Config cfg,
+      @CanonicalWebUrl Provider<String> provider) {
     pluginsDir = sitePaths.plugins_dir;
     dataDir = sitePaths.data_dir;
     tmpDir = sitePaths.tmp_dir;
@@ -106,6 +112,10 @@
     toCleanup = Queues.newArrayDeque();
     cleanupHandles = Maps.newConcurrentMap();
     cleaner = pct;
+    urlProvider = provider;
+
+    remoteAdmin =
+        cfg.getBoolean("plugins", null, "allowRemoteAdmin", false);
 
     long checkFrequency = ConfigUtil.getTimeUnit(cfg,
         "plugins", null, "checkFrequency",
@@ -117,6 +127,10 @@
     }
   }
 
+  public boolean isRemoteAdminEnabled() {
+    return remoteAdmin;
+  }
+
   public Plugin get(String name) {
     Plugin p = running.get(name);
     if (p != null) {
@@ -137,8 +151,10 @@
 
   public void installPluginFromStream(String originalName, InputStream in)
       throws IOException, PluginInstallException {
+    checkRemoteInstall();
+
     String fileName = originalName;
-    if (!fileName.endsWith(".jar")) {
+    if (!(fileName.endsWith(".jar") || fileName.endsWith(".js"))) {
       fileName += ".jar";
     }
     File tmp = asTemp(in, ".next_" + fileName + "_", ".tmp", pluginsDir);
@@ -150,7 +166,8 @@
           name, originalName));
     }
 
-    File dst = new File(pluginsDir, name + ".jar");
+    String fileExtension = getExtension(fileName);
+    File dst = new File(pluginsDir, name + fileExtension);
     synchronized (this) {
       Plugin active = running.get(name);
       if (active != null) {
@@ -220,6 +237,12 @@
   }
 
   public void disablePlugins(Set<String> names) {
+    if (!isRemoteAdminEnabled()) {
+      log.warn("Remote plugin administration is disabled,"
+          + " ignoring disablePlugins(" + names + ")");
+      return;
+    }
+
     synchronized (this) {
       for (String name : names) {
         Plugin active = running.get(name);
@@ -248,6 +271,12 @@
   }
 
   public void enablePlugins(Set<String> names) throws PluginInstallException {
+    if (!isRemoteAdminEnabled()) {
+      log.warn("Remote plugin administration is disabled,"
+          + " ignoring enablePlugins(" + names + ")");
+      return;
+    }
+
     synchronized (this) {
       for (String name : names) {
         Plugin off = disabled.get(name);
@@ -344,8 +373,9 @@
     syncDisabledPlugins(jars);
 
     Map<String, File> activePlugins = filterDisabled(jars);
-    for (String name : activePlugins.keySet()) {
-      File jar = activePlugins.get(name);
+    for (Map.Entry<String, File> entry : activePlugins.entrySet()) {
+      String name = entry.getKey();
+      File jar = entry.getValue();
       FileSnapshot brokenTime = broken.get(name);
       if (brokenTime != null && !brokenTime.isModified(jar)) {
         continue;
@@ -380,11 +410,11 @@
     dropRemovedDisabledPlugins(jars);
   }
 
-  private Plugin runPlugin(String name, File jar, Plugin oldPlugin)
+  private Plugin runPlugin(String name, File plugin, Plugin oldPlugin)
       throws PluginInstallException {
-    FileSnapshot snapshot = FileSnapshot.save(jar);
+    FileSnapshot snapshot = FileSnapshot.save(plugin);
     try {
-      Plugin newPlugin = loadPlugin(name, jar, snapshot);
+      Plugin newPlugin = loadPlugin(name, plugin, snapshot);
       boolean reload = oldPlugin != null
           && oldPlugin.canReload()
           && newPlugin.canReload();
@@ -448,7 +478,9 @@
       iterator.remove();
 
       CleanupHandle cleanupHandle = cleanupHandles.remove(plugin);
-      cleanupHandle.cleanup();
+      if (cleanupHandle != null) {
+        cleanupHandle.cleanup();
+      }
     }
     return toCleanup.size();
   }
@@ -460,8 +492,8 @@
     }
   }
 
-  public static String nameOf(File jar) {
-    return nameOf(jar.getName());
+  public static String nameOf(File plugin) {
+    return nameOf(plugin.getName());
   }
 
   private static String nameOf(String name) {
@@ -472,16 +504,39 @@
     return 0 < ext ? name.substring(0, ext) : name;
   }
 
-  private Plugin loadPlugin(String name, File srcJar, FileSnapshot snapshot)
-      throws IOException, ClassNotFoundException, InvalidPluginException {
-    File tmp;
-    FileInputStream in = new FileInputStream(srcJar);
-    try {
-      tmp = asTemp(in, tempNameFor(name), ".jar", tmpDir);
-    } finally {
-      in.close();
-    }
+  private static String getExtension(File file) {
+    return getExtension(file.getName());
+  }
 
+  private static String getExtension(String name) {
+    int ext = name.lastIndexOf('.');
+    return 0 < ext ? name.substring(ext) : "";
+  }
+
+  private Plugin loadPlugin(String name, File srcPlugin, FileSnapshot snapshot)
+      throws IOException, ClassNotFoundException, InvalidPluginException {
+    String pluginName = srcPlugin.getName();
+    if (isJarPlugin(pluginName)) {
+      File tmp;
+      FileInputStream in = new FileInputStream(srcPlugin);
+      String extension = getExtension(srcPlugin);
+      try {
+        tmp = asTemp(in, tempNameFor(name), extension, tmpDir);
+      } finally {
+        in.close();
+      }
+      return loadJarPlugin(name, srcPlugin, snapshot, tmp);
+    } else if (isJsPlugin(pluginName)) {
+      return loadJsPlugin(name, srcPlugin, snapshot);
+    } else {
+      throw new InvalidPluginException(String.format(
+          "Unsupported plugin type: %s", srcPlugin.getName()));
+    }
+  }
+
+  private Plugin loadJarPlugin(String name, File srcJar, FileSnapshot snapshot,
+      File tmp) throws IOException, InvalidPluginException,
+      MalformedURLException, ClassNotFoundException {
     JarFile jarFile = new JarFile(tmp);
     boolean keep = false;
     try {
@@ -498,13 +553,32 @@
             Plugin.ApiType.PLUGIN));
       }
 
-      URL[] urls = {tmp.toURI().toURL()};
-      ClassLoader parentLoader = parentFor(type);
-      ClassLoader pluginLoader = new URLClassLoader(urls, parentLoader);
+      List<URL> urls = new ArrayList<>(2);
+      String overlay = System.getProperty("gerrit.plugin-classes");
+      if (overlay != null) {
+        File classes = new File(new File(new File(overlay), name), "main");
+        if (classes.isDirectory()) {
+          log.info(String.format(
+              "plugin %s: including %s",
+              name, classes.getPath()));
+          urls.add(classes.toURI().toURL());
+        }
+      }
+      urls.add(tmp.toURI().toURL());
+
+      ClassLoader pluginLoader = new URLClassLoader(
+          urls.toArray(new URL[urls.size()]),
+          parentFor(type));
       Class<? extends Module> sysModule = load(sysName, pluginLoader);
       Class<? extends Module> sshModule = load(sshName, pluginLoader);
       Class<? extends Module> httpModule = load(httpName, pluginLoader);
-      Plugin plugin = new JarPlugin(name, pluginUserFactory.create(name),
+
+      String url = String.format("%s/plugins/%s/",
+          CharMatcher.is('/').trimTrailingFrom(urlProvider.get()),
+          name);
+
+      Plugin plugin = new JarPlugin(name, url,
+          pluginUserFactory.create(name),
           srcJar, snapshot,
           jarFile, manifest,
           new File(dataDir, name), type, pluginLoader,
@@ -519,6 +593,10 @@
     }
   }
 
+  private Plugin loadJsPlugin(String name, File srcJar, FileSnapshot snapshot) {
+    return new JsPlugin(name, srcJar, pluginUserFactory.create(name), snapshot);
+  }
+
   private static ClassLoader parentFor(Plugin.ApiType type)
       throws InvalidPluginException {
     switch (type) {
@@ -630,7 +708,7 @@
       @Override
       public boolean accept(File pathname) {
         String n = pathname.getName();
-        return (n.endsWith(".jar") || n.endsWith(".jar.disabled"))
+        return (isJarPlugin(n) || isJsPlugin(n))
             && !n.startsWith(".last_")
             && !n.startsWith(".next_")
             && pathname.isFile();
@@ -654,13 +732,20 @@
   }
 
   public static String getGerritPluginName(File srcFile) throws IOException {
-    JarFile jarFile = new JarFile(srcFile);
-    try {
-      return jarFile.getManifest().getMainAttributes()
-          .getValue("Gerrit-PluginName");
-    } finally {
-      jarFile.close();
+    String fileName = srcFile.getName();
+    if (isJarPlugin(fileName)) {
+      JarFile jarFile = new JarFile(srcFile);
+      try {
+        return jarFile.getManifest().getMainAttributes()
+            .getValue("Gerrit-PluginName");
+      } finally {
+        jarFile.close();
+      }
     }
+    if (isJsPlugin(fileName)) {
+      return fileName.substring(0, fileName.length() - 3);
+    }
+    return null;
   }
 
   private static Multimap<String, File> asMultimap(List<File> plugins)
@@ -672,4 +757,23 @@
     }
     return map;
   }
+
+  private static boolean isJarPlugin(String name) {
+    return isPlugin(name, "jar");
+  }
+
+  private static boolean isJsPlugin(String name) {
+    return isPlugin(name, "js");
+  }
+
+  private static boolean isPlugin(String fileName, String ext) {
+    String fullExt = "." + ext;
+    return fileName.endsWith(fullExt) || fileName.endsWith(fullExt + ".disabled");
+  }
+
+  private void checkRemoteInstall() throws PluginInstallException {
+    if (!isRemoteAdminEnabled()) {
+      throw new PluginInstallException("remote installation is disabled");
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java
index bd1c3c8..055baf1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginModule.java
@@ -16,9 +16,8 @@
 
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
 import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.inject.AbstractModule;
 
-public class PluginModule extends AbstractModule {
+public class PluginModule extends LifecycleModule {
   @Override
   protected void configure() {
     bind(ServerInformationImpl.class);
@@ -28,11 +27,6 @@
     bind(PluginGuiceEnvironment.class);
     bind(PluginLoader.class);
     bind(CopyConfigModule.class);
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().to(PluginLoader.class);
-      }
-    });
+    listener().to(PluginLoader.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
index 6d30b42..076bfdd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AcceptsCreate;
 import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestCollection;
 import com.google.gerrit.extensions.restapi.RestView;
@@ -58,7 +59,10 @@
   @SuppressWarnings("unchecked")
   @Override
   public InstallPlugin create(TopLevelResource parent, IdString id)
-      throws ResourceNotFoundException {
+      throws ResourceNotFoundException, MethodNotAllowedException {
+    if (!loader.isRemoteAdminEnabled()) {
+      throw new MethodNotAllowedException("remote installation is disabled");
+    }
     return new InstallPlugin(loader, id.get(), true /* created */);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java
index 9f9ef2db..32e8b24 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.plugins.ListPlugins.PluginInfo;
 import com.google.gerrit.server.plugins.ReloadPlugin.Input;
 import com.google.inject.Inject;
 
@@ -38,7 +39,7 @@
   }
 
   @Override
-  public Object apply(PluginResource resource, Input input) throws ResourceConflictException {
+  public PluginInfo apply(PluginResource resource, Input input) throws ResourceConflictException {
     String name = resource.getName();
     try {
       loader.reload(ImmutableList.of(name));
@@ -52,6 +53,6 @@
       pw.flush();
       throw new ResourceConflictException(buf.toString());
     }
-    return new ListPlugins.PluginInfo(loader.get(name));
+    return new PluginInfo(loader.get(name));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java
index 3b1191c..7215d18 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/AccessControlModule.java
@@ -22,8 +22,6 @@
 import com.google.gerrit.server.config.GitReceivePackGroupsProvider;
 import com.google.gerrit.server.config.GitUploadPackGroups;
 import com.google.gerrit.server.config.GitUploadPackGroupsProvider;
-import com.google.gerrit.server.config.ProjectOwnerGroups;
-import com.google.gerrit.server.config.ProjectOwnerGroupsProvider;
 import com.google.inject.TypeLiteral;
 
 import java.util.Set;
@@ -32,10 +30,6 @@
   @Override
   protected void configure() {
     bind(new TypeLiteral<Set<AccountGroup.UUID>>() {}) //
-        .annotatedWith(ProjectOwnerGroups.class) //
-        .toProvider(ProjectOwnerGroupsProvider.class).in(SINGLETON);
-
-    bind(new TypeLiteral<Set<AccountGroup.UUID>>() {}) //
         .annotatedWith(GitUploadPackGroups.class) //
         .toProvider(GitUploadPackGroupsProvider.class).in(SINGLETON);
 
@@ -43,6 +37,7 @@
         .annotatedWith(GitReceivePackGroups.class) //
         .toProvider(GitReceivePackGroupsProvider.class).in(SINGLETON);
 
+    factory(ChangeControl.AssistedFactory.class);
     factory(ProjectControl.AssistedFactory.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 698a0ac..8e3c1e9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -28,13 +28,16 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import com.google.inject.util.Providers;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
 
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
 import com.googlecode.prolog_cafe.lang.ListTerm;
@@ -48,6 +51,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
@@ -166,16 +170,58 @@
     }
   }
 
-  private final RefControl refControl;
-  private final Change change;
+  interface AssistedFactory {
+    ChangeControl create(RefControl refControl, Change change);
+    ChangeControl create(RefControl refControl, ChangeNotes notes);
+  }
 
-  ChangeControl(final RefControl r, final Change c) {
-    this.refControl = r;
-    this.change = c;
+  /**
+   * Exception thrown when the label term of a submit record
+   * unexpectedly didn't contain a user term.
+   */
+  private static class UserTermExpected extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    public UserTermExpected(SubmitRecord.Label label) {
+      super(String.format("A label with the status %s must contain a user.",
+          label.toString()));
+    }
+  }
+
+  private final ApprovalsUtil approvalsUtil;
+  private final ChangeData.Factory changeDataFactory;
+  private final RefControl refControl;
+  private final ChangeNotes notes;
+
+  @AssistedInject
+  ChangeControl(
+      ApprovalsUtil approvalsUtil,
+      ChangeData.Factory changeDataFactory,
+      ChangeNotes.Factory notesFactory,
+      @Assisted RefControl refControl,
+      @Assisted Change change) {
+    this(approvalsUtil, changeDataFactory, refControl,
+        notesFactory.create(change));
+  }
+
+  @AssistedInject
+  ChangeControl(
+      ApprovalsUtil approvalsUtil,
+      ChangeData.Factory changeDataFactory,
+      @Assisted RefControl refControl,
+      @Assisted ChangeNotes notes) {
+    this.approvalsUtil = approvalsUtil;
+    this.changeDataFactory = changeDataFactory;
+    this.refControl = refControl;
+    this.notes = notes;
   }
 
   public ChangeControl forUser(final CurrentUser who) {
-    return new ChangeControl(getRefControl().forUser(who), getChange());
+    if (getCurrentUser().equals(who)) {
+      return this;
+    }
+    return new ChangeControl(approvalsUtil, changeDataFactory,
+        getRefControl().forUser(who), notes);
   }
 
   public RefControl getRefControl() {
@@ -195,12 +241,17 @@
   }
 
   public Change getChange() {
-    return change;
+    return notes.getChange();
+  }
+
+  public ChangeNotes getNotes() {
+    return notes;
   }
 
   /** Can this user see this change? */
   public boolean isVisible(ReviewDb db) throws OrmException {
-    if (change.getStatus() == Change.Status.DRAFT && !isDraftVisible(db, null)) {
+    if (getChange().getStatus() == Change.Status.DRAFT
+        && !isDraftVisible(db, null)) {
       return false;
     }
     return isRefVisible();
@@ -279,12 +330,12 @@
 
   /** All value ranges of any allowed label permission. */
   public List<PermissionRange> getLabelRanges() {
-    return getRefControl().getLabelRanges();
+    return getRefControl().getLabelRanges(isOwner());
   }
 
   /** The range of permitted values associated with a label permission. */
   public PermissionRange getRange(String permission) {
-    return getRefControl().getRange(permission);
+    return getRefControl().getRange(permission, isOwner());
   }
 
   /** Can this user add a patch set to this change? */
@@ -296,7 +347,7 @@
   public boolean isOwner() {
     if (getCurrentUser().isIdentifiedUser()) {
       final IdentifiedUser i = (IdentifiedUser) getCurrentUser();
-      return i.getAccountId().equals(change.getOwner());
+      return i.getAccountId().equals(getChange().getOwner());
     }
     return false;
   }
@@ -310,18 +361,9 @@
   public boolean isReviewer(ReviewDb db, @Nullable ChangeData cd)
       throws OrmException {
     if (getCurrentUser().isIdentifiedUser()) {
-      final IdentifiedUser user = (IdentifiedUser) getCurrentUser();
-      Iterable<PatchSetApproval> results;
-      if (cd != null) {
-        results = cd.currentApprovals(Providers.of(db));
-      } else {
-        results = db.patchSetApprovals().byChange(change.getId());
-      }
-      for (PatchSetApproval approval : results) {
-        if (user.getAccountId().equals(approval.getAccountId())) {
-          return true;
-        }
-      }
+      Collection<Account.Id> results = changeData(db, cd).reviewers().values();
+      IdentifiedUser user = (IdentifiedUser) getCurrentUser();
+      return results.contains(user.getAccountId());
     }
     return false;
   }
@@ -363,7 +405,7 @@
 
   /** Can this user edit the topic name? */
   public boolean canEditTopicName() {
-    if (change.getStatus().isOpen()) {
+    if (getChange().getStatus().isOpen()) {
       return isOwner() // owner (aka creator) of the change can edit topic
           || getRefControl().isOwner() // branch owner can edit topic
           || getProjectControl().isOwner() // project owner can edit topic
@@ -383,6 +425,10 @@
     return getRefControl().canSubmit();
   }
 
+  public boolean canSubmitAs() {
+    return getRefControl().canSubmitAs();
+  }
+
   public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet) {
     return canSubmit(db, patchSet, null, false, false, false);
   }
@@ -390,17 +436,18 @@
   public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet,
       @Nullable ChangeData cd, boolean fastEvalLabels, boolean allowClosed,
       boolean allowDraft) {
-    if (!allowClosed && change.getStatus().isClosed()) {
+    if (!allowClosed && getChange().getStatus().isClosed()) {
       SubmitRecord rec = new SubmitRecord();
       rec.status = SubmitRecord.Status.CLOSED;
       return Collections.singletonList(rec);
     }
 
-    if (!patchSet.getId().equals(change.currentPatchSetId())) {
+    if (!patchSet.getId().equals(getChange().currentPatchSetId())) {
       return ruleError("Patch set " + patchSet.getPatchSetId() + " is not current");
     }
 
-    if ((change.getStatus() == Change.Status.DRAFT || patchSet.isDraft())
+    cd = changeData(db, cd);
+    if ((getChange().getStatus() == Change.Status.DRAFT || patchSet.isDraft())
         && !allowDraft) {
       return cannotSubmitDraft(db, patchSet, cd);
     }
@@ -410,7 +457,7 @@
     try {
       evaluator = new SubmitRuleEvaluator(db, patchSet,
           getProjectControl(),
-          this, change, cd,
+          this, getChange(), cd,
           fastEvalLabels,
           "locate_submit_rule", "can_submit",
           "locate_submit_filter", "filter_submit_results");
@@ -425,7 +472,7 @@
       // required for this change to be submittable. Each label will indicate
       // whether or not that is actually possible given the permissions.
       log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
-          + change.getId() + " of " + getProject().getName()
+          + getChange().getId() + " of " + getProject().getName()
           + " has no solution.");
       return ruleError("Project submit rule has no solution");
     }
@@ -439,7 +486,7 @@
   }
 
   private List<SubmitRecord> cannotSubmitDraft(ReviewDb db, PatchSet patchSet,
-      ChangeData cd) {
+      @Nullable ChangeData cd) {
     try {
       if (!isDraftVisible(db, cd)) {
         return ruleError("Patch set " + patchSet.getPatchSetId() + " not found");
@@ -504,25 +551,29 @@
         lbl.label = state.arg(0).name();
         Term status = state.arg(1);
 
-        if ("ok".equals(status.name())) {
-          lbl.status = SubmitRecord.Label.Status.OK;
-          appliedBy(lbl, status);
+        try {
+          if ("ok".equals(status.name())) {
+            lbl.status = SubmitRecord.Label.Status.OK;
+            appliedBy(lbl, status);
 
-        } else if ("reject".equals(status.name())) {
-          lbl.status = SubmitRecord.Label.Status.REJECT;
-          appliedBy(lbl, status);
+          } else if ("reject".equals(status.name())) {
+            lbl.status = SubmitRecord.Label.Status.REJECT;
+            appliedBy(lbl, status);
 
-        } else if ("need".equals(status.name())) {
-          lbl.status = SubmitRecord.Label.Status.NEED;
+          } else if ("need".equals(status.name())) {
+            lbl.status = SubmitRecord.Label.Status.NEED;
 
-        } else if ("may".equals(status.name())) {
-          lbl.status = SubmitRecord.Label.Status.MAY;
+          } else if ("may".equals(status.name())) {
+            lbl.status = SubmitRecord.Label.Status.MAY;
 
-        } else if ("impossible".equals(status.name())) {
-          lbl.status = SubmitRecord.Label.Status.IMPOSSIBLE;
+          } else if ("impossible".equals(status.name())) {
+            lbl.status = SubmitRecord.Label.Status.IMPOSSIBLE;
 
-        } else {
-          return logInvalidResult(submitRule, submitRecord);
+          } else {
+            return logInvalidResult(submitRule, submitRecord);
+          }
+        } catch (UserTermExpected e) {
+          return logInvalidResult(submitRule, submitRecord, e.getMessage());
         }
       }
 
@@ -541,8 +592,10 @@
 
   public SubmitTypeRecord getSubmitTypeRecord(ReviewDb db, PatchSet patchSet,
       @Nullable ChangeData cd) {
+    cd = changeData(db, cd);
     try {
-      if (change.getStatus() == Change.Status.DRAFT && !isDraftVisible(db, cd)) {
+      if (getChange().getStatus() == Change.Status.DRAFT
+          && !isDraftVisible(db, cd)) {
         return typeRuleError("Patch set " + patchSet.getPatchSetId()
             + " not found");
       }
@@ -559,7 +612,7 @@
     SubmitRuleEvaluator evaluator;
     try {
       evaluator = new SubmitRuleEvaluator(db, patchSet,
-          getProjectControl(), this, change, cd,
+          getProjectControl(), this, getChange(), cd,
           false,
           "locate_submit_type", "get_submit_type",
           "locate_submit_type_filter", "filter_submit_type_results");
@@ -571,7 +624,7 @@
     if (results.isEmpty()) {
       // Should never occur for a well written rule
       log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
-          + change.getId() + " of " + getProject().getName()
+          + getChange().getId() + " of " + getProject().getName()
           + " has no solution.");
       return typeRuleError("Project submit rule has no solution");
     }
@@ -579,7 +632,7 @@
     Term typeTerm = results.get(0);
     if (!typeTerm.isSymbol()) {
       log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
-          + change.getId() + " of " + getProject().getName()
+          + getChange().getId() + " of " + getProject().getName()
           + " did not return a symbol.");
       return typeRuleError("Project submit rule has invalid solution");
     }
@@ -593,9 +646,14 @@
     }
   }
 
+  private List<SubmitRecord> logInvalidResult(Term rule, Term record, String reason) {
+    return logRuleError("Submit rule " + rule + " for change " + getChange().getId()
+        + " of " + getProject().getName() + " output invalid result: " + record
+        + (reason == null ? "" : ". Reason: " + reason));
+  }
+
   private List<SubmitRecord> logInvalidResult(Term rule, Term record) {
-    return logRuleError("Submit rule " + rule + " for change " + change.getId()
-        + " of " + getProject().getName() + " output invalid result: " + record);
+    return logInvalidResult(rule, record, null);
   }
 
   private List<SubmitRecord> logRuleError(String err, Exception e) {
@@ -617,7 +675,7 @@
 
   private SubmitTypeRecord logInvalidType(Term rule, String record) {
     return logTypeRuleError("Submit type rule " + rule + " for change "
-        + change.getId() + " of " + getProject().getName()
+        + getChange().getId() + " of " + getProject().getName()
         + " output invalid result: " + record);
   }
 
@@ -638,11 +696,18 @@
     return rec;
   }
 
-  private void appliedBy(SubmitRecord.Label label, Term status) {
+  private ChangeData changeData(ReviewDb db, @Nullable ChangeData cd) {
+    return cd != null ? cd : changeDataFactory.create(db, this);
+  }
+
+  private void appliedBy(SubmitRecord.Label label, Term status)
+      throws UserTermExpected {
     if (status.isStructure() && status.arity() == 1) {
       Term who = status.arg(0);
       if (isUser(who)) {
         label.appliedBy = new Account.Id(((IntegerTerm) who.arg(0)).intValue());
+      } else {
+        throw new UserTermExpected(label);
       }
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
index 9b9b517..bb620a0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
@@ -17,18 +17,26 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.common.ActionInfo;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicMap.Entry;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
-import com.google.gerrit.server.actions.ActionInfo;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.extensions.webui.UiActions;
 import com.google.gerrit.server.git.TransferConfig;
 import com.google.inject.util.Providers;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 public class ConfigInfo {
   public final String kind = "gerritcodereview#project_config";
@@ -41,6 +49,7 @@
   public MaxObjectSizeLimitInfo maxObjectSizeLimit;
   public SubmitType submitType;
   public Project.State state;
+  public Map<String, Map<String, ConfigParameterInfo>> pluginConfig;
   public Map<String, ActionInfo> actions;
 
   public Map<String, CommentLinkInfo> commentlinks;
@@ -48,6 +57,9 @@
 
   public ConfigInfo(ProjectControl control,
       TransferConfig config,
+      DynamicMap<ProjectConfigEntry> pluginConfigEntries,
+      PluginConfigFactory cfgFactory,
+      AllProjectsNameProvider allProjects,
       DynamicMap<RestView<ProjectResource>> views) {
     ProjectState projectState = control.getProjectState();
     Project p = control.getProject();
@@ -103,6 +115,10 @@
       this.commentlinks.put(cl.name, cl);
     }
 
+    pluginConfig =
+        getPluginConfig(control.getProjectState(), pluginConfigEntries,
+            cfgFactory, allProjects);
+
     actions = Maps.newTreeMap();
     for (UiAction.Description d : UiActions.from(
         views, new ProjectResource(control),
@@ -112,6 +128,64 @@
     this.theme = projectState.getTheme();
   }
 
+  private Map<String, Map<String, ConfigParameterInfo>> getPluginConfig(
+      ProjectState project, DynamicMap<ProjectConfigEntry> pluginConfigEntries,
+      PluginConfigFactory cfgFactory, AllProjectsNameProvider allProjects) {
+    TreeMap<String, Map<String, ConfigParameterInfo>> pluginConfig = new TreeMap<>();
+    for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
+      ProjectConfigEntry configEntry = e.getProvider().get();
+      PluginConfig cfg =
+          cfgFactory.getFromProjectConfig(project, e.getPluginName());
+      String configuredValue = cfg.getString(e.getExportName());
+      ConfigParameterInfo p = new ConfigParameterInfo();
+      p.displayName = configEntry.getDisplayName();
+      p.description = configEntry.getDescription();
+      p.warning = configEntry.getWarning(project);
+      p.type = configEntry.getType();
+      p.permittedValues = configEntry.getPermittedValues();
+      p.editable = configEntry.isEditable(project) ? true : null;
+      if (configEntry.isInheritable()
+          && !allProjects.get().equals(project.getProject().getNameKey())) {
+        PluginConfig cfgWithInheritance =
+            cfgFactory.getFromProjectConfigWithInheritance(project,
+                e.getPluginName());
+        p.inheritable = true;
+        p.value = cfgWithInheritance.getString(e.getExportName(), configEntry.getDefaultValue());
+        p.configuredValue = configuredValue;
+        p.inheritedValue = getInheritedValue(project, cfgFactory, e);
+      } else {
+        if (configEntry.getType() == ProjectConfigEntry.Type.ARRAY) {
+          p.values = Arrays.asList(cfg.getStringList(e.getExportName()));
+        } else {
+          p.value = configuredValue != null ? configuredValue : configEntry.getDefaultValue();
+        }
+      }
+      Map<String, ConfigParameterInfo> pc = pluginConfig.get(e.getPluginName());
+      if (pc == null) {
+        pc = new TreeMap<>();
+        pluginConfig.put(e.getPluginName(), pc);
+      }
+      pc.put(e.getExportName(), p);
+    }
+    return !pluginConfig.isEmpty() ? pluginConfig : null;
+  }
+
+  private String getInheritedValue(ProjectState project,
+      PluginConfigFactory cfgFactory, Entry<ProjectConfigEntry> e) {
+    ProjectConfigEntry configEntry = e.getProvider().get();
+    ProjectState parent = Iterables.getFirst(project.parents(), null);
+    String inheritedValue = configEntry.getDefaultValue();
+    if (parent != null) {
+      PluginConfig parentCfgWithInheritance =
+          cfgFactory.getFromProjectConfigWithInheritance(parent,
+              e.getPluginName());
+      inheritedValue =
+          parentCfgWithInheritance.getString(e.getExportName(),
+              configEntry.getDefaultValue());
+    }
+    return inheritedValue;
+  }
+
   public static class InheritedBooleanInfo {
     public Boolean value;
     public InheritableBoolean configuredValue;
@@ -123,4 +197,18 @@
     public String configuredValue;
     public String inheritedValue;
   }
+
+  public static class ConfigParameterInfo {
+    public String displayName;
+    public String description;
+    public String warning;
+    public ProjectConfigEntry.Type type;
+    public String value;
+    public Boolean editable;
+    public Boolean inheritable;
+    public String configuredValue;
+    public String inheritedValue;
+    public List<String> permittedValues;
+    public List<String> values;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index f854b58..8e8c844 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -53,14 +53,14 @@
 public class CreateBranch implements RestModifyView<ProjectResource, Input> {
   private static final Logger log = LoggerFactory.getLogger(CreateBranch.class);
 
-  static class Input {
-    String ref;
+  public static class Input {
+    public String ref;
 
     @DefaultInput
-    String revision;
+    public String revision;
   }
 
-  static interface Factory {
+  public static interface Factory {
     CreateBranch create(String ref);
   }
 
@@ -164,11 +164,7 @@
           }
         }
 
-        BranchInfo b = new BranchInfo();
-        b.ref = ref;
-        b.revision = revid.getName();
-        b.setCanDelete(refControl.canDelete());
-        return b;
+        return new BranchInfo(ref, revid.getName(), refControl.canDelete());
       } catch (IOException err) {
         log.error("Cannot create branch \"" + name + "\"", err);
         throw err;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index 0d4f336..5f3d2da 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -20,7 +20,10 @@
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.errors.ProjectCreationFailedException;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -29,9 +32,14 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.group.GroupsCollection;
 import com.google.gerrit.server.project.CreateProject.Input;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
+import com.google.gerrit.server.project.PutConfig.ConfigValue;
+import com.google.gerrit.server.validators.ProjectCreationValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
@@ -40,23 +48,25 @@
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 @RequiresCapability(GlobalCapability.CREATE_PROJECT)
 public class CreateProject implements RestModifyView<TopLevelResource, Input> {
   public static class Input {
-    String name;
-    String parent;
-    String description;
-    boolean permissionsOnly;
-    boolean createEmptyCommit;
-    SubmitType submitType;
-    List<String> branches;
-    List<String> owners;
-    InheritableBoolean useContributorAgreements;
-    InheritableBoolean useSignedOffBy;
-    InheritableBoolean useContentMerge;
-    InheritableBoolean requireChangeId;
-    String maxObjectSizeLimit;
+    public String name;
+    public String parent;
+    public String description;
+    public boolean permissionsOnly;
+    public boolean createEmptyCommit;
+    public SubmitType submitType;
+    public List<String> branches;
+    public List<String> owners;
+    public InheritableBoolean useContributorAgreements;
+    public InheritableBoolean useSignedOffBy;
+    public InheritableBoolean useContentMerge;
+    public InheritableBoolean requireChangeId;
+    public String maxObjectSizeLimit;
+    public Map<String, Map<String, ConfigValue>> pluginConfigValues;
   }
 
   public static interface Factory {
@@ -66,25 +76,37 @@
   private final PerformCreateProject.Factory createProjectFactory;
   private final Provider<ProjectsCollection> projectsCollection;
   private final Provider<GroupsCollection> groupsCollection;
+  private final DynamicSet<ProjectCreationValidationListener> projectCreationValidationListeners;
   private final ProjectJson json;
+  private final ProjectControl.GenericFactory projectControlFactory;
+  private final Provider<CurrentUser> currentUser;
+  private final Provider<PutConfig> putConfig;
   private final String name;
 
   @Inject
   CreateProject(PerformCreateProject.Factory performCreateProjectFactory,
       Provider<ProjectsCollection> projectsCollection,
       Provider<GroupsCollection> groupsCollection, ProjectJson json,
+      DynamicSet<ProjectCreationValidationListener> projectCreationValidationListeners,
+      ProjectControl.GenericFactory projectControlFactory,
+      Provider<CurrentUser> currentUser, Provider<PutConfig> putConfig,
       @Assisted String name) {
     this.createProjectFactory = performCreateProjectFactory;
     this.projectsCollection = projectsCollection;
     this.groupsCollection = groupsCollection;
+    this.projectCreationValidationListeners = projectCreationValidationListeners;
     this.json = json;
+    this.projectControlFactory = projectControlFactory;
+    this.currentUser = currentUser;
+    this.putConfig = putConfig;
     this.name = name;
   }
 
   @Override
-  public Object apply(TopLevelResource resource, Input input)
+  public Response<ProjectInfo> apply(TopLevelResource resource, Input input)
       throws BadRequestException, UnprocessableEntityException,
-      ProjectCreationFailedException, IOException {
+      ResourceConflictException, ProjectCreationFailedException,
+      ResourceNotFoundException, IOException {
     if (input == null) {
       input = new Input();
     }
@@ -100,8 +122,7 @@
     args.createEmptyCommit = input.createEmptyCommit;
     args.permissionsOnly = input.permissionsOnly;
     args.projectDescription = Strings.emptyToNull(input.description);
-    args.submitType =
-        Objects.firstNonNull(input.submitType, SubmitType.MERGE_IF_NECESSARY);
+    args.submitType = input.submitType;
     args.branch = input.branches;
     if (input.owners != null) {
       List<AccountGroup.UUID> ownerIds =
@@ -129,7 +150,28 @@
       throw new BadRequestException(e.getMessage());
     }
 
+    for (ProjectCreationValidationListener l : projectCreationValidationListeners) {
+      try {
+        l.validateNewProject(args);
+      } catch (ValidationException e) {
+        throw new ResourceConflictException(e.getMessage(), e);
+      }
+    }
+
     Project p = createProjectFactory.create(args).createProject();
+
+    if (input.pluginConfigValues != null) {
+      try {
+        ProjectControl projectControl =
+            projectControlFactory.controlFor(p.getNameKey(), currentUser.get());
+        PutConfig.Input in = new PutConfig.Input();
+        in.pluginConfigValues = input.pluginConfigValues;
+        putConfig.get().apply(new ProjectResource(projectControl), in);
+      } catch (NoSuchProjectException e) {
+        throw new ResourceNotFoundException(p.getName());
+      }
+    }
+
     return Response.created(json.format(p));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
index 6e5ef65..3df8e2f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.project;
 
-import static com.google.gerrit.server.git.GitRepositoryManager.REFS_DASHBOARDS;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index a41c197..600f65d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -59,7 +59,7 @@
   }
 
   @Override
-  public Object apply(BranchResource rsrc, Input input) throws AuthException,
+  public Response<?> apply(BranchResource rsrc, Input input) throws AuthException,
       ResourceConflictException, OrmException, IOException {
     if (!rsrc.getControl().controlForRef(rsrc.getBranchKey()).canDelete()) {
       throw new AuthException("Cannot delete branch");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteDashboard.java
index da1e46b..669f024 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteDashboard.java
@@ -18,11 +18,16 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.project.DashboardsCollection.DashboardInfo;
 import com.google.gerrit.server.project.DeleteDashboard.Input;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import java.io.IOException;
+
 class DeleteDashboard implements RestModifyView<DashboardResource, Input> {
   static class Input {
     String commitMessage;
@@ -36,9 +41,9 @@
   }
 
   @Override
-  public Object apply(DashboardResource resource, Input input)
+  public Response<DashboardInfo> apply(DashboardResource resource, Input input)
       throws AuthException, BadRequestException, ResourceConflictException,
-      Exception {
+      ResourceNotFoundException, MethodNotAllowedException, IOException {
     if (resource.isProjectDefault()) {
       SetDashboard.Input in = new SetDashboard.Input();
       in.commitMessage = input != null ? input.commitMessage : null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java
new file mode 100644
index 0000000..29506c9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FileResource.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.project.BranchResource;
+import com.google.inject.TypeLiteral;
+
+public class FileResource extends BranchResource {
+  public static final TypeLiteral<RestView<FileResource>> FILE_KIND =
+      new TypeLiteral<RestView<FileResource>>() {};
+
+  private final String path;
+
+  public FileResource(BranchResource rsrc, String path) {
+    super(rsrc.getControl(), rsrc.getBranchInfo());
+    this.path = path;
+  }
+
+  public String getPath() {
+    return path;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
new file mode 100644
index 0000000..389189e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesCollection.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.project.BranchResource;
+import com.google.inject.Inject;
+
+public class FilesCollection implements
+    ChildCollection<BranchResource, FileResource> {
+  private final DynamicMap<RestView<FileResource>> views;
+
+  @Inject
+  FilesCollection(DynamicMap<RestView<FileResource>> views) {
+    this.views = views;
+  }
+
+  @Override
+  public RestView<BranchResource> list() throws ResourceNotFoundException {
+    throw new ResourceNotFoundException();
+  }
+
+  @Override
+  public FileResource parse(BranchResource parent, IdString id) {
+    return new FileResource(parent, id.get());
+  }
+
+  @Override
+  public DynamicMap<RestView<FileResource>> views() {
+    return views;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
index ca7f6f0..2911f6e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
@@ -14,13 +14,17 @@
 
 package com.google.gerrit.server.project;
 
-import com.google.common.base.Charsets;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.gerrit.common.data.GarbageCollectionResult;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.server.git.GarbageCollection;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
 import com.google.gerrit.server.project.GarbageCollect.Input;
 import com.google.inject.Inject;
 
@@ -31,24 +35,31 @@
 import java.util.Collections;
 
 @RequiresCapability(GlobalCapability.RUN_GC)
-public class GarbageCollect implements RestModifyView<ProjectResource, Input> {
+public class GarbageCollect implements RestModifyView<ProjectResource, Input>,
+    UiAction<ProjectResource> {
   public static class Input {
+    public boolean showProgress;
   }
 
+  private final boolean canGC;
   private GarbageCollection.Factory garbageCollectionFactory;
 
   @Inject
-  GarbageCollect(GarbageCollection.Factory garbageCollectionFactory) {
+  GarbageCollect(
+      GitRepositoryManager repoManager,
+      GarbageCollection.Factory garbageCollectionFactory) {
+    this.canGC = repoManager instanceof LocalDiskRepositoryManager;
     this.garbageCollectionFactory = garbageCollectionFactory;
   }
 
+  @SuppressWarnings("resource")
   @Override
-  public BinaryResult apply(final ProjectResource rsrc, Input input) {
+  public BinaryResult apply(final ProjectResource rsrc, final Input input) {
     return new BinaryResult() {
       @Override
       public void writeTo(OutputStream out) throws IOException {
         PrintWriter writer = new PrintWriter(
-            new OutputStreamWriter(out, Charsets.UTF_8)) {
+            new OutputStreamWriter(out, UTF_8)) {
           @Override
           public void println() {
             write('\n');
@@ -56,35 +67,43 @@
         };
         try {
           GarbageCollectionResult result = garbageCollectionFactory.create().run(
-              Collections.singletonList(rsrc.getNameKey()), writer);
+              Collections.singletonList(rsrc.getNameKey()), input.showProgress ? writer : null);
+          String msg = "Garbage collection completed successfully.";
           if (result.hasErrors()) {
             for (GarbageCollectionResult.Error e : result.getErrors()) {
-              String msg;
               switch (e.getType()) {
                 case REPOSITORY_NOT_FOUND:
-                  msg = "error: project \"" + e.getProjectName() + "\" not found";
+                  msg = "Error: project \"" + e.getProjectName() + "\" not found.";
                   break;
                 case GC_ALREADY_SCHEDULED:
-                  msg = "error: garbage collection for project \""
-                      + e.getProjectName() + "\" was already scheduled";
+                  msg = "Error: garbage collection for project \""
+                      + e.getProjectName() + "\" was already scheduled.";
                   break;
                 case GC_FAILED:
-                  msg = "error: garbage collection for project \"" + e.getProjectName()
-                      + "\" failed";
+                  msg = "Error: garbage collection for project \"" + e.getProjectName()
+                      + "\" failed.";
                   break;
                 default:
-                  msg = "error: garbage collection for project \"" + e.getProjectName()
-                      + "\" failed: " + e.getType();
+                  msg = "Error: garbage collection for project \"" + e.getProjectName()
+                      + "\" failed: " + e.getType() + ".";
               }
-              writer.println(msg);
             }
           }
+          writer.println(msg);
         } finally {
           writer.flush();
         }
       }
     }.setContentType("text/plain")
-     .setCharacterEncoding(Charsets.UTF_8.name())
+     .setCharacterEncoding(UTF_8.name())
      .disableGzip();
   }
+
+  @Override
+  public UiAction.Description getDescription(ProjectResource rsrc) {
+    return new UiAction.Description()
+        .setLabel("Run GC")
+        .setTitle("Triggers the Git Garbage Collection for this project.")
+        .setVisible(canGC);
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
index 6f78651..88ac83f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
@@ -18,6 +18,9 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.git.TransferConfig;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -25,18 +28,28 @@
 public class GetConfig implements RestReadView<ProjectResource> {
 
   private final TransferConfig config;
+  private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
+  private final PluginConfigFactory cfgFactory;
+  private final AllProjectsNameProvider allProjects;
   private final DynamicMap<RestView<ProjectResource>> views;
 
   @Inject
   public GetConfig(TransferConfig config,
+      DynamicMap<ProjectConfigEntry> pluginConfigEntries,
+      PluginConfigFactory cfgFactory,
+      AllProjectsNameProvider allProjects,
       DynamicMap<RestView<ProjectResource>> views,
       Provider<CurrentUser> currentUser) {
     this.config = config;
+    this.pluginConfigEntries = pluginConfigEntries;
+    this.allProjects = allProjects;
+    this.cfgFactory = cfgFactory;
     this.views = views;
   }
 
   @Override
   public ConfigInfo apply(ProjectResource resource) {
-    return new ConfigInfo(resource.getControl(), config, views);
+    return new ConfigInfo(resource.getControl(), config,
+        pluginConfigEntries, cfgFactory, allProjects, views);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java
new file mode 100644
index 0000000..48a3ced
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetContent.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class GetContent implements RestReadView<FileResource> {
+  private final Provider<com.google.gerrit.server.change.GetContent> getContent;
+
+  @Inject
+  GetContent(Provider<com.google.gerrit.server.change.GetContent> getContent) {
+    this.getContent = getContent;
+  }
+
+  @Override
+  public BinaryResult apply(FileResource rsrc)
+      throws ResourceNotFoundException, IOException {
+    return getContent.get().apply(rsrc.getNameKey(), rsrc.getRef(),
+        rsrc.getPath());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
index 51d6191..8acc29e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
@@ -14,12 +14,13 @@
 
 package com.google.gerrit.server.project;
 
-import static com.google.gerrit.server.git.GitRepositoryManager.REFS_DASHBOARDS;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
 
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.Url;
@@ -45,7 +46,7 @@
 
   @Override
   public DashboardInfo apply(DashboardResource resource)
-      throws ResourceNotFoundException, IOException, ConfigInvalidException {
+      throws ResourceNotFoundException, ResourceConflictException, IOException {
     if (inherited && !resource.isProjectDefault()) {
       // inherited flag can only be used with default.
       throw new ResourceNotFoundException("inherited");
@@ -54,7 +55,11 @@
     String project = resource.getControl().getProject().getName();
     if (resource.isProjectDefault()) {
       // The default is not resolved to a definition yet.
-      resource = defaultOf(resource.getControl());
+      try {
+        resource = defaultOf(resource.getControl());
+      } catch (ConfigInvalidException e) {
+        throw new ResourceConflictException(e.getMessage());
+      }
     }
 
     return DashboardsCollection.parse(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDescription.java
index d8fabab..aeff72f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDescription.java
@@ -20,7 +20,7 @@
 
 class GetDescription implements RestReadView<ProjectResource> {
   @Override
-  public Object apply(ProjectResource resource) {
+  public String apply(ProjectResource resource) {
     Project project = resource.getControl().getProject();
     return Strings.nullToEmpty(project.getDescription());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
index a73676b..af7559a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.auth.AuthException;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 
@@ -54,7 +54,7 @@
         if (rsrc.getControl().controlForRef(n).isVisible()) {
           return n;
         }
-        throw new AuthException();
+        throw new AuthException("not allowed to see HEAD");
       } else if (head.getObjectId() != null) {
         RevWalk rw = new RevWalk(repo);
         try {
@@ -62,16 +62,12 @@
           if (rsrc.getControl().canReadCommit(rw, commit)) {
             return head.getObjectId().name();
           }
-          throw new AuthException();
-        } catch (MissingObjectException e) {
+          throw new AuthException("not allowed to see HEAD");
+        } catch (MissingObjectException | IncorrectObjectTypeException e) {
           if (rsrc.getControl().isOwner()) {
             return head.getObjectId().name();
           }
-          throw new AuthException();
-        } catch (IncorrectObjectTypeException e) {
-          if (rsrc.getControl().isOwner()) {
-            return head.getObjectId().name();
-          }
+          throw new AuthException("not allowed to see HEAD");
         } finally {
           rw.release();
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetParent.java
index 1cebd87..f790b7e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetParent.java
@@ -28,7 +28,7 @@
   }
 
   @Override
-  public Object apply(ProjectResource resource) {
+  public String apply(ProjectResource resource) {
     Project project = resource.getControl().getProject();
     Project.NameKey parentName = project.getParent(allProjectsName);
     return parentName != null ? parentName.get() : "";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java
index a482278..961f7b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.project;
 
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
 import com.google.inject.Inject;
 
 class GetProject implements RestReadView<ProjectResource> {
@@ -27,7 +28,7 @@
   }
 
   @Override
-  public Object apply(ProjectResource rsrc) {
+  public ProjectInfo apply(ProjectResource rsrc) {
     return json.format(rsrc);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index 7ed5b5f..b39e362 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -18,6 +18,7 @@
 import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 
@@ -97,12 +98,9 @@
             target = target.substring(Constants.R_HEADS.length());
           }
 
-          BranchInfo b = new BranchInfo();
-          b.ref = ref.getName();
-          b.revision = target;
+          BranchInfo b = new BranchInfo(ref.getName(), target, false);
 
           if (Constants.HEAD.equals(ref.getName())) {
-            b.setCanDelete(false);
             headBranch = b;
           } else {
             b.setCanDelete(targetRefControl.canDelete());
@@ -115,7 +113,7 @@
         if (refControl.isVisible()) {
           if (ref.getName().startsWith(Constants.R_HEADS)) {
             branches.add(createBranchInfo(ref, refControl, targets));
-          } else if (GitRepositoryManager.REF_CONFIG.equals(ref.getName())) {
+          } else if (RefNames.REFS_CONFIG.equals(ref.getName())) {
             configBranch = createBranchInfo(ref, refControl, targets);
           }
         }
@@ -140,13 +138,9 @@
 
   private static BranchInfo createBranchInfo(Ref ref, RefControl refControl,
       Set<String> targets) {
-    BranchInfo b = new BranchInfo();
-    b.ref = ref.getName();
-    if (ref.getObjectId() != null) {
-      b.revision = ref.getObjectId().name();
-    }
-    b.setCanDelete(!targets.contains(ref.getName()) && refControl.canDelete());
-    return b;
+    return new BranchInfo(ref.getName(),
+        ref.getObjectId() != null ? ref.getObjectId().name() : null,
+        !targets.contains(ref.getName()) && refControl.canDelete());
   }
 
   public static class BranchInfo {
@@ -154,6 +148,12 @@
     public String revision;
     public Boolean canDelete;
 
+    public BranchInfo(String ref, String revision, boolean canDelete) {
+      this.ref = ref;
+      this.revision = revision;
+      this.canDelete = canDelete;
+    }
+
     void setCanDelete(boolean canDelete) {
       this.canDelete = canDelete ? true : null;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
index d4b84dd..58abe40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
@@ -47,6 +47,10 @@
     this.projectNodeFactory = projectNodeFactory;
   }
 
+  public void setRecursive(boolean recursive) {
+    this.recursive = recursive;
+  }
+
   @Override
   public List<ProjectInfo> apply(ProjectResource rsrc) {
     if (recursive) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
index c063618..fbfcc8f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.project;
 
-import static com.google.gerrit.server.git.GitRepositoryManager.REFS_DASHBOARDS;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -53,7 +53,7 @@
   }
 
   @Override
-  public Object apply(ProjectResource resource)
+  public List<?> apply(ProjectResource resource)
       throws ResourceNotFoundException, IOException {
     ProjectControl ctl = resource.getControl();
     String project = ctl.getProject().getName();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index 4f24dea..d0b10dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.StringUtil;
@@ -85,7 +86,7 @@
         Ref head = git.getRef(Constants.HEAD);
         return head != null
           && head.isSymbolic()
-          && GitRepositoryManager.REF_CONFIG.equals(head.getLeaf().getName());
+          && RefNames.REFS_CONFIG.equals(head.getLeaf().getName());
       }
     },
     ALL {
@@ -109,7 +110,7 @@
   @Option(name = "--format", usage = "(deprecated) output format")
   private OutputFormat format = OutputFormat.TEXT;
 
-  @Option(name = "--show-branch", aliases = {"-b"}, multiValued = true,
+  @Option(name = "--show-branch", aliases = {"-b"},
       usage = "displays the sha of each project in the specified branch")
   public void addShowBranch(String branch) {
     showBranch.add(branch);
@@ -278,6 +279,7 @@
               info.name = parentState.getProject().getName();
               info.description = Strings.emptyToNull(
                   parentState.getProject().getDescription());
+              info.state = parentState.getProject().getState();
             } else {
               rejected.add(parentState.getProject().getName());
               continue;
@@ -320,6 +322,8 @@
             info.description = Strings.emptyToNull(e.getProject().getDescription());
           }
 
+          info.state = e.getProject().getState();
+
           try {
             if (!showBranch.isEmpty()) {
               Repository git = repoManager.openRepository(projectName);
@@ -485,8 +489,8 @@
   }
 
   private static boolean hasValidRef(List<Ref> refs) {
-    for (int i = 0; i < refs.size(); i++) {
-      if (refs.get(i) != null) {
+    for (Ref ref : refs) {
+      if (ref != null) {
         return true;
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index 0ef875e..1b6c410 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -18,6 +18,7 @@
 import static com.google.gerrit.server.project.ChildProjectResource.CHILD_PROJECT_KIND;
 import static com.google.gerrit.server.project.DashboardResource.DASHBOARD_KIND;
 import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
+import static com.google.gerrit.server.project.FileResource.FILE_KIND;
 
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.RestApiModule;
@@ -33,6 +34,7 @@
     DynamicMap.mapOf(binder(), CHILD_PROJECT_KIND);
     DynamicMap.mapOf(binder(), BRANCH_KIND);
     DynamicMap.mapOf(binder(), DASHBOARD_KIND);
+    DynamicMap.mapOf(binder(), FILE_KIND);
 
     put(PROJECT_KIND).to(PutProject.class);
     get(PROJECT_KIND).to(GetProject.class);
@@ -57,6 +59,8 @@
     get(BRANCH_KIND).to(GetBranch.class);
     delete(BRANCH_KIND).to(DeleteBranch.class);
     install(new FactoryModuleBuilder().build(CreateBranch.Factory.class));
+    child(BRANCH_KIND, "files").to(FilesCollection.class);
+    get(FILE_KIND, "content").to(GetContent.class);
 
     child(PROJECT_KIND, "dashboards").to(DashboardsCollection.class);
     get(DASHBOARD_KIND).to(GetDashboard.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
index 86410af..ca006de 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.common.base.Objects;
 import com.google.gerrit.common.ProjectUtil;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GroupDescription;
@@ -25,9 +26,12 @@
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.ProjectOwnerGroups;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -40,6 +44,7 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -66,6 +71,7 @@
     PerformCreateProject create(CreateProjectArgs createProjectArgs);
   }
 
+  private final Config cfg;
   private final Set<AccountGroup.UUID> projectOwnerGroups;
   private final IdentifiedUser currentUser;
   private final GitRepositoryManager repoManager;
@@ -78,13 +84,15 @@
   private final MetaDataUpdate.User metaDataUpdateFactory;
 
   @Inject
-  PerformCreateProject(@ProjectOwnerGroups Set<AccountGroup.UUID> pOwnerGroups,
+  PerformCreateProject(@GerritServerConfig Config cfg,
+      @ProjectOwnerGroups Set<AccountGroup.UUID> pOwnerGroups,
       IdentifiedUser identifiedUser, GitRepositoryManager gitRepoManager,
       GitReferenceUpdated referenceUpdated,
       DynamicSet<NewProjectCreatedListener> createdListener,
       @GerritPersonIdent PersonIdent personIdent, GroupBackend groupBackend,
       MetaDataUpdate.User metaDataUpdateFactory,
       @Assisted CreateProjectArgs createPArgs, ProjectCache pCache) {
+    this.cfg = cfg;
     this.projectOwnerGroups = pOwnerGroups;
     this.currentUser = identifiedUser;
     this.repoManager = gitRepoManager;
@@ -102,7 +110,7 @@
     final Project.NameKey nameKey = createProjectArgs.getProject();
     try {
       final String head =
-          createProjectArgs.permissionsOnly ? GitRepositoryManager.REF_CONFIG
+          createProjectArgs.permissionsOnly ? RefNames.REFS_CONFIG
               : createProjectArgs.branch.get(0);
       final Repository repo = repoManager.createRepository(nameKey);
       try {
@@ -179,7 +187,8 @@
 
       Project newProject = config.getProject();
       newProject.setDescription(createProjectArgs.projectDescription);
-      newProject.setSubmitType(createProjectArgs.submitType);
+      newProject.setSubmitType(Objects.firstNonNull(createProjectArgs.submitType,
+          cfg.getEnum("repository", "*", "defaultSubmitType", SubmitType.MERGE_IF_NECESSARY)));
       newProject
           .setUseContributorAgreements(createProjectArgs.contributorAgreements);
       newProject.setUseSignedOffBy(createProjectArgs.signedOffBy);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
index 6647652..3b6ce91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
@@ -27,6 +27,7 @@
 import com.google.inject.Singleton;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -59,22 +60,23 @@
      *        priority order (project specific definitions must appear before
      *        inherited ones).
      * @param ref reference being accessed.
-     * @param username if the reference is a per-user reference, access sections
-     *        using the parameter variable "${username}" will first have {@code
-     *        username} inserted into them before seeing if they apply to the
-     *        reference named by {@code ref}. If null, per-user references are
-     *        ignored.
+     * @param usernames if the reference is a per-user reference, access sections
+     *        using the parameter variable "${username}" will first have each of
+     *        {@code usernames} inserted into them before seeing if they apply to
+     *        the reference named by {@code ref}. If null or empty, per-user
+     *        references are ignored.
      * @return map of permissions that apply to this reference, keyed by
      *         permission name.
      */
     PermissionCollection filter(Iterable<SectionMatcher> matcherList,
-        String ref, String username) {
+        String ref, Collection<String> usernames) {
       if (isRE(ref)) {
         ref = RefControl.shortestExample(ref);
       } else if (ref.endsWith("/*")) {
         ref = ref.substring(0, ref.length() - 1);
       }
 
+      boolean hasUsernames = usernames != null && !usernames.isEmpty();
       boolean perUser = false;
       Map<AccessSection, Project.NameKey> sectionToProject = Maps.newLinkedHashMap();
       for (SectionMatcher sm : matcherList) {
@@ -90,12 +92,17 @@
         // that will never be shared with non-user references, and the per-user
         // references are usually less frequent than the non-user references.
         //
-        if (username != null && !perUser
-            && sm.matcher instanceof RefPatternMatcher.ExpandParameters) {
-          perUser = ((RefPatternMatcher.ExpandParameters) sm.matcher).matchPrefix(ref);
-        }
-
-        if (sm.match(ref, username)) {
+        if (hasUsernames) {
+          if (!perUser && sm.matcher instanceof RefPatternMatcher.ExpandParameters) {
+            perUser = ((RefPatternMatcher.ExpandParameters) sm.matcher).matchPrefix(ref);
+          }
+          for (String username : usernames) {
+            if (sm.match(ref, username)) {
+              sectionToProject.put(sm.section, sm.project);
+              break;
+            }
+          }
+        } else if (sm.match(ref, null)) {
           sectionToProject.put(sm.section, sm.project);
         }
       }
@@ -117,7 +124,7 @@
 
           for (PermissionRule rule : permission.getRules()) {
             SeenRule s = new SeenRule(section, permission, rule);
-            boolean addRule = false;
+            boolean addRule;
             if (rule.isBlock()) {
               addRule = seenBlockingRules.add(s);
             } else {
@@ -140,21 +147,20 @@
         }
       }
 
-      return new PermissionCollection(permissions, ruleProps,
-          perUser ? username : null);
+      return new PermissionCollection(permissions, ruleProps, perUser);
     }
   }
 
   private final Map<String, List<PermissionRule>> rules;
   private final Map<PermissionRule, ProjectRef> ruleProps;
-  private final String username;
+  private final boolean perUser;
 
   private PermissionCollection(Map<String, List<PermissionRule>> rules,
       Map<PermissionRule, ProjectRef> ruleProps,
-      String username) {
+      boolean perUser) {
     this.rules = rules;
     this.ruleProps = ruleProps;
-    this.username = username;
+    this.perUser = perUser;
   }
 
   /**
@@ -162,7 +168,7 @@
    *         this collection, making the results user specific.
    */
   public boolean isUserSpecific() {
-    return username != null;
+    return perUser;
   }
 
   /**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index ddfe752..f71c4c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -14,8 +14,7 @@
 
 package com.google.gerrit.server.project;
 
-import static org.eclipse.jgit.lib.RefDatabase.ALL;
-
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.PageLinks;
@@ -34,16 +33,19 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.InternalUser;
+import com.google.gerrit.server.change.IncludedInResolver;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.GitReceivePackGroups;
 import com.google.gerrit.server.config.GitUploadPackGroups;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -58,7 +60,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.Map.Entry;
 
 /** Access control management for a user accessing a project's data. */
 public class ProjectControl {
@@ -144,6 +145,7 @@
   private final CurrentUser user;
   private final ProjectState state;
   private final GitRepositoryManager repoManager;
+  private final ChangeControl.AssistedFactory changeControlFactory;
   private final PermissionCollection.Factory permissionFilter;
   private final Collection<ContributorAgreement> contributorAgreements;
 
@@ -156,11 +158,15 @@
   @Inject
   ProjectControl(@GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups,
       @GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
-      final ProjectCache pc, final PermissionCollection.Factory permissionFilter,
-      final GitRepositoryManager repoManager,
-      @CanonicalWebUrl @Nullable final String canonicalWebUrl,
-      @Assisted CurrentUser who, @Assisted ProjectState ps) {
+      ProjectCache pc,
+      PermissionCollection.Factory permissionFilter,
+      GitRepositoryManager repoManager,
+      ChangeControl.AssistedFactory changeControlFactory,
+      @CanonicalWebUrl @Nullable String canonicalWebUrl,
+      @Assisted CurrentUser who,
+      @Assisted ProjectState ps) {
     this.repoManager = repoManager;
+    this.changeControlFactory = changeControlFactory;
     this.uploadGroups = uploadGroups;
     this.receiveGroups = receiveGroups;
     this.permissionFilter = permissionFilter;
@@ -178,7 +184,7 @@
   }
 
   public ChangeControl controlFor(final Change change) {
-    return new ChangeControl(controlForRef(change.getDest()), change);
+    return changeControlFactory.create(controlForRef(change.getDest()), change);
   }
 
   public RefControl controlForRef(Branch.NameKey ref) {
@@ -191,8 +197,15 @@
     }
     RefControl ctl = refControls.get(refName);
     if (ctl == null) {
+      ImmutableList.Builder<String> usernames = ImmutableList.<String> builder();
+      if (user.getUserName() != null) {
+        usernames.add(user.getUserName());
+      }
+      if (user instanceof IdentifiedUser) {
+        usernames.addAll(((IdentifiedUser) user).getEmailAddresses());
+      }
       PermissionCollection relevant =
-          permissionFilter.filter(access(), refName, user.getUserName());
+          permissionFilter.filter(access(), refName, usernames.build());
       ctl = new RefControl(this, refName, relevant);
       refControls.put(refName, ctl);
     }
@@ -463,9 +476,19 @@
     return match(rule.getGroup().getUUID());
   }
 
+  boolean match(PermissionRule rule, boolean isChangeOwner) {
+    return match(rule.getGroup().getUUID(), isChangeOwner);
+  }
+
   boolean match(AccountGroup.UUID uuid) {
-    if (AccountGroup.PROJECT_OWNERS.equals(uuid)) {
+    return match(uuid, false);
+  }
+
+  boolean match(AccountGroup.UUID uuid, boolean isChangeOwner) {
+    if (SystemGroupBackend.PROJECT_OWNERS.equals(uuid)) {
       return isDeclaredOwner();
+    } else if (SystemGroupBackend.CHANGE_OWNER.equals(uuid)) {
+      return isChangeOwner;
     } else {
       return user.getEffectiveGroups().contains(uuid);
     }
@@ -498,22 +521,20 @@
     try {
       Repository repo = repoManager.openRepository(projName);
       try {
-        Map<String, Ref> allRefs = repo.getRefDatabase().getRefs(ALL);
-        for (Entry<String, Ref> entry : allRefs.entrySet()) {
-          String refName = entry.getKey();
-          if (!refName.startsWith("refs/heads") && !refName.startsWith("refs/tags")) {
-            continue;
+        RefDatabase refDb = repo.getRefDatabase();
+        List<Ref> allRefs = Lists.newLinkedList();
+        allRefs.addAll(refDb.getRefs(Constants.R_HEADS).values());
+        allRefs.addAll(refDb.getRefs(Constants.R_TAGS).values());
+        List<Ref> canReadRefs = Lists.newLinkedList();
+        for (Ref r : allRefs) {
+          if (controlForRef(r.getName()).canPerform(Permission.READ)) {
+            canReadRefs.add(r);
           }
-          RevCommit tip;
-          try {
-            tip = rw.parseCommit(entry.getValue().getObjectId());
-          } catch (IncorrectObjectTypeException e) {
-            continue;
-          }
-          if (rw.isMergedInto(commit, tip)
-              && controlForRef(entry.getKey()).canPerform(Permission.READ)) {
-            return true;
-          }
+        }
+
+        if (!canReadRefs.isEmpty() && IncludedInResolver.includedInOne(
+            repo, rw, commit, canReadRefs)) {
+          return true;
         }
       } finally {
         repo.close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
index 8db5cbb..72910a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
@@ -41,6 +41,7 @@
     Project.NameKey parentName = p.getParent(allProjects);
     info.parent = parentName != null ? parentName.get() : null;
     info.description = Strings.emptyToNull(p.getDescription());
+    info.state = p.getState();
     info.finish();
     return info;
   }
@@ -51,6 +52,7 @@
     public String name;
     public String parent;
     public String description;
+    public Project.State state;
     public Map<String, String> branches;
 
     void finish() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java
index 5b4b334..cc85312 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNode.java
@@ -46,7 +46,7 @@
   /**
    * Returns the project parent name.
    *
-   * @return Project parent name, <code>null</code> for the 'All-Projects' root
+   * @return Project parent name, {@code null} for the 'All-Projects' root
    *         project
    */
   public Project.NameKey getParentName() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
index f4449f0..459e392 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
@@ -41,6 +41,10 @@
     return control.getProject().getNameKey();
   }
 
+  public Project.State getState() {
+    return control.getProject().getState();
+  }
+
   public ProjectControl getControl() {
     return control;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 800fa6e..50f232a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -14,7 +14,8 @@
 
 package com.google.gerrit.server.project;
 
-import com.google.common.base.Charsets;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
@@ -31,6 +32,7 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.RulesCache;
 import com.google.gerrit.server.CurrentUser;
@@ -40,12 +42,14 @@
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.git.ProjectLevelConfig;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
 import com.googlecode.prolog_cafe.compiler.CompileException;
 import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.slf4j.Logger;
@@ -83,6 +87,7 @@
   private final List<CommentLinkInfo> commentLinks;
 
   private final ProjectConfig config;
+  private final Map<String, ProjectLevelConfig> configs;
   private final Set<AccountGroup.UUID> localOwners;
 
   /** Prolog rule state. */
@@ -121,6 +126,7 @@
     this.rulesCache = rulesCache;
     this.commentLinks = commentLinks;
     this.config = config;
+    this.configs = Maps.newHashMap();
     this.capabilities = isAllProjects
       ? new CapabilityCollection(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
       : null;
@@ -160,7 +166,7 @@
     try {
       Repository git = gitMgr.openRepository(getProject().getNameKey());
       try {
-        Ref ref = git.getRef(GitRepositoryManager.REF_CONFIG);
+        Ref ref = git.getRef(RefNames.REFS_CONFIG);
         if (ref == null || ref.getObjectId() == null) {
           return true;
         }
@@ -216,6 +222,29 @@
     return config;
   }
 
+  public ProjectLevelConfig getConfig(String fileName) {
+    if (configs.containsKey(fileName)) {
+      return configs.get(fileName);
+    }
+
+    ProjectLevelConfig cfg = new ProjectLevelConfig(fileName, this);
+    try {
+      Repository git = gitMgr.openRepository(getProject().getNameKey());
+      try {
+        cfg.load(git);
+      } finally {
+        git.close();
+      }
+    } catch (IOException e) {
+      log.warn("Failed to load " + fileName + " for " + getProject().getName(), e);
+    } catch (ConfigInvalidException e) {
+      log.warn("Failed to load " + fileName + " for " + getProject().getName(), e);
+    }
+
+    configs.put(fileName, cfg);
+    return cfg;
+  }
+
   public long getMaxObjectSizeLimit() {
     return config.getMaxObjectSizeLimit();
   }
@@ -454,7 +483,7 @@
   }
 
   private String readFile(File f) throws IOException {
-    return f.exists() ? Files.toString(f, Charsets.UTF_8) : null;
+    return f.exists() ? Files.toString(f, UTF_8) : null;
   }
 
   private boolean getInheritableBoolean(Function<Project, InheritableBoolean> func) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 4d17627..c481eb4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.gerrit.common.ChangeHooks;
@@ -27,7 +29,12 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
@@ -40,10 +47,21 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 public class PutConfig implements RestModifyView<ProjectResource, Input> {
+  private static final Logger log = LoggerFactory.getLogger(PutConfig.class);
+  public static class ConfigValue {
+    public String value;
+    public List<String> values;
+  }
   public static class Input {
     public String description;
     public InheritableBoolean useContributorAgreements;
@@ -53,13 +71,17 @@
     public String maxObjectSizeLimit;
     public SubmitType submitType;
     public Project.State state;
+    public Map<String, Map<String, ConfigValue>> pluginConfigValues;
   }
 
   private final MetaDataUpdate.User metaDataUpdateFactory;
   private final ProjectCache projectCache;
-  private final Provider<CurrentUser> self;
+  private final GitRepositoryManager gitMgr;
   private final ProjectState.Factory projectStateFactory;
   private final TransferConfig config;
+  private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
+  private final PluginConfigFactory cfgFactory;
+  private final AllProjectsNameProvider allProjects;
   private final DynamicMap<RestView<ProjectResource>> views;
   private final Provider<CurrentUser> currentUser;
   private final ChangeHooks hooks;
@@ -67,17 +89,23 @@
   @Inject
   PutConfig(MetaDataUpdate.User metaDataUpdateFactory,
       ProjectCache projectCache,
-      Provider<CurrentUser> self,
+      GitRepositoryManager gitMgr,
       ProjectState.Factory projectStateFactory,
       TransferConfig config,
+      DynamicMap<ProjectConfigEntry> pluginConfigEntries,
+      PluginConfigFactory cfgFactory,
+      AllProjectsNameProvider allProjects,
       DynamicMap<RestView<ProjectResource>> views,
       ChangeHooks hooks,
       Provider<CurrentUser> currentUser) {
     this.metaDataUpdateFactory = metaDataUpdateFactory;
     this.projectCache = projectCache;
-    this.self = self;
+    this.gitMgr = gitMgr;
     this.projectStateFactory = projectStateFactory;
     this.config = config;
+    this.pluginConfigEntries = pluginConfigEntries;
+    this.cfgFactory = cfgFactory;
+    this.allProjects = allProjects;
     this.views = views;
     this.hooks = hooks;
     this.currentUser = currentUser;
@@ -135,6 +163,11 @@
         p.setState(input.state);
       }
 
+      if (input.pluginConfigValues != null) {
+        setPluginConfigValues(rsrc.getControl().getProjectState(),
+            projectConfig, input.pluginConfigValues);
+      }
+
       md.setMessage("Modified project settings\n");
       try {
         ObjectId baseRev = projectConfig.getRevision();
@@ -143,11 +176,11 @@
         if (!Objects.equal(baseRev, commitRev)) {
           IdentifiedUser user = (IdentifiedUser) currentUser.get();
           hooks.doRefUpdatedHook(
-            new Branch.NameKey(projectName, GitRepositoryManager.REF_CONFIG),
+            new Branch.NameKey(projectName, RefNames.REFS_CONFIG),
             baseRev, commitRev, user.getAccount());
         };
-        (new PerRequestProjectControlCache(projectCache, self.get()))
-            .evict(projectConfig.getProject());
+        projectCache.evict(projectConfig.getProject());
+        gitMgr.setProjectDescription(projectName, p.getDescription());
       } catch (IOException e) {
         if (e.getCause() instanceof ConfigInvalidException) {
           throw new ResourceConflictException("Cannot update " + projectName
@@ -158,9 +191,8 @@
       }
 
       ProjectState state = projectStateFactory.create(projectConfig);
-      return new ConfigInfo(
-          state.controlFor(currentUser.get()),
-          config, views);
+      return new ConfigInfo(state.controlFor(currentUser.get()), config,
+          pluginConfigEntries, cfgFactory, allProjects, views);
     } catch (ConfigInvalidException err) {
       throw new ResourceConflictException("Cannot read project " + projectName, err);
     } catch (IOException err) {
@@ -169,4 +201,99 @@
       md.close();
     }
   }
+
+  private void setPluginConfigValues(ProjectState projectState,
+      ProjectConfig projectConfig, Map<String, Map<String, ConfigValue>> pluginConfigValues)
+      throws BadRequestException {
+    for (Entry<String, Map<String, ConfigValue>> e : pluginConfigValues.entrySet()) {
+      String pluginName = e.getKey();
+      PluginConfig cfg = projectConfig.getPluginConfig(pluginName);
+      for (Entry<String, ConfigValue> v : e.getValue().entrySet()) {
+        ProjectConfigEntry projectConfigEntry =
+            pluginConfigEntries.get(pluginName, v.getKey());
+        if (projectConfigEntry != null) {
+          if (!isValidParameterName(v.getKey())) {
+            log.warn(String.format(
+                "Parameter name '%s' must match '^[a-zA-Z0-9]+[a-zA-Z0-9-]*$'", v.getKey()));
+            continue;
+          }
+          String oldValue = cfg.getString(v.getKey());
+          String value = v.getValue().value;
+          if (projectConfigEntry.getType() == ProjectConfigEntry.Type.ARRAY) {
+            List<String> l = Arrays.asList(cfg.getStringList(v.getKey()));
+            oldValue = Joiner.on("\n").join(l);
+            value = Joiner.on("\n").join(v.getValue().values);
+          }
+          if (Strings.emptyToNull(value) != null) {
+            if (!value.equals(oldValue)) {
+              validateProjectConfigEntryIsEditable(projectConfigEntry,
+                  projectState, e.getKey(), pluginName);
+              try {
+                switch (projectConfigEntry.getType()) {
+                  case BOOLEAN:
+                    boolean newBooleanValue = Boolean.parseBoolean(value);
+                    cfg.setBoolean(v.getKey(), newBooleanValue);
+                    break;
+                  case INT:
+                    int newIntValue = Integer.parseInt(value);
+                    cfg.setInt(v.getKey(), newIntValue);
+                    break;
+                  case LONG:
+                    long newLongValue = Long.parseLong(value);
+                    cfg.setLong(v.getKey(), newLongValue);
+                    break;
+                  case LIST:
+                    if (!projectConfigEntry.getPermittedValues().contains(value)) {
+                      throw new BadRequestException(String.format(
+                          "The value '%s' is not permitted for parameter '%s' of plugin '"
+                              + pluginName + "'", value, v.getKey()));
+                    }
+                  case STRING:
+                    cfg.setString(v.getKey(), value);
+                    break;
+                  case ARRAY:
+                    cfg.setStringList(v.getKey(), v.getValue().values);
+                    break;
+                  default:
+                    log.warn(String.format(
+                        "The type '%s' of parameter '%s' is not supported.",
+                        projectConfigEntry.getType().name(), v.getKey()));
+                }
+              } catch (NumberFormatException ex) {
+                throw new BadRequestException(String.format(
+                    "The value '%s' of config parameter '%s' of plugin '%s' is invalid: %s",
+                    v.getValue(), v.getKey(), pluginName, ex.getMessage()));
+              }
+            }
+          } else {
+            if (oldValue != null) {
+              validateProjectConfigEntryIsEditable(projectConfigEntry,
+                  projectState, e.getKey(), pluginName);
+              cfg.unset(v.getKey());
+            }
+          }
+        } else {
+          throw new BadRequestException(String.format(
+              "The config parameter '%s' of plugin '%s' does not exist.",
+              v.getKey(), pluginName));
+        }
+      }
+    }
+  }
+
+  private static void validateProjectConfigEntryIsEditable(
+      ProjectConfigEntry projectConfigEntry, ProjectState projectState,
+      String parameterName, String pluginName) throws BadRequestException {
+    if (!projectConfigEntry.isEditable(projectState)) {
+      throw new BadRequestException(String.format(
+          "Not allowed to set parameter '%s' of plugin '%s' on project '%s'.",
+          parameterName, pluginName, projectState.getProject().getName()));
+    }
+  }
+
+  private static boolean isValidParameterName(String name) {
+    return CharMatcher.JAVA_LETTER_OR_DIGIT
+        .or(CharMatcher.is('-'))
+        .matchesAllOf(name) && !name.startsWith("-");
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
index e794678..17ab7b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
@@ -18,7 +18,6 @@
 import com.google.common.base.Strings;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -26,6 +25,7 @@
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
@@ -63,8 +63,8 @@
   }
 
   @Override
-  public Object apply(ProjectResource resource, Input input)
-      throws AuthException, BadRequestException, ResourceConflictException,
+  public Response<String> apply(ProjectResource resource, Input input)
+      throws AuthException, ResourceConflictException,
       ResourceNotFoundException, IOException {
     if (input == null) {
       input = new Input(); // Delete would set description to null.
@@ -96,7 +96,7 @@
         // Only fire hook if project was actually changed.
         if (!Objects.equal(baseRev, commitRev)) {
           hooks.doRefUpdatedHook(
-            new Branch.NameKey(resource.getNameKey(), GitRepositoryManager.REF_CONFIG),
+            new Branch.NameKey(resource.getNameKey(), RefNames.REFS_CONFIG),
             baseRev, commitRev, user.getAccount());
         }
         cache.evict(ctl.getProject());
@@ -105,8 +105,8 @@
             project.getDescription());
 
         return Strings.isNullOrEmpty(project.getDescription())
-            ? Response.none()
-            : project.getDescription();
+            ? Response.<String>none()
+            : Response.ok(project.getDescription());
       } finally {
         md.close();
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java
index 0a96cb5..836899a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java
@@ -15,12 +15,13 @@
 package com.google.gerrit.server.project;
 
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.server.project.CreateProject.Input;
 
 public class PutProject implements RestModifyView<ProjectResource, Input> {
   @Override
-  public Object apply(ProjectResource resource, Input input)
+  public Response<?> apply(ProjectResource resource, Input input)
       throws ResourceConflictException {
     throw new ResourceConflictException("Project \"" + resource.getName()
         + "\" already exists");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 6b1073d..7c956da 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -22,12 +22,12 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.common.errors.InvalidNameException;
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.InternalUser;
-import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.group.SystemGroupBackend;
 
 import dk.brics.automaton.RegExp;
 
@@ -125,8 +125,7 @@
     for (PermissionRule rule : access) {
       if (rule.isBlock()) {
         blocks.add(relevant.getRuleProps(rule));
-      } else if (rule.getGroup().getUUID().equals(AccountGroup.ANONYMOUS_USERS)
-          || rule.getGroup().getUUID().equals(AccountGroup.REGISTERED_USERS)) {
+      } else if (SystemGroupBackend.isAnonymousOrRegistered(rule.getGroup())) {
         allows.add(relevant.getRuleProps(rule));
       }
     }
@@ -162,7 +161,7 @@
 
   /** @return true if this user can submit patch sets to this ref */
   public boolean canSubmit() {
-    if (GitRepositoryManager.REF_CONFIG.equals(refName)) {
+    if (RefNames.REFS_CONFIG.equals(refName)) {
       // Always allow project owners to submit configuration changes.
       // Submitting configuration changes modifies the access control
       // rules. Allowing this to be done by a non-project-owner opens
@@ -174,9 +173,14 @@
         && canWrite();
   }
 
+  /** @return true if this user was granted submitAs to this ref */
+  public boolean canSubmitAs() {
+    return canPerform(Permission.SUBMIT_AS);
+  }
+
   /** @return true if the user can update the reference as a fast-forward. */
   public boolean canUpdate() {
-    if (GitRepositoryManager.REF_CONFIG.equals(refName)
+    if (RefNames.REFS_CONFIG.equals(refName)
         && !projectControl.isOwner()) {
       // Pushing requires being at least project owner, in addition to push.
       // Pushing configuration changes modifies the access control
@@ -212,7 +216,7 @@
   }
 
   private boolean canPushWithForce() {
-    if (!canWrite() || (GitRepositoryManager.REF_CONFIG.equals(refName)
+    if (!canWrite() || (RefNames.REFS_CONFIG.equals(refName)
         && !projectControl.isOwner())) {
       // Pushing requires being at least project owner, in addition to push.
       // Pushing configuration changes modifies the access control
@@ -300,7 +304,7 @@
    * @return {@code true} if the user specified can delete a Git ref.
    */
   public boolean canDelete() {
-    if (!canWrite() || (GitRepositoryManager.REF_CONFIG.equals(refName))) {
+    if (!canWrite() || (RefNames.REFS_CONFIG.equals(refName))) {
       // Never allow removal of the refs/meta/config branch.
       // Deleting the branch would destroy all Gerrit specific
       // metadata about the project, including its access rules.
@@ -382,14 +386,14 @@
   }
 
   /** All value ranges of any allowed label permission. */
-  public List<PermissionRange> getLabelRanges() {
+  public List<PermissionRange> getLabelRanges(boolean isChangeOwner) {
     List<PermissionRange> r = new ArrayList<PermissionRange>();
     for (Map.Entry<String, List<PermissionRule>> e : relevant.getDeclaredPermissions()) {
       if (Permission.isLabel(e.getKey())) {
         int min = 0;
         int max = 0;
         for (PermissionRule rule : e.getValue()) {
-          if (projectControl.match(rule)) {
+          if (projectControl.match(rule, isChangeOwner)) {
             min = Math.min(min, rule.getMin());
             max = Math.max(max, rule.getMax());
           }
@@ -404,8 +408,13 @@
 
   /** The range of permitted values associated with a label permission. */
   public PermissionRange getRange(String permission) {
+    return getRange(permission, false);
+  }
+
+  /** The range of permitted values associated with a label permission. */
+  public PermissionRange getRange(String permission, boolean isChangeOwner) {
     if (Permission.hasRange(permission)) {
-      return toRange(permission, access(permission));
+      return toRange(permission, access(permission, isChangeOwner));
     }
     return null;
   }
@@ -530,6 +539,12 @@
 
   /** Rules for the given permission, or the empty list. */
   private List<PermissionRule> access(String permissionName) {
+    return access(permissionName, false);
+  }
+
+  /** Rules for the given permission, or the empty list. */
+  private List<PermissionRule> access(String permissionName,
+      boolean isChangeOwner) {
     List<PermissionRule> rules = effective.get(permissionName);
     if (rules != null) {
       return rules;
@@ -543,7 +558,7 @@
     }
 
     if (rules.size() == 1) {
-      if (!projectControl.match(rules.get(0))) {
+      if (!projectControl.match(rules.get(0), isChangeOwner)) {
         rules = Collections.emptyList();
       }
       effective.put(permissionName, rules);
@@ -552,7 +567,7 @@
 
     List<PermissionRule> mine = new ArrayList<PermissionRule>(rules.size());
     for (PermissionRule rule : rules) {
-      if (projectControl.match(rule)) {
+      if (projectControl.match(rule, isChangeOwner)) {
         mine.add(rule);
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
index b71d194..dcf6841 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -20,7 +20,7 @@
 import java.util.Collections;
 import java.util.regex.Pattern;
 
-abstract class RefPatternMatcher {
+public abstract class RefPatternMatcher {
   public static RefPatternMatcher getMatcher(String pattern) {
     if (pattern.contains("${")) {
       return new ExpandParameters(pattern);
@@ -33,7 +33,7 @@
     }
   }
 
-  abstract boolean match(String ref, String username);
+  public abstract boolean match(String ref, String username);
 
   private static class Exact extends RefPatternMatcher {
     private final String expect;
@@ -43,7 +43,7 @@
     }
 
     @Override
-    boolean match(String ref, String username) {
+    public boolean match(String ref, String username) {
       return expect.equals(ref);
     }
   }
@@ -56,7 +56,7 @@
     }
 
     @Override
-    boolean match(String ref, String username) {
+    public boolean match(String ref, String username) {
       return ref.startsWith(prefix);
     }
   }
@@ -69,7 +69,7 @@
     }
 
     @Override
-    boolean match(String ref, String username) {
+    public boolean match(String ref, String username) {
       return pattern.matcher(ref).matches();
     }
   }
@@ -98,21 +98,21 @@
     }
 
     @Override
-    boolean match(String ref, String username) {
+    public boolean match(String ref, String username) {
       if (!ref.startsWith(prefix) || username == null) {
         return false;
       }
 
       String u;
       if (isRE(template.getPattern())) {
-        u = username.replace(".", "\\.");
+        u = Pattern.quote(username);
       } else {
         u = username;
       }
 
       RefPatternMatcher next =
           getMatcher(template.replace(Collections.singletonMap("username", u)));
-      return next != null ? next.match(ref, username) : false;
+      return next != null && next.match(ref, username);
     }
 
     boolean matchPrefix(String ref) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java
index 44c8b9a..4d1688f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java
@@ -45,7 +45,7 @@
   }
 
   @Override
-  boolean match(String ref, String username) {
+  public boolean match(String ref, String username) {
     return this.matcher.match(ref, username);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java
index 930da12..8319bbd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDashboard.java
@@ -19,11 +19,14 @@
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.server.project.SetDashboard.Input;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import java.io.IOException;
+
 class SetDashboard implements RestModifyView<DashboardResource, Input> {
   static class Input {
     @DefaultInput
@@ -41,7 +44,7 @@
   @Override
   public Object apply(DashboardResource resource, Input input)
       throws AuthException, BadRequestException, ResourceConflictException,
-      Exception {
+      MethodNotAllowedException, ResourceNotFoundException, IOException {
     if (resource.isProjectDefault()) {
       return defaultSetter.get().apply(resource, input);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
index 3f70bc2..a323ec8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
@@ -36,6 +36,8 @@
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.kohsuke.args4j.Option;
 
+import java.io.IOException;
+
 class SetDefaultDashboard implements RestModifyView<DashboardResource, Input> {
   private final ProjectCache cache;
   private final MetaDataUpdate.Server updateFactory;
@@ -57,9 +59,9 @@
   }
 
   @Override
-  public Object apply(DashboardResource resource, Input input)
+  public Response<DashboardInfo> apply(DashboardResource resource, Input input)
       throws AuthException, BadRequestException, ResourceConflictException,
-      Exception {
+      ResourceNotFoundException, IOException {
     if (input == null) {
       input = new Input(); // Delete would set input to null.
     }
@@ -79,6 +81,8 @@
             IdString.fromUrl(input.id));
       } catch (ResourceNotFoundException e) {
         throw new BadRequestException("dashboard " + input.id + " not found");
+      } catch (ConfigInvalidException e) {
+        throw new ResourceConflictException(e.getMessage());
       }
     }
 
@@ -109,7 +113,7 @@
         if (target != null) {
           DashboardInfo info = get.get().apply(target);
           info.isDefault = true;
-          return info;
+          return Response.ok(info);
         }
         return Response.none();
       } finally {
@@ -136,14 +140,13 @@
     }
 
     @Override
-    public Object apply(ProjectResource resource, Input input)
+    public Response<DashboardInfo> apply(ProjectResource resource, Input input)
         throws AuthException, BadRequestException, ResourceConflictException,
-        Exception {
+        ResourceNotFoundException, IOException {
       SetDefaultDashboard set = setDefault.get();
       set.inherited = inherited;
-      return Response.created(set.apply(
-          DashboardResource.projectDefault(resource.getControl()),
-          input));
+      return set.apply(
+          DashboardResource.projectDefault(resource.getControl()), input);
     }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
index 2f8e26d..05b392b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.server.project;
 
 import com.google.common.base.Strings;
+import com.google.gerrit.extensions.events.HeadUpdatedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -31,10 +33,14 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 
 public class SetHead implements RestModifyView<ProjectResource, Input> {
+  private static final Logger log = LoggerFactory.getLogger(SetHead.class);
+
   static class Input {
     @DefaultInput
     String ref;
@@ -42,15 +48,19 @@
 
   private final GitRepositoryManager repoManager;
   private final Provider<IdentifiedUser> identifiedUser;
+  private final DynamicSet<HeadUpdatedListener> headUpdatedListener;
 
   @Inject
-  SetHead(GitRepositoryManager repoManager, Provider<IdentifiedUser> identifiedUser) {
+  SetHead(GitRepositoryManager repoManager,
+      Provider<IdentifiedUser> identifiedUser,
+      DynamicSet<HeadUpdatedListener> headUpdatedListener) {
     this.repoManager = repoManager;
     this.identifiedUser = identifiedUser;
+    this.headUpdatedListener = headUpdatedListener;
   }
 
   @Override
-  public String apply(ProjectResource rsrc, Input input) throws AuthException,
+  public String apply(final ProjectResource rsrc, Input input) throws AuthException,
       ResourceNotFoundException, BadRequestException,
       UnprocessableEntityException, IOException {
     if (!rsrc.getControl().isOwner()) {
@@ -72,10 +82,12 @@
             "Ref Not Found: %s", ref));
       }
 
-      if (!repo.getRef(Constants.HEAD).getTarget().getName().equals(ref)) {
+      final String oldHead = repo.getRef(Constants.HEAD).getTarget().getName();
+      final String newHead = ref;
+      if (!oldHead.equals(newHead)) {
         final RefUpdate u = repo.updateRef(Constants.HEAD, true);
         u.setRefLogIdent(identifiedUser.get().newRefLogIdent());
-        RefUpdate.Result res = u.link(ref);
+        RefUpdate.Result res = u.link(newHead);
         switch(res) {
           case NO_CHANGE:
           case RENAMED:
@@ -85,6 +97,30 @@
           default:
             throw new IOException("Setting HEAD failed with " + res);
         }
+
+        HeadUpdatedListener.Event event = new HeadUpdatedListener.Event() {
+          @Override
+          public String getProjectName() {
+            return rsrc.getNameKey().get();
+          }
+
+          @Override
+          public String getOldHeadName() {
+            return oldHead;
+          }
+
+          @Override
+          public String getNewHeadName() {
+            return newHead;
+          }
+        };
+        for (HeadUpdatedListener l : headUpdatedListener) {
+          try {
+            l.onHeadUpdated(event);
+          } catch (RuntimeException e) {
+            log.warn("Failure in HeadUpdatedListener", e);
+          }
+        }
       }
       return ref;
     } catch (RepositoryNotFoundException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
index 999358c..4f0941d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
@@ -19,7 +19,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -38,11 +37,11 @@
 
 import java.io.IOException;
 
-class SetParent implements RestModifyView<ProjectResource, Input> {
-  static class Input {
+public class SetParent implements RestModifyView<ProjectResource, Input> {
+  public static class Input {
     @DefaultInput
-    String parent;
-    String commitMessage;
+    public String parent;
+    public String commitMessage;
   }
 
   private final ProjectCache cache;
@@ -59,45 +58,18 @@
   }
 
   @Override
-  public String apply(final ProjectResource rsrc, Input input) throws AuthException,
-      BadRequestException, ResourceConflictException,
+  public String apply(final ProjectResource rsrc, Input input)
+      throws AuthException, ResourceConflictException,
       ResourceNotFoundException, UnprocessableEntityException, IOException {
     ProjectControl ctl = rsrc.getControl();
+    validateParentUpdate(ctl, input.parent, true);
     IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
-    if (!user.getCapabilities().canAdministrateServer()) {
-      throw new AuthException("not administrator");
-    }
-
-    if (rsrc.getNameKey().equals(allProjects)) {
-      throw new ResourceConflictException("cannot set parent of "
-          + allProjects.get());
-    }
-
-    input.parent = Strings.emptyToNull(input.parent);
-    if (input.parent != null) {
-      ProjectState parent = cache.get(new Project.NameKey(input.parent));
-      if (parent == null) {
-        throw new UnprocessableEntityException("parent project " + input.parent
-            + " not found");
-      }
-
-      if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() {
-        @Override
-        public boolean apply(ProjectState input) {
-          return input.getProject().getNameKey().equals(rsrc.getNameKey());
-        }
-      }).isPresent()) {
-        throw new ResourceConflictException("cycle exists between "
-            + rsrc.getName() + " and " + parent.getProject().getName());
-      }
-    }
-
     try {
       MetaDataUpdate md = updateFactory.create(rsrc.getNameKey());
       try {
         ProjectConfig config = ProjectConfig.read(md);
         Project project = config.getProject();
-        project.setParentName(input.parent);
+        project.setParentName(Strings.emptyToNull(input.parent));
 
         String msg = Strings.emptyToNull(input.commitMessage);
         if (msg == null) {
@@ -124,4 +96,39 @@
           "invalid project.config: %s", e.getMessage()));
     }
   }
+
+  public void validateParentUpdate(final ProjectControl ctl, String newParent,
+      boolean checkIfAdmin) throws AuthException, ResourceConflictException,
+      UnprocessableEntityException {
+    IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
+    if (checkIfAdmin && !user.getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not administrator");
+    }
+
+    if (ctl.getProject().getNameKey().equals(allProjects)) {
+      throw new ResourceConflictException("cannot set parent of "
+          + allProjects.get());
+    }
+
+    newParent = Strings.emptyToNull(newParent);
+    if (newParent != null) {
+      ProjectState parent = cache.get(new Project.NameKey(newParent));
+      if (parent == null) {
+        throw new UnprocessableEntityException("parent project " + newParent
+            + " not found");
+      }
+
+      if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() {
+        @Override
+        public boolean apply(ProjectState input) {
+          return input.getProject().getNameKey()
+              .equals(ctl.getProject().getNameKey());
+        }
+      }).isPresent()) {
+        throw new ResourceConflictException("cycle exists between "
+            + ctl.getProject().getName() + " and "
+            + parent.getProject().getName());
+      }
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index 87161c5..f7c3c9d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -14,8 +14,9 @@
 
 package com.google.gerrit.server.project;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import com.google.common.collect.Lists;
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -70,7 +71,7 @@
    */
   public SubmitRuleEvaluator(ReviewDb db, PatchSet patchSet,
       ProjectControl projectControl,
-      ChangeControl changeControl, Change change, @Nullable ChangeData cd,
+      ChangeControl changeControl, Change change, ChangeData cd,
       boolean fastEvalLabels,
       String userRuleLocatorName, String userRuleWrapperName,
       String filterRuleLocatorName, String filterRuleWrapperName) {
@@ -88,14 +89,14 @@
    *        rule.
    * @param filterRuleWrapperName The name of the rule used to evaluate the
    *        filter rule.
-   * @param skipSubmitFilters if <code>true</code> submit filter will not be
+   * @param skipSubmitFilters if {@code true} submit filter will not be
    *        applied
    * @param rules when non-null the rules will be read from this input stream
    *        instead of refs/meta/config:rules.pl file
    */
   public SubmitRuleEvaluator(ReviewDb db, PatchSet patchSet,
       ProjectControl projectControl,
-      ChangeControl changeControl, Change change, @Nullable ChangeData cd,
+      ChangeControl changeControl, Change change, ChangeData cd,
       boolean fastEvalLabels,
       String userRuleLocatorName, String userRuleWrapperName,
       String filterRuleLocatorName, String filterRuleWrapperName,
@@ -105,7 +106,7 @@
     this.projectControl = projectControl;
     this.changeControl = changeControl;
     this.change = change;
-    this.cd = cd;
+    this.cd = checkNotNull(cd, "ChangeData");
     this.fastEvalLabels = fastEvalLabels;
     this.userRuleLocatorName = userRuleLocatorName;
     this.userRuleWrapperName = userRuleWrapperName;
@@ -182,7 +183,6 @@
           + getProjectName(), err);
     }
     env.set(StoredValues.REVIEW_DB, db);
-    env.set(StoredValues.CHANGE, change);
     env.set(StoredValues.CHANGE_DATA, cd);
     env.set(StoredValues.PATCH_SET, patchSet);
     env.set(StoredValues.CHANGE_CONTROL, changeControl);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
index 5966dde..915a364 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
@@ -27,6 +27,7 @@
   private final List<Predicate<T>> children;
   private final int cost;
 
+  @SafeVarargs
   protected AndPredicate(final Predicate<T>... that) {
     this(Arrays.asList(that));
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
index 8a0ac68..2c91809 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
@@ -27,6 +27,7 @@
   private final List<Predicate<T>> children;
   private final int cost;
 
+  @SafeVarargs
   protected OrPredicate(final Predicate<T>... that) {
     this(Arrays.asList(that));
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
index b0f12b5..c134458 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
@@ -40,7 +40,7 @@
  * other predicates should override {@link #getChildren()} to return the list of
  * children nested within the predicate.
  *
- * @type <T> type of object the predicate can evaluate in memory.
+ * @param <T> type of object the predicate can evaluate in memory.
  */
 public abstract class Predicate<T> {
   /** A predicate that matches any input, always, with no cost. */
@@ -50,6 +50,7 @@
   }
 
   /** Combine the passed predicates into a single AND node. */
+  @SafeVarargs
   public static <T> Predicate<T> and(final Predicate<T>... that) {
     if (that.length == 1) {
       return that[0];
@@ -67,6 +68,7 @@
   }
 
   /** Combine the passed predicates into a single OR node. */
+  @SafeVarargs
   public static <T> Predicate<T> or(final Predicate<T>... that) {
     if (that.length == 1) {
       return that[0];
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
index 6088d8e..c0f8414 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryRewriter.java
@@ -51,7 +51,7 @@
  * Rewrite methods are applied in order by declared name, so naming methods with
  * a numeric prefix to ensure a specific ordering (if required) is suggested.
  *
- * @type <T> type of object the predicate can evaluate in memory.
+ * @param <T> type of object the predicate can evaluate in memory.
  */
 public abstract class QueryRewriter<T> {
   /**
@@ -97,7 +97,7 @@
   }
 
   /** Combine the passed predicates into a single AND node. */
-  public Predicate<T> and(Predicate<T>... that) {
+  public Predicate<T> and(@SuppressWarnings("unchecked") Predicate<T>... that) {
     return and(Arrays.asList(that));
   }
 
@@ -107,7 +107,7 @@
   }
 
   /** Combine the passed predicates into a single OR node. */
-  public Predicate<T> or(Predicate<T>... that) {
+  public Predicate<T> or(@SuppressWarnings("unchecked") Predicate<T>... that) {
     return or(Arrays.asList(that));
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/RewritePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/RewritePredicate.java
index 1c5c89c..f4a2111 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/RewritePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/RewritePredicate.java
@@ -14,7 +14,8 @@
 
 package com.google.gerrit.server.query;
 
-import java.util.Arrays;
+import com.google.common.collect.ImmutableList;
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -24,10 +25,10 @@
   private String name = getClass().getSimpleName();
   private List<Predicate<T>> children = Collections.emptyList();
 
-  protected void init(String name, Predicate<T>... args) {
+  protected void init(String name, @SuppressWarnings("unchecked") Predicate<T>... args) {
     this.init = true;
     this.name = name;
-    this.children = Arrays.asList(args);
+    this.children = ImmutableList.copyOf(args);
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
new file mode 100644
index 0000000..cb0038c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.index.TimestampRangePredicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
+
+import java.util.Date;
+
+public class AfterPredicate extends TimestampRangePredicate<ChangeData> {
+  private final Date cut;
+
+  AfterPredicate(Schema<ChangeData> schema, String value)
+      throws QueryParseException {
+    super(updatedField(schema), ChangeQueryBuilder.FIELD_BEFORE, value);
+    cut = parse(value);
+  }
+
+  @Override
+  public Date getMinTimestamp() {
+    return cut;
+  }
+
+  @Override
+  public Date getMaxTimestamp() {
+    return new Date(Long.MAX_VALUE);
+  }
+
+  @Override
+  public boolean match(ChangeData cd) throws OrmException {
+    return cd.change().getLastUpdatedOn().getTime() >= cut.getTime();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
index 9a867d4..1894596 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
@@ -18,49 +18,38 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.TimestampRangePredicate;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 import java.sql.Timestamp;
 
 public class AgePredicate extends TimestampRangePredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
   private final long cut;
 
-  AgePredicate(Provider<ReviewDb> dbProvider, String value) {
-    super(ChangeField.UPDATED, ChangeQueryBuilder.FIELD_AGE, value);
-    this.dbProvider = dbProvider;
+  AgePredicate(Schema<ChangeData> schema, String value) {
+    super(updatedField(schema), ChangeQueryBuilder.FIELD_AGE, value);
 
     long s = ConfigUtil.getTimeUnit(getValue(), 0, SECONDS);
     long ms = MILLISECONDS.convert(s, SECONDS);
     this.cut = TimeUtil.nowMs() - ms;
   }
 
+  @Override
   public Timestamp getMinTimestamp() {
     return new Timestamp(0);
   }
 
+  @Override
   public Timestamp getMaxTimestamp() {
     return new Timestamp(cut);
   }
 
-  long getCut() {
-    return cut + 1;
-  }
-
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
+    Change change = object.change();
     return change != null && change.getLastUpdatedOn().getTime() <= cut;
   }
-
-  @Override
-  public int getCost() {
-    return 1;
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
index 5282b49..ad0ec3c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
@@ -14,19 +14,20 @@
 
 package com.google.gerrit.server.query.change;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import com.google.common.base.Function;
 import com.google.common.base.Throwables;
 import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.query.AndPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.ListResultSet;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.OrmRuntimeException;
 import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -73,13 +74,18 @@
     return r;
   }
 
-  private final Provider<ReviewDb> db;
+  private final int start;
   private int cardinality = -1;
 
-  public AndSource(Provider<ReviewDb> db,
-      Collection<? extends Predicate<ChangeData>> that) {
+  public AndSource(Collection<? extends Predicate<ChangeData>> that) {
+    this(that, 0);
+  }
+
+  public AndSource(Collection<? extends Predicate<ChangeData>> that,
+      int start) {
     super(sort(that));
-    this.db = db;
+    checkArgument(start >= 0, "negative start: %s", start);
+    this.start = start;
   }
 
   @Override
@@ -103,9 +109,13 @@
     if (source == null) {
       throw new OrmException("No ChangeDataSource: " + this);
     }
+    @SuppressWarnings("unchecked")
+    Predicate<ChangeData> pred = (Predicate<ChangeData>) source;
+    boolean useSortKey = ChangeQueryBuilder.hasSortKey(pred);
 
     List<ChangeData> r = Lists.newArrayList();
     ChangeData last = null;
+    int nextStart = 0;
     boolean skipped = false;
     for (ChangeData data : buffer(source, source.read())) {
       if (match(data)) {
@@ -114,6 +124,7 @@
         skipped = true;
       }
       last = data;
+      nextStart++;
     }
 
     if (skipped && last != null && source instanceof Paginated) {
@@ -122,21 +133,31 @@
       // limit the caller wants.  Restart the source and continue.
       //
       Paginated p = (Paginated) source;
-      while (skipped && r.size() < p.limit()) {
+      while (skipped && r.size() < p.limit() + start) {
         ChangeData lastBeforeRestart = last;
         skipped = false;
         last = null;
-        for (ChangeData data : buffer(source, p.restart(lastBeforeRestart))) {
+        ResultSet<ChangeData> next = useSortKey
+            ? p.restart(lastBeforeRestart)
+            : p.restart(nextStart);
+
+        for (ChangeData data : buffer(source, next)) {
           if (match(data)) {
             r.add(data);
           } else {
             skipped = true;
           }
           last = data;
+          nextStart++;
         }
       }
     }
 
+    if (start >= r.size()) {
+      r = ImmutableList.of();
+    } else if (start > 0) {
+      r = ImmutableList.copyOf(r.subList(start, r.size()));
+    }
     return new ListResultSet<ChangeData>(r);
   }
 
@@ -151,7 +172,7 @@
         public List<ChangeData> apply(List<ChangeData> buffer) {
           if (loadChange) {
             try {
-              ChangeData.ensureChangeLoaded(db, buffer);
+              ChangeData.ensureChangeLoaded(buffer);
             } catch (OrmException e) {
               throw new OrmRuntimeException(e);
             }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
index f81b0ca..8ce6fa3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
@@ -14,42 +14,34 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.index.ChangeIndex;
-import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.query.IntPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryRewriter;
+import com.google.inject.Inject;
 import com.google.inject.OutOfScopeException;
 import com.google.inject.Provider;
 import com.google.inject.name.Named;
 
-public abstract class BasicChangeRewrites extends QueryRewriter<ChangeData> {
-  protected static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
+public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
+  private static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
       new ChangeQueryBuilder.Arguments( //
           new InvalidProvider<ReviewDb>(), //
           new InvalidProvider<ChangeQueryRewriter>(), //
-          null, null, null, null, null, //
-          null, null, null, null, null), null);
+          null, null, null, null, null, null, null, //
+          null, null, null, null, null, null, null, null, null, null), null);
 
-  static Schema<ChangeData> schema(@Nullable IndexCollection indexes) {
-    ChangeIndex index = indexes != null ? indexes.getSearchIndex() : null;
-    return index != null ? index.getSchema() : null;
-  }
+  private static final QueryRewriter.Definition<ChangeData, BasicChangeRewrites> mydef =
+      new QueryRewriter.Definition<ChangeData, BasicChangeRewrites>(
+          BasicChangeRewrites.class, BUILDER);
 
   protected final Provider<ReviewDb> dbProvider;
-  private final IndexCollection indexes;
 
-  protected BasicChangeRewrites(
-      Definition<ChangeData, ? extends QueryRewriter<ChangeData>> def,
-      Provider<ReviewDb> dbProvider, IndexCollection indexes) {
-    super(def);
+  @Inject
+  public BasicChangeRewrites(Provider<ReviewDb> dbProvider) {
+    super(mydef);
     this.dbProvider = dbProvider;
-    this.indexes = indexes;
   }
 
   @Rewrite("-status:open")
@@ -69,7 +61,7 @@
   @Rewrite("-status:merged")
   public Predicate<ChangeData> r00_notMerged() {
     return or(ChangeStatusPredicate.open(dbProvider),
-        new ChangeStatusPredicate(dbProvider, Change.Status.ABANDONED));
+        new ChangeStatusPredicate(Change.Status.ABANDONED));
   }
 
   @SuppressWarnings("unchecked")
@@ -77,15 +69,7 @@
   @Rewrite("-status:abandoned")
   public Predicate<ChangeData> r00_notAbandoned() {
     return or(ChangeStatusPredicate.open(dbProvider),
-        new ChangeStatusPredicate(dbProvider, Change.Status.MERGED));
-  }
-
-  @SuppressWarnings("unchecked")
-  @NoCostComputation
-  @Rewrite("sortkey_before:z A=(age:*)")
-  public Predicate<ChangeData> r00_ageToSortKey(@Named("A") AgePredicate a) {
-    String cut = ChangeUtil.sortKey(a.getCut(), Integer.MAX_VALUE);
-    return and(new SortKeyPredicate.Before(schema(indexes), dbProvider, cut), a);
+        new ChangeStatusPredicate(Change.Status.MERGED));
   }
 
   @NoCostComputation
@@ -96,21 +80,6 @@
     return a.intValue() <= b.intValue() ? a : b;
   }
 
-  @NoCostComputation
-  @Rewrite("A=(sortkey_before:*) B=(sortkey_before:*)")
-  public Predicate<ChangeData> r00_oldestSortKey(
-      @Named("A") SortKeyPredicate.Before a,
-      @Named("B") SortKeyPredicate.Before b) {
-    return a.getValue().compareTo(b.getValue()) <= 0 ? a : b;
-  }
-
-  @NoCostComputation
-  @Rewrite("A=(sortkey_after:*) B=(sortkey_after:*)")
-  public Predicate<ChangeData> r00_newestSortKey(
-      @Named("A") SortKeyPredicate.After a, @Named("B") SortKeyPredicate.After b) {
-    return a.getValue().compareTo(b.getValue()) >= 0 ? a : b;
-  }
-
   private static final class InvalidProvider<T> implements Provider<T> {
     @Override
     public T get() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
new file mode 100644
index 0000000..f724676
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.index.TimestampRangePredicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
+
+import java.util.Date;
+
+public class BeforePredicate extends TimestampRangePredicate<ChangeData> {
+  private final Date cut;
+
+  BeforePredicate(Schema<ChangeData> schema, String value)
+      throws QueryParseException {
+    super(updatedField(schema), ChangeQueryBuilder.FIELD_BEFORE, value);
+    cut = parse(value);
+  }
+
+  @Override
+  public Date getMinTimestamp() {
+    return new Date(0);
+  }
+
+  @Override
+  public Date getMaxTimestamp() {
+    return cut;
+  }
+
+  @Override
+  public boolean match(ChangeData cd) throws OrmException {
+    return cd.change().getLastUpdatedOn().getTime() <= cut.getTime();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 91b2d9d..333c343 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -14,34 +14,40 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.common.base.Function;
+import static com.google.gerrit.server.ApprovalsUtil.sortApprovals;
+
 import com.google.common.base.Objects;
-import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Ordering;
-import com.google.common.collect.Sets;
+import com.google.common.collect.SetMultimap;
 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.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.client.TrackingId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.notedb.ReviewerState;
 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.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -53,30 +59,15 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 
 import java.io.IOException;
-import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 public class ChangeData {
-  private static Ordering<PatchSetApproval> SORT_APPROVALS = Ordering.natural()
-      .onResultOf(new Function<PatchSetApproval, Timestamp>() {
-            @Override
-            public Timestamp apply(PatchSetApproval a) {
-              return a.getGranted();
-            }
-          });
-
-  public static List<PatchSetApproval> sortApprovals(
-      Iterable<PatchSetApproval> approvals) {
-    return SORT_APPROVALS.sortedCopy(approvals);
-  }
-
-  public static void ensureChangeLoaded(Provider<ReviewDb> db,
-      Iterable<ChangeData> changes) throws OrmException {
+  public static void ensureChangeLoaded(Iterable<ChangeData> changes)
+      throws OrmException {
     Map<Change.Id, ChangeData> missing = Maps.newHashMap();
     for (ChangeData cd : changes) {
       if (cd.change == null) {
@@ -84,90 +75,179 @@
       }
     }
     if (!missing.isEmpty()) {
-      for (Change change : db.get().changes().get(missing.keySet())) {
-        missing.get(change.getId()).change = change;
-      }
-    }
-  }
-
-  public static void ensureAllPatchSetsLoaded(Provider<ReviewDb> db,
-      Iterable<ChangeData> changes) throws OrmException {
-    for (ChangeData cd : changes) {
-      cd.patches(db);
-    }
-  }
-
-  public static void ensureCurrentPatchSetLoaded(Provider<ReviewDb> db,
-      Iterable<ChangeData> changes) throws OrmException {
-    Map<PatchSet.Id, ChangeData> missing = Maps.newHashMap();
-    for (ChangeData cd : changes) {
-      if (cd.currentPatchSet == null && cd.patches == null) {
-        missing.put(cd.change(db).currentPatchSetId(), cd);
-      }
-    }
-    if (!missing.isEmpty()) {
-      for (PatchSet ps : db.get().patchSets().get(missing.keySet())) {
-        ChangeData cd = missing.get(ps.getId());
-        cd.currentPatchSet = ps;
-        if (cd.limitedIds == null) {
-          cd.patches = Lists.newArrayList(ps);
+      ChangeData first = missing.values().iterator().next();
+      if (!first.notesMigration.readPatchSetApprovals()) {
+        ReviewDb db = missing.values().iterator().next().db;
+        for (Change change : db.changes().get(missing.keySet())) {
+          missing.get(change.getId()).change = change;
+        }
+      } else {
+        for (ChangeData cd : missing.values()) {
+          cd.change();
         }
       }
     }
   }
 
-  public static void ensureCurrentApprovalsLoaded(Provider<ReviewDb> db,
-      Iterable<ChangeData> changes) throws OrmException {
+  public static void ensureAllPatchSetsLoaded(Iterable<ChangeData> changes)
+      throws OrmException {
+    for (ChangeData cd : changes) {
+      cd.patches();
+    }
+  }
+
+  public static void ensureCurrentPatchSetLoaded(Iterable<ChangeData> changes)
+      throws OrmException {
+    Map<PatchSet.Id, ChangeData> missing = Maps.newHashMap();
+    for (ChangeData cd : changes) {
+      if (cd.currentPatchSet == null && cd.patches == null) {
+        missing.put(cd.change().currentPatchSetId(), cd);
+      }
+    }
+    if (!missing.isEmpty()) {
+      ReviewDb db = missing.values().iterator().next().db;
+      for (PatchSet ps : db.patchSets().get(missing.keySet())) {
+        ChangeData cd = missing.get(ps.getId());
+        cd.currentPatchSet = ps;
+      }
+    }
+  }
+
+  public static void ensureCurrentApprovalsLoaded(Iterable<ChangeData> changes)
+      throws OrmException {
     List<ResultSet<PatchSetApproval>> pending = Lists.newArrayList();
     for (ChangeData cd : changes) {
-      if (cd.currentApprovals == null && cd.limitedApprovals == null) {
-        pending.add(db.get().patchSetApprovals()
-            .byPatchSet(cd.change(db).currentPatchSetId()));
+      if (!cd.notesMigration.readPatchSetApprovals()) {
+        if (cd.currentApprovals == null) {
+          pending.add(cd.db.patchSetApprovals()
+              .byPatchSet(cd.change().currentPatchSetId()));
+        }
+      } else {
+        cd.currentApprovals();
       }
     }
     if (!pending.isEmpty()) {
       int idx = 0;
       for (ChangeData cd : changes) {
-        if (cd.currentApprovals == null && cd.limitedApprovals == null) {
+        if (cd.currentApprovals == null) {
           cd.currentApprovals = sortApprovals(pending.get(idx++));
         }
       }
     }
   }
 
+  public interface Factory {
+    ChangeData create(ReviewDb db, Change.Id id);
+    ChangeData create(ReviewDb db, Change c);
+    ChangeData create(ReviewDb db, ChangeControl c);
+  }
+
+  /**
+   * Create an instance for testing only.
+   * <p>
+   * Attempting to lazy load data will fail with NPEs.
+   *
+   * @param id change ID
+   * @return instance for testing.
+   */
+  static ChangeData createForTest(Change.Id id) {
+    return new ChangeData(null, null, null, null, null, null, null, null, id);
+  }
+
+  private final ReviewDb db;
+  private final GitRepositoryManager repoManager;
+  private final ChangeControl.GenericFactory changeControlFactory;
+  private final IdentifiedUser.GenericFactory userFactory;
+  private final ChangeNotes.Factory notesFactory;
+  private final ApprovalsUtil approvalsUtil;
+  private final PatchListCache patchListCache;
+  private final NotesMigration notesMigration;
   private final Change.Id legacyId;
   private ChangeDataSource returnedBySource;
   private Change change;
+  private ChangeNotes notes;
   private String commitMessage;
   private List<FooterLine> commitFooters;
   private PatchSet currentPatchSet;
-  private Set<PatchSet.Id> limitedIds;
   private Collection<PatchSet> patches;
-  private ListMultimap<PatchSet.Id, PatchSetApproval> limitedApprovals;
   private ListMultimap<PatchSet.Id, PatchSetApproval> allApprovals;
   private List<PatchSetApproval> currentApprovals;
   private List<String> currentFiles;
   private Collection<PatchLineComment> comments;
-  private Collection<TrackingId> trackingIds;
   private CurrentUser visibleTo;
   private ChangeControl changeControl;
   private List<ChangeMessage> messages;
   private List<SubmitRecord> submitRecords;
-  private boolean patchesLoaded;
+  private ChangedLines changedLines;
 
-  public ChangeData(final Change.Id id) {
+  @AssistedInject
+  private ChangeData(
+      GitRepositoryManager repoManager,
+      ChangeControl.GenericFactory changeControlFactory,
+      IdentifiedUser.GenericFactory userFactory,
+      ChangeNotes.Factory notesFactory,
+      ApprovalsUtil approvalsUtil,
+      PatchListCache patchListCache,
+      NotesMigration notesMigration,
+      @Assisted ReviewDb db,
+      @Assisted Change.Id id) {
+    this.db = db;
+    this.repoManager = repoManager;
+    this.changeControlFactory = changeControlFactory;
+    this.userFactory = userFactory;
+    this.notesFactory = notesFactory;
+    this.approvalsUtil = approvalsUtil;
+    this.patchListCache = patchListCache;
+    this.notesMigration = notesMigration;
     legacyId = id;
   }
 
-  public ChangeData(final Change c) {
+  @AssistedInject
+  private ChangeData(
+      GitRepositoryManager repoManager,
+      ChangeControl.GenericFactory changeControlFactory,
+      IdentifiedUser.GenericFactory userFactory,
+      ChangeNotes.Factory notesFactory,
+      ApprovalsUtil approvalsUtil,
+      PatchListCache patchListCache,
+      NotesMigration notesMigration,
+      @Assisted ReviewDb db,
+      @Assisted Change c) {
+    this.db = db;
+    this.repoManager = repoManager;
+    this.changeControlFactory = changeControlFactory;
+    this.userFactory = userFactory;
+    this.notesFactory = notesFactory;
+    this.approvalsUtil = approvalsUtil;
+    this.patchListCache = patchListCache;
+    this.notesMigration = notesMigration;
     legacyId = c.getId();
     change = c;
   }
 
-  public ChangeData(final ChangeControl c) {
+  @AssistedInject
+  private ChangeData(
+      GitRepositoryManager repoManager,
+      ChangeControl.GenericFactory changeControlFactory,
+      IdentifiedUser.GenericFactory userFactory,
+      ChangeNotes.Factory notesFactory,
+      ApprovalsUtil approvalsUtil,
+      PatchListCache patchListCache,
+      NotesMigration notesMigration,
+      @Assisted ReviewDb db,
+      @Assisted ChangeControl c) {
+    this.db = db;
+    this.repoManager = repoManager;
+    this.changeControlFactory = changeControlFactory;
+    this.userFactory = userFactory;
+    this.notesFactory = notesFactory;
+    this.approvalsUtil = approvalsUtil;
+    this.patchListCache = patchListCache;
+    this.notesMigration = notesMigration;
     legacyId = c.getChange().getId();
     change = c.getChange();
     changeControl = c;
+    notes = c.getNotes();
   }
 
   public boolean isFromSource(ChangeDataSource s) {
@@ -178,40 +258,24 @@
     returnedBySource = s;
   }
 
-  public void limitToPatchSets(Collection<PatchSet.Id> ids) {
-    limitedIds = Sets.newLinkedHashSetWithExpectedSize(ids.size());
-    for (PatchSet.Id id : ids) {
-      if (!id.getParentKey().equals(legacyId)) {
-        throw new IllegalArgumentException(String.format(
-            "invalid patch set %s for change %s", id, legacyId));
-      }
-      limitedIds.add(id);
-    }
-  }
-
-  public Collection<PatchSet.Id> getLimitedPatchSets() {
-    return limitedIds;
-  }
-
   public void setCurrentFilePaths(List<String> filePaths) {
     currentFiles = ImmutableList.copyOf(filePaths);
   }
 
-  public List<String> currentFilePaths(Provider<ReviewDb> db,
-      PatchListCache cache) throws OrmException {
+  public List<String> currentFilePaths() throws OrmException {
     if (currentFiles == null) {
-      Change c = change(db);
+      Change c = change();
       if (c == null) {
         return null;
       }
-      PatchSet ps = currentPatchSet(db);
+      PatchSet ps = currentPatchSet();
       if (ps == null) {
         return null;
       }
 
       PatchList p;
       try {
-        p = cache.get(c, ps);
+        p = patchListCache.get(c, ps);
       } catch (PatchListNotAvailableException e) {
         currentFiles = Collections.emptyList();
         return currentFiles;
@@ -243,23 +307,49 @@
     return currentFiles;
   }
 
+  public ChangedLines changedLines() throws OrmException {
+    if (changedLines == null) {
+      Change c = change();
+      if (c == null) {
+        return null;
+      }
+
+      PatchSet ps = currentPatchSet();
+      if (ps == null) {
+        return null;
+      }
+
+      PatchList p;
+      try {
+        p = patchListCache.get(c, ps);
+      } catch (PatchListNotAvailableException e) {
+        return null;
+      }
+
+      changedLines = new ChangedLines(p.getInsertions(), p.getDeletions());
+    }
+    return changedLines;
+  }
+
   public Change.Id getId() {
     return legacyId;
   }
 
-  public Change getChange() {
-    return change;
-  }
-
-  public boolean hasChange() {
-    return change != null;
-  }
-
   boolean fastIsVisibleTo(CurrentUser user) {
     return visibleTo == user;
   }
 
-  public ChangeControl changeControl() {
+  public boolean hasChangeControl() {
+    return changeControl != null;
+  }
+
+  public ChangeControl changeControl() throws NoSuchChangeException,
+      OrmException {
+    if (changeControl == null) {
+      Change c = change();
+      changeControl =
+          changeControlFactory.controlFor(c, userFactory.create(c.getOwner()));
+    }
     return changeControl;
   }
 
@@ -268,24 +358,27 @@
     changeControl = ctl;
   }
 
-  public Change change(Provider<ReviewDb> db) throws OrmException {
+  public Change change() throws OrmException {
     if (change == null) {
-      change = db.get().changes().get(legacyId);
+      change = db.changes().get(legacyId);
     }
     return change;
   }
 
-  void setChange(Change c) {
-    change = c;
+  public ChangeNotes notes() throws OrmException {
+    if (notes == null) {
+      notes = notesFactory.create(change());
+    }
+    return notes;
   }
 
-  public PatchSet currentPatchSet(Provider<ReviewDb> db) throws OrmException {
+  public PatchSet currentPatchSet() throws OrmException {
     if (currentPatchSet == null) {
-      Change c = change(db);
+      Change c = change();
       if (c == null) {
         return null;
       }
-      for (PatchSet p : patches(db)) {
+      for (PatchSet p : patches()) {
         if (p.getId().equals(c.currentPatchSetId())) {
           currentPatchSet = p;
           return p;
@@ -295,20 +388,17 @@
     return currentPatchSet;
   }
 
-  public List<PatchSetApproval> currentApprovals(Provider<ReviewDb> db)
+  public List<PatchSetApproval> currentApprovals()
       throws OrmException {
     if (currentApprovals == null) {
-      Change c = change(db);
+      Change c = change();
       if (c == null) {
         currentApprovals = Collections.emptyList();
       } else if (allApprovals != null) {
         return allApprovals.get(c.currentPatchSetId());
-      } else if (limitedApprovals != null &&
-          (limitedIds == null || limitedIds.contains(c.currentPatchSetId()))) {
-        return limitedApprovals.get(c.currentPatchSetId());
       } else {
-        currentApprovals = sortApprovals(db.get().patchSetApprovals()
-            .byPatchSet(c.currentPatchSetId()));
+        currentApprovals = approvalsUtil.byPatchSet(
+            db, notes(), c.currentPatchSetId());
       }
     }
     return currentApprovals;
@@ -318,28 +408,32 @@
     currentApprovals = approvals;
   }
 
-  public String commitMessage(GitRepositoryManager repoManager,
-      Provider<ReviewDb> db) throws IOException, OrmException {
+  public String commitMessage() throws NoSuchChangeException, IOException,
+      OrmException {
     if (commitMessage == null) {
-      loadCommitData(repoManager, db);
+      loadCommitData();
     }
     return commitMessage;
   }
 
-  public List<FooterLine> commitFooters(GitRepositoryManager repoManager,
-      Provider<ReviewDb> db) throws IOException, OrmException {
+  public List<FooterLine> commitFooters() throws NoSuchChangeException,
+      IOException, OrmException {
     if (commitFooters == null) {
-      loadCommitData(repoManager, db);
+      loadCommitData();
     }
     return commitFooters;
   }
 
-  private void loadCommitData(GitRepositoryManager repoManager,
-      Provider<ReviewDb> db) throws OrmException, RepositoryNotFoundException,
-      IOException, MissingObjectException, IncorrectObjectTypeException {
-    PatchSet.Id psId = change(db).currentPatchSetId();
-    String sha1 = db.get().patchSets().get(psId).getRevision().get();
-    Repository repo = repoManager.openRepository(change.getProject());
+  private void loadCommitData() throws NoSuchChangeException, OrmException,
+      RepositoryNotFoundException, IOException, MissingObjectException,
+      IncorrectObjectTypeException {
+    PatchSet.Id psId = change().currentPatchSetId();
+    PatchSet ps = db.patchSets().get(psId);
+    if (ps == null) {
+      throw new NoSuchChangeException(legacyId);
+    }
+    String sha1 = ps.getRevision().get();
+    Repository repo = repoManager.openRepository(change().getProject());
     try {
       RevWalk walk = new RevWalk(repo);
       try {
@@ -355,120 +449,63 @@
   }
 
   /**
-   * @param db review database.
-   * @return patches for the change. If {@link #limitToPatchSets(Collection)}
-   *     was previously called, only contains patches with the specified IDs.
+   * @return patches for the change.
    * @throws OrmException an error occurred reading the database.
    */
-  public Collection<PatchSet> patches(Provider<ReviewDb> db)
+  public Collection<PatchSet> patches()
       throws OrmException {
-    if (patches == null || !patchesLoaded) {
-      if (limitedIds != null) {
-        patches = Lists.newArrayList();
-        for (PatchSet ps : db.get().patchSets().byChange(legacyId)) {
-          if (limitedIds.contains(ps.getId())) {
-            patches.add(ps);
-          }
-        }
-      } else {
-        patches = db.get().patchSets().byChange(legacyId).toList();
-      }
-      patchesLoaded = true;
+    if (patches == null) {
+      patches = db.patchSets().byChange(legacyId).toList();
     }
     return patches;
   }
 
   /**
-   * @param db review database.
-   * @return patch set approvals for the change in timestamp order. If
-   *     {@link #limitToPatchSets(Collection)} was previously called, only contains
-   *     approvals for the patches with the specified IDs.
+   * @return patch with the given ID, or null if it does not exist.
    * @throws OrmException an error occurred reading the database.
    */
-  public List<PatchSetApproval> approvals(Provider<ReviewDb> db)
-      throws OrmException {
-    return ImmutableList.copyOf(approvalsMap(db).values());
-  }
-
-  /**
-   * @param db review database.
-   * @return patch set approvals for the change, keyed by ID, ordered by
-   *     timestamp within each patch set. If
-   *     {@link #limitToPatchSets(Collection)} was previously called, only
-   *     contains approvals for the patches with the specified IDs.
-   * @throws OrmException an error occurred reading the database.
-   */
-  public ListMultimap<PatchSet.Id, PatchSetApproval> approvalsMap(
-      Provider<ReviewDb> db) throws OrmException {
-    if (limitedApprovals == null) {
-      limitedApprovals = ArrayListMultimap.create();
-      if (allApprovals != null) {
-        for (PatchSet.Id id : limitedIds) {
-          limitedApprovals.putAll(id, allApprovals.get(id));
-        }
-      } else {
-        for (PatchSetApproval psa : sortApprovals(
-            db.get().patchSetApprovals().byChange(legacyId))) {
-          if (limitedIds == null || limitedIds.contains(legacyId)) {
-            limitedApprovals.put(psa.getPatchSetId(), psa);
-          }
-        }
+  public PatchSet patch(PatchSet.Id psId) throws OrmException {
+    if (currentPatchSet != null && currentPatchSet.getId().equals(psId)) {
+      return currentPatchSet;
+    }
+    for (PatchSet ps : patches()) {
+      if (ps.getId().equals(psId)) {
+        return ps;
       }
     }
-    return limitedApprovals;
+    return null;
   }
 
   /**
-   * @param db review database.
-   * @return all patch set approvals for the change in timestamp order
-   *     (regardless of whether {@link #limitToPatchSets(Collection)} was
-   *     previously called).
+   * @return all patch set approvals for the change, keyed by ID, ordered by
+   *     timestamp within each patch set.
    * @throws OrmException an error occurred reading the database.
    */
-  public List<PatchSetApproval> allApprovals(Provider<ReviewDb> db)
+  public ListMultimap<PatchSet.Id, PatchSetApproval> approvals()
       throws OrmException {
-    return ImmutableList.copyOf(allApprovalsMap(db).values());
-  }
-
-  /**
-   * @param db review database.
-   * @return all patch set approvals for the change (regardless of whether
-   *     {@link #limitToPatchSets(Collection)} was previously called), keyed by
-   *     ID, ordered by timestamp within each patch set.
-   * @throws OrmException an error occurred reading the database.
-   */
-  public ListMultimap<PatchSet.Id, PatchSetApproval> allApprovalsMap(
-      Provider<ReviewDb> db) throws OrmException {
     if (allApprovals == null) {
-      allApprovals = ArrayListMultimap.create();
-      for (PatchSetApproval psa : sortApprovals(
-          db.get().patchSetApprovals().byChange(legacyId))) {
-        allApprovals.put(psa.getPatchSetId(), psa);
-      }
+      allApprovals = approvalsUtil.byChange(db, notes());
     }
     return allApprovals;
   }
 
-  public Collection<PatchLineComment> comments(Provider<ReviewDb> db)
+  public SetMultimap<ReviewerState, Account.Id> reviewers()
+      throws OrmException {
+    return approvalsUtil.getReviewers(notes(), approvals().values());
+  }
+
+  public Collection<PatchLineComment> comments()
       throws OrmException {
     if (comments == null) {
-      comments = db.get().patchComments().byChange(legacyId).toList();
+      comments = db.patchComments().byChange(legacyId).toList();
     }
     return comments;
   }
 
-  public Collection<TrackingId> trackingIds(Provider<ReviewDb> db)
-      throws OrmException {
-    if (trackingIds == null) {
-      trackingIds = db.get().trackingIds().byChange(legacyId).toList();
-    }
-    return trackingIds;
-  }
-
-  public List<ChangeMessage> messages(Provider<ReviewDb> db)
+  public List<ChangeMessage> messages()
       throws OrmException {
     if (messages == null) {
-      messages = db.get().changeMessages().byChange(legacyId).toList();
+      messages = db.changeMessages().byChange(legacyId).toList();
     }
     return messages;
   }
@@ -485,4 +522,14 @@
   public String toString() {
     return Objects.toStringHelper(this).addValue(getId()).toString();
   }
+
+  public static class ChangedLines {
+    public final int insertions;
+    public final int deletions;
+
+    ChangedLines(int insertions, int deletions) {
+      this.insertions = insertions;
+      this.deletions = deletions;
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java
index c2eb9b9..f469228 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java
@@ -16,38 +16,31 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Provider;
 
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 abstract class ChangeDataResultSet<T> extends AbstractResultSet<ChangeData> {
-  static ResultSet<ChangeData> change(final ResultSet<Change> rs) {
+  static ResultSet<ChangeData> change(final ChangeData.Factory factory,
+      final Provider<ReviewDb> db, final ResultSet<Change> rs) {
     return new ChangeDataResultSet<Change>(rs, true) {
       @Override
       ChangeData convert(Change t) {
-        return new ChangeData(t);
+        return factory.create(db.get(), t);
       }
     };
   }
 
-  static ResultSet<ChangeData> patchSet(final ResultSet<PatchSet> rs) {
+  static ResultSet<ChangeData> patchSet(final ChangeData.Factory factory,
+      final Provider<ReviewDb> db, final ResultSet<PatchSet> rs) {
     return new ChangeDataResultSet<PatchSet>(rs, false) {
       @Override
       ChangeData convert(PatchSet t) {
-        return new ChangeData(t.getId().getParentKey());
-      }
-    };
-  }
-
-  static ResultSet<ChangeData> patchSetApproval(
-      final ResultSet<PatchSetApproval> rs) {
-    return new ChangeDataResultSet<PatchSetApproval>(rs, false) {
-      @Override
-      ChangeData convert(PatchSetApproval t) {
-        return new ChangeData(t.getPatchSetId().getParentKey());
+        return factory.create(db.get(), t.getId().getParentKey());
       }
     };
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
index 457d657..d1a6c6e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
@@ -15,25 +15,24 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
 
 class ChangeIdPredicate extends IndexPredicate<ChangeData> implements
     ChangeDataSource {
-  private final Provider<ReviewDb> dbProvider;
+  private final Arguments args;
 
-  ChangeIdPredicate(Provider<ReviewDb> dbProvider, String id) {
+  ChangeIdPredicate(Arguments args, String id) {
     super(ChangeField.ID, ChangeQueryBuilder.FIELD_CHANGE, id);
-    this.dbProvider = dbProvider;
+    this.args = args;
   }
 
   @Override
   public boolean match(final ChangeData cd) throws OrmException {
-    Change change = cd.change(dbProvider);
+    Change change = cd.change();
     if (change == null) {
       return false;
     }
@@ -49,8 +48,8 @@
   public ResultSet<ChangeData> read() throws OrmException {
     Change.Key a = new Change.Key(getValue());
     Change.Key b = a.max();
-    return ChangeDataResultSet.change( //
-        dbProvider.get().changes().byKeyRange(a, b));
+    return ChangeDataResultSet.change(args.changeDataFactory, args.db,
+        args.db.get().changes().byKeyRange(a, b));
   }
 
   @Override
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 9fc3dbf..4a6381e 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
@@ -16,6 +16,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -31,12 +32,16 @@
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupBackends;
 import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
 import com.google.gerrit.server.index.ChangeIndex;
 import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ListChildProjects;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.IntPredicate;
 import com.google.gerrit.server.query.Predicate;
@@ -48,6 +53,7 @@
 import com.google.inject.assistedinject.Assisted;
 
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.Config;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -79,20 +85,26 @@
   // NOTE: As new search operations are added, please keep the
   // SearchSuggestOracle up to date.
 
+  public static final String FIELD_AFTER = "after";
   public static final String FIELD_AGE = "age";
+  public static final String FIELD_BEFORE = "before";
   public static final String FIELD_BRANCH = "branch";
   public static final String FIELD_CHANGE = "change";
   public static final String FIELD_COMMENT = "comment";
   public static final String FIELD_COMMIT = "commit";
+  public static final String FIELD_CONFLICTS = "conflicts";
   public static final String FIELD_DRAFTBY = "draftby";
   public static final String FIELD_FILE = "file";
   public static final String FIELD_IS = "is";
   public static final String FIELD_HAS = "has";
   public static final String FIELD_LABEL = "label";
   public static final String FIELD_LIMIT = "limit";
+  public static final String FIELD_MERGEABLE = "mergeable";
   public static final String FIELD_MESSAGE = "message";
   public static final String FIELD_OWNER = "owner";
   public static final String FIELD_OWNERIN = "ownerin";
+  public static final String FIELD_PARENTPROJECT = "parentproject";
+  public static final String FIELD_PATH = "path";
   public static final String FIELD_PROJECT = "project";
   public static final String FIELD_REF = "ref";
   public static final String FIELD_REVIEWER = "reviewer";
@@ -113,19 +125,16 @@
           ChangeQueryBuilder.class);
 
   @SuppressWarnings("unchecked")
-  public static boolean hasLimit(Predicate<ChangeData> p) {
-    return find(p, IntPredicate.class, FIELD_LIMIT) != null;
-  }
-
-  @SuppressWarnings("unchecked")
-  public static int getLimit(Predicate<ChangeData> p) {
-    return ((IntPredicate<?>) find(p, IntPredicate.class, FIELD_LIMIT)).intValue();
+  public static Integer getLimit(Predicate<ChangeData> p) {
+    IntPredicate<?> ip =
+        (IntPredicate<?>) find(p, IntPredicate.class, FIELD_LIMIT);
+    return ip != null ? ip.intValue() : null;
   }
 
   public static boolean hasNonTrivialSortKeyAfter(Schema<ChangeData> schema,
       Predicate<ChangeData> p) {
     SortKeyPredicate after =
-        (SortKeyPredicate) find(p, SortKeyPredicate.class, "sortkey_after");
+        find(p, SortKeyPredicate.class, "sortkey_after");
     return after != null && after.getMaxValue(schema) > 0;
   }
 
@@ -136,45 +145,68 @@
 
   @VisibleForTesting
   public static class Arguments {
-    final Provider<ReviewDb> dbProvider;
+    final Provider<ReviewDb> db;
     final Provider<ChangeQueryRewriter> rewriter;
     final IdentifiedUser.GenericFactory userFactory;
+    final Provider<CurrentUser> self;
     final CapabilityControl.Factory capabilityControlFactory;
     final ChangeControl.GenericFactory changeControlGenericFactory;
+    final ChangeData.Factory changeDataFactory;
     final AccountResolver accountResolver;
     final GroupBackend groupBackend;
     final AllProjectsName allProjectsName;
     final PatchListCache patchListCache;
     final GitRepositoryManager repoManager;
     final ProjectCache projectCache;
+    final Provider<ListChildProjects> listChildProjects;
     final IndexCollection indexes;
+    final SubmitStrategyFactory submitStrategyFactory;
+    final ConflictsCache conflictsCache;
+    final TrackingFooters trackingFooters;
+    final boolean allowsDrafts;
 
     @Inject
     @VisibleForTesting
     public Arguments(Provider<ReviewDb> dbProvider,
         Provider<ChangeQueryRewriter> rewriter,
         IdentifiedUser.GenericFactory userFactory,
+        Provider<CurrentUser> self,
         CapabilityControl.Factory capabilityControlFactory,
         ChangeControl.GenericFactory changeControlGenericFactory,
+        ChangeData.Factory changeDataFactory,
         AccountResolver accountResolver,
         GroupBackend groupBackend,
         AllProjectsName allProjectsName,
         PatchListCache patchListCache,
         GitRepositoryManager repoManager,
         ProjectCache projectCache,
-        IndexCollection indexes) {
-      this.dbProvider = dbProvider;
+        Provider<ListChildProjects> listChildProjects,
+        IndexCollection indexes,
+        SubmitStrategyFactory submitStrategyFactory,
+        ConflictsCache conflictsCache,
+        TrackingFooters trackingFooters,
+        @GerritServerConfig Config cfg) {
+      this.db = dbProvider;
       this.rewriter = rewriter;
       this.userFactory = userFactory;
+      this.self = self;
       this.capabilityControlFactory = capabilityControlFactory;
       this.changeControlGenericFactory = changeControlGenericFactory;
+      this.changeDataFactory = changeDataFactory;
       this.accountResolver = accountResolver;
       this.groupBackend = groupBackend;
       this.allProjectsName = allProjectsName;
       this.patchListCache = patchListCache;
       this.repoManager = repoManager;
       this.projectCache = projectCache;
+      this.listChildProjects = listChildProjects;
       this.indexes = indexes;
+      this.submitStrategyFactory = submitStrategyFactory;
+      this.conflictsCache = conflictsCache;
+      this.trackingFooters = trackingFooters;
+      this.allowsDrafts = cfg == null
+          ? true
+          : cfg.getBoolean("change", "allowDrafts", true);
     }
   }
 
@@ -184,7 +216,6 @@
 
   private final Arguments args;
   private final CurrentUser currentUser;
-  private boolean allowFileRegex;
 
   @Inject
   public ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) {
@@ -202,26 +233,38 @@
     this.currentUser = currentUser;
   }
 
-  public void setAllowFileRegex(boolean on) {
-    allowFileRegex = on;
+  @Operator
+  public Predicate<ChangeData> age(String value) {
+    return new AgePredicate(schema(args.indexes), value);
   }
 
   @Operator
-  public Predicate<ChangeData> age(String value) {
-    return new AgePredicate(args.dbProvider, value);
+  public Predicate<ChangeData> before(String value) throws QueryParseException {
+    return new BeforePredicate(schema(args.indexes), value);
+  }
+
+  @Operator
+  public Predicate<ChangeData> until(String value) throws QueryParseException {
+    return before(value);
+  }
+
+  @Operator
+  public Predicate<ChangeData> after(String value) throws QueryParseException {
+    return new AfterPredicate(schema(args.indexes), value);
+  }
+
+  @Operator
+  public Predicate<ChangeData> since(String value) throws QueryParseException {
+    return after(value);
   }
 
   @Operator
   public Predicate<ChangeData> change(String query) {
     if (PAT_LEGACY_ID.matcher(query).matches()) {
-      return new LegacyChangeIdPredicate(args.dbProvider, Change.Id
-          .parse(query));
+      return new LegacyChangeIdPredicate(args, Change.Id.parse(query));
 
     } else if (PAT_CHANGE_ID.matcher(query).matches()) {
-      if (query.charAt(0) == 'i') {
-        query = "I" + query.substring(1);
-      }
-      return new ChangeIdPredicate(args.dbProvider, query);
+      return new ChangeIdPredicate(args, parseChangeId(query));
     }
 
     throw new IllegalArgumentException();
@@ -229,8 +272,8 @@
 
   @Operator
   public Predicate<ChangeData> comment(String value) throws QueryParseException {
-    ChangeIndex index = requireIndex(FIELD_COMMENT, value);
-    return new CommentPredicate(args.dbProvider, index, value);
+    ChangeIndex index = args.indexes.getSearchIndex();
+    return new CommentPredicate(args, index, value);
   }
 
   @Operator
@@ -239,37 +282,37 @@
       return status_open();
 
     } else if ("closed".equals(statusName)) {
-      return ChangeStatusPredicate.closed(args.dbProvider);
+      return ChangeStatusPredicate.closed(args.db);
 
     } else if ("reviewed".equalsIgnoreCase(statusName)) {
-      return new IsReviewedPredicate(args.dbProvider);
+      return new IsReviewedPredicate();
 
     } else {
-      return new ChangeStatusPredicate(args.dbProvider, statusName);
+      return new ChangeStatusPredicate(statusName);
     }
   }
 
   public Predicate<ChangeData> status_open() {
-    return ChangeStatusPredicate.open(args.dbProvider);
+    return ChangeStatusPredicate.open(args.db);
   }
 
   @Operator
   public Predicate<ChangeData> has(String value) {
     if ("star".equalsIgnoreCase(value)) {
-      return new IsStarredByPredicate(args.dbProvider, currentUser);
+      return new IsStarredByPredicate(args, currentUser);
     }
 
     if ("draft".equalsIgnoreCase(value)) {
-      return new HasDraftByPredicate(args.dbProvider, self());
+      return new HasDraftByPredicate(args, self());
     }
 
     throw new IllegalArgumentException();
   }
 
   @Operator
-  public Predicate<ChangeData> is(String value) {
+  public Predicate<ChangeData> is(String value) throws QueryParseException {
     if ("starred".equalsIgnoreCase(value)) {
-      return new IsStarredByPredicate(args.dbProvider, currentUser);
+      return new IsStarredByPredicate(args, currentUser);
     }
 
     if ("watched".equalsIgnoreCase(value)) {
@@ -281,15 +324,19 @@
     }
 
     if ("reviewed".equalsIgnoreCase(value)) {
-      return new IsReviewedPredicate(args.dbProvider);
+      return new IsReviewedPredicate();
     }
 
     if ("owner".equalsIgnoreCase(value)) {
-      return new OwnerPredicate(args.dbProvider, self());
+      return new OwnerPredicate(self());
     }
 
     if ("reviewer".equalsIgnoreCase(value)) {
-      return new ReviewerPredicate(args.dbProvider, self());
+      return new ReviewerPredicate(self(), args.allowsDrafts);
+    }
+
+    if ("mergeable".equalsIgnoreCase(value)) {
+      return new IsMergeablePredicate();
     }
 
     try {
@@ -303,15 +350,31 @@
 
   @Operator
   public Predicate<ChangeData> commit(String id) {
-    return new CommitPredicate(args.dbProvider, AbbreviatedObjectId
-        .fromString(id));
+    return new CommitPredicate(args, AbbreviatedObjectId.fromString(id));
+  }
+
+  @Operator
+  public Predicate<ChangeData> conflicts(String value) throws OrmException,
+      QueryParseException {
+    return new ConflictsPredicate(args, value, parseChange(value));
+  }
+
+  @Operator
+  public Predicate<ChangeData> p(String name) {
+    return project(name);
   }
 
   @Operator
   public Predicate<ChangeData> project(String name) {
     if (name.startsWith("^"))
-      return new RegexProjectPredicate(args.dbProvider, name);
-    return new ProjectPredicate(args.dbProvider, name);
+      return new RegexProjectPredicate(name);
+    return new ProjectPredicate(name);
+  }
+
+  @Operator
+  public Predicate<ChangeData> parentproject(String name) {
+    return new ParentProjectPredicate(args.db, args.projectCache,
+        args.listChildProjects, args.self, name);
   }
 
   @Operator
@@ -330,27 +393,37 @@
   @Operator
   public Predicate<ChangeData> topic(String name) {
     if (name.startsWith("^"))
-      return new RegexTopicPredicate(args.dbProvider, name);
-    return new TopicPredicate(args.dbProvider, name);
+      return new RegexTopicPredicate(name);
+    return new TopicPredicate(name);
   }
 
   @Operator
   public Predicate<ChangeData> ref(String ref) {
     if (ref.startsWith("^"))
-      return new RegexRefPredicate(args.dbProvider, ref);
-    return new RefPredicate(args.dbProvider, ref);
+      return new RegexRefPredicate(ref);
+    return new RefPredicate(ref);
+  }
+
+  @Operator
+  public Predicate<ChangeData> f(String file) throws QueryParseException {
+    return file(file);
   }
 
   @Operator
   public Predicate<ChangeData> file(String file) throws QueryParseException {
     if (file.startsWith("^")) {
-      if (!allowFileRegex) {
-        requireIndex(FIELD_FILE, file);
-      }
-      return new RegexFilePredicate(args.dbProvider, args.patchListCache, file);
+      return new RegexPathPredicate(FIELD_FILE, file);
     } else {
-      requireIndex(FIELD_FILE, file);
-      return new EqualsFilePredicate(args.dbProvider, args.patchListCache, file);
+      return EqualsFilePredicate.create(args, file);
+    }
+  }
+
+  @Operator
+  public Predicate<ChangeData> path(String path) throws QueryParseException {
+    if (path.startsWith("^")) {
+      return new RegexPathPredicate(FIELD_PATH, path);
+    } else {
+      return new EqualsPathPredicate(FIELD_PATH, path);
     }
   }
 
@@ -404,31 +477,27 @@
     }
 
     return new LabelPredicate(args.projectCache,
-        args.changeControlGenericFactory, args.userFactory, args.dbProvider,
+        args.changeControlGenericFactory, args.userFactory, args.db,
         name, accounts, group);
   }
 
   @Operator
   public Predicate<ChangeData> message(String text) throws QueryParseException {
     ChangeIndex index = args.indexes.getSearchIndex();
-    if (index == null) {
-      return new LegacyMessagePredicate(args.dbProvider, args.repoManager, text);
-    }
-
-    return new MessagePredicate(args.dbProvider, index, text);
+    return new MessagePredicate(args, index, text);
   }
 
   @Operator
   public Predicate<ChangeData> starredby(String who)
       throws QueryParseException, OrmException {
     if ("self".equals(who)) {
-      return new IsStarredByPredicate(args.dbProvider, currentUser);
+      return new IsStarredByPredicate(args, currentUser);
     }
     Set<Account.Id> m = parseAccount(who);
     List<IsStarredByPredicate> p = Lists.newArrayListWithCapacity(m.size());
     for (Account.Id id : m) {
-      p.add(new IsStarredByPredicate(args.dbProvider,
-          args.userFactory.create(args.dbProvider, id)));
+      p.add(new IsStarredByPredicate(args,
+          args.userFactory.create(args.db, id)));
     }
     return Predicate.or(p);
   }
@@ -444,7 +513,7 @@
         p.add(new IsWatchedByPredicate(args, currentUser, false));
       } else {
         p.add(new IsWatchedByPredicate(args,
-            args.userFactory.create(args.dbProvider, id), true));
+            args.userFactory.create(args.db, id), true));
       }
     }
     return Predicate.or(p);
@@ -456,7 +525,7 @@
     Set<Account.Id> m = parseAccount(who);
     List<HasDraftByPredicate> p = Lists.newArrayListWithCapacity(m.size());
     for (Account.Id id : m) {
-      p.add(new HasDraftByPredicate(args.dbProvider, id));
+      p.add(new HasDraftByPredicate(args, id));
     }
     return Predicate.or(p);
   }
@@ -471,7 +540,7 @@
     if (!m.isEmpty()) {
       List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(m.size());
       for (Account.Id id : m) {
-        return visibleto(args.userFactory.create(args.dbProvider, id));
+        return visibleto(args.userFactory.create(args.db, id));
       }
       return Predicate.or(p);
     }
@@ -491,7 +560,7 @@
   }
 
   public Predicate<ChangeData> visibleto(CurrentUser user) {
-    return new IsVisibleToPredicate(args.dbProvider, //
+    return new IsVisibleToPredicate(args.db, //
         args.changeControlGenericFactory, //
         user);
   }
@@ -501,12 +570,18 @@
   }
 
   @Operator
+  public Predicate<ChangeData> o(String who)
+      throws QueryParseException, OrmException {
+    return owner(who);
+  }
+
+  @Operator
   public Predicate<ChangeData> owner(String who) throws QueryParseException,
       OrmException {
     Set<Account.Id> m = parseAccount(who);
     List<OwnerPredicate> p = Lists.newArrayListWithCapacity(m.size());
     for (Account.Id id : m) {
-      p.add(new OwnerPredicate(args.dbProvider, id));
+      p.add(new OwnerPredicate(id));
     }
     return Predicate.or(p);
   }
@@ -518,7 +593,13 @@
     if (g == null) {
       throw error("Group " + group + " not found");
     }
-    return new OwnerinPredicate(args.dbProvider, args.userFactory, g.getUUID());
+    return new OwnerinPredicate(args.db, args.userFactory, g.getUUID());
+  }
+
+  @Operator
+  public Predicate<ChangeData> r(String who)
+      throws QueryParseException, OrmException {
+    return reviewer(who);
   }
 
   @Operator
@@ -527,7 +608,7 @@
     Set<Account.Id> m = parseAccount(who);
     List<ReviewerPredicate> p = Lists.newArrayListWithCapacity(m.size());
     for (Account.Id id : m) {
-      p.add(new ReviewerPredicate(args.dbProvider, id));
+      p.add(new ReviewerPredicate(id, args.allowsDrafts));
     }
     return Predicate.or(p);
   }
@@ -539,12 +620,12 @@
     if (g == null) {
       throw error("Group " + group + " not found");
     }
-    return new ReviewerinPredicate(args.dbProvider, args.userFactory, g.getUUID());
+    return new ReviewerinPredicate(args.db, args.userFactory, g.getUUID());
   }
 
   @Operator
   public Predicate<ChangeData> tr(String trackingId) {
-    return new TrackingIdPredicate(args.dbProvider, trackingId);
+    return new TrackingIdPredicate(args.trackingFooters, trackingId);
   }
 
   @Operator
@@ -577,16 +658,18 @@
     return new LimitPredicate(limit);
   }
 
+  boolean supportsSortKey() {
+    return SortKeyPredicate.hasSortKeyField(schema(args.indexes));
+  }
+
   @Operator
   public Predicate<ChangeData> sortkey_after(String sortKey) {
-    return new SortKeyPredicate.After(
-        BasicChangeRewrites.schema(args.indexes), args.dbProvider, sortKey);
+    return new SortKeyPredicate.After(schema(args.indexes), args.db, sortKey);
   }
 
   @Operator
   public Predicate<ChangeData> sortkey_before(String sortKey) {
-    return new SortKeyPredicate.Before(
-        BasicChangeRewrites.schema(args.indexes), args.dbProvider, sortKey);
+    return new SortKeyPredicate.Before(schema(args.indexes), args.db, sortKey);
   }
 
   @Operator
@@ -594,7 +677,6 @@
     return sortkey_before(sortKey);
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   protected Predicate<ChangeData> defaultField(String query)
       throws QueryParseException {
@@ -627,7 +709,7 @@
           new ArrayList<ProjectPredicate>();
       for (Project.NameKey name : args.projectCache.all()) {
         if (name.get().toLowerCase().contains(query.toLowerCase())) {
-          predicate.add(new ProjectPredicate(args.dbProvider, name.get()));
+          predicate.add(new ProjectPredicate(name.get()));
         }
       }
 
@@ -665,6 +747,31 @@
     return g;
   }
 
+  private List<Change> parseChange(String value) throws OrmException,
+      QueryParseException {
+    if (PAT_LEGACY_ID.matcher(value).matches()) {
+      return Collections.singletonList(args.db.get().changes()
+          .get(Change.Id.parse(value)));
+    } else if (PAT_CHANGE_ID.matcher(value).matches()) {
+      Change.Key a = new Change.Key(parseChangeId(value));
+      List<Change> changes =
+          args.db.get().changes().byKeyRange(a, a.max()).toList();
+      if (changes.isEmpty()) {
+        throw error("Change " + value + " not found");
+      }
+      return changes;
+    }
+
+    throw error("Change " + value + " not found");
+  }
+
+  private static String parseChangeId(String value) {
+    if (value.charAt(0) == 'i') {
+      value = "I" + value.substring(1);
+    }
+    return value;
+  }
+
   private Account.Id self() {
     if (currentUser.isIdentifiedUser()) {
       return ((IdentifiedUser) currentUser).getAccountId();
@@ -672,12 +779,8 @@
     throw new IllegalArgumentException();
   }
 
-  private ChangeIndex requireIndex(String field, String value)
-      throws QueryParseException {
-    ChangeIndex idx = args.indexes.getSearchIndex();
-    if (idx == null) {
-      throw error("secondary index must be enabled for " + field + ":" + value);
-    }
-    return idx;
+  private static Schema<ChangeData> schema(@Nullable IndexCollection indexes) {
+    ChangeIndex index = indexes != null ? indexes.getSearchIndex() : null;
+    return index != null ? index.getSchema() : null;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
index bd186c7..bbf235b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -18,6 +18,6 @@
 import com.google.gerrit.server.query.QueryParseException;
 
 public interface ChangeQueryRewriter {
-  Predicate<ChangeData> rewrite(Predicate<ChangeData> in)
+  Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
       throws QueryParseException;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
index d5d6a92..9ff416f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.ImmutableBiMap;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
@@ -29,7 +30,7 @@
 import java.util.List;
 
 /**
- * Predicate for a {@link Change.Status}.
+ * Predicate for a {@link Status}.
  * <p>
  * The actual name of this operator can differ, it usually comes as {@code
  * status:} but may also be {@code is:} to help do-what-i-meanery for end-users
@@ -51,7 +52,7 @@
     List<Predicate<ChangeData>> r = new ArrayList<Predicate<ChangeData>>(4);
     for (final Change.Status e : Change.Status.values()) {
       if (e.isOpen()) {
-        r.add(new ChangeStatusPredicate(dbProvider, e));
+        r.add(new ChangeStatusPredicate(e));
       }
     }
     return r.size() == 1 ? r.get(0) : or(r);
@@ -61,25 +62,22 @@
     List<Predicate<ChangeData>> r = new ArrayList<Predicate<ChangeData>>(4);
     for (final Change.Status e : Change.Status.values()) {
       if (e.isClosed()) {
-        r.add(new ChangeStatusPredicate(dbProvider, e));
+        r.add(new ChangeStatusPredicate(e));
       }
     }
     return r.size() == 1 ? r.get(0) : or(r);
   }
 
-  private final Provider<ReviewDb> dbProvider;
   private final Change.Status status;
 
-  ChangeStatusPredicate(Provider<ReviewDb> dbProvider, String value) {
+  ChangeStatusPredicate(String value) {
     super(ChangeField.STATUS, value);
-    this.dbProvider = dbProvider;
     status = VALUES.inverse().get(value);
     checkArgument(status != null, "invalid change status: %s", value);
   }
 
-  ChangeStatusPredicate(Provider<ReviewDb> dbProvider, Change.Status status) {
+  ChangeStatusPredicate(Change.Status status) {
     super(ChangeField.STATUS, VALUES.get(status));
-    this.dbProvider = dbProvider;
     this.status = status;
   }
 
@@ -89,7 +87,7 @@
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
+    Change change = object.change();
     return change != null && status.equals(change.getStatus());
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
index 563e37b..5994e5c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
@@ -14,31 +14,29 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.ChangeIndex;
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 class CommentPredicate extends IndexPredicate<ChangeData> {
-  private final Provider<ReviewDb> db;
+  private final Arguments args;
   private final ChangeIndex index;
 
-  CommentPredicate(Provider<ReviewDb> db, ChangeIndex index, String value) {
+  CommentPredicate(Arguments args, ChangeIndex index, String value) {
     super(ChangeField.COMMENT, value);
-    this.db = db;
+    this.args = args;
     this.index = index;
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   public boolean match(ChangeData object) throws OrmException {
     try {
       for (ChangeData cData : index.getSource(
-          Predicate.and(new LegacyChangeIdPredicate(db, object.getId()), this), 1)
+          Predicate.and(new LegacyChangeIdPredicate(args, object.getId()), this), 0, 1)
           .read()) {
         if (cData.getId().equals(object.getId())) {
           return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
index 082dc83..109d67a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -16,30 +16,29 @@
 
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
 
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.ObjectId;
 
 class CommitPredicate extends IndexPredicate<ChangeData> implements
     ChangeDataSource {
-  private final Provider<ReviewDb> dbProvider;
+  private final Arguments args;
   private final AbbreviatedObjectId abbrevId;
 
-  CommitPredicate(Provider<ReviewDb> dbProvider, AbbreviatedObjectId id) {
+  CommitPredicate(Arguments args, AbbreviatedObjectId id) {
     super(ChangeField.COMMIT, id.name());
-    this.dbProvider = dbProvider;
+    this.args = args;
     this.abbrevId = id;
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    for (PatchSet p : object.patches(dbProvider)) {
+    for (PatchSet p : object.patches()) {
       if (p.getRevision() != null && p.getRevision().get() != null) {
         final ObjectId id = ObjectId.fromString(p.getRevision().get());
         if (abbrevId.prefixCompare(id) == 0) {
@@ -54,12 +53,12 @@
   public ResultSet<ChangeData> read() throws OrmException {
     final RevId id = new RevId(abbrevId.name());
     if (id.isComplete()) {
-      return ChangeDataResultSet.patchSet(//
-          dbProvider.get().patchSets().byRevision(id));
+      return ChangeDataResultSet.patchSet(args.changeDataFactory, args.db,
+          args.db.get().patchSets().byRevision(id));
 
     } else {
-      return ChangeDataResultSet.patchSet(//
-          dbProvider.get().patchSets().byRevisionRange(id, id.max()));
+      return ChangeDataResultSet.patchSet(args.changeDataFactory, args.db,
+          args.db.get().patchSets().byRevisionRange(id, id.max()));
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java
new file mode 100644
index 0000000..e177e37
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictKey.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.common.base.Objects;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.io.Serializable;
+
+public class ConflictKey implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private final ObjectId commit;
+  private final ObjectId otherCommit;
+  private final SubmitType submitType;
+  private final boolean contentMerge;
+
+  public ConflictKey(ObjectId commit, ObjectId otherCommit,
+      SubmitType submitType, boolean contentMerge) {
+    if (SubmitType.FAST_FORWARD_ONLY.equals(submitType)
+        || commit.compareTo(otherCommit) < 0) {
+      this.commit = commit;
+      this.otherCommit = otherCommit;
+    } else {
+      this.commit = otherCommit;
+      this.otherCommit = commit;
+    }
+    this.submitType = submitType;
+    this.contentMerge = contentMerge;
+  }
+
+  public ObjectId getCommit() {
+    return commit;
+  }
+
+  public ObjectId getOtherCommit() {
+    return otherCommit;
+  }
+
+  public SubmitType getSubmitType() {
+    return submitType;
+  }
+
+  public boolean isContentMerge() {
+    return contentMerge;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof ConflictKey)) {
+      return false;
+    }
+    ConflictKey other = (ConflictKey)o;
+    return commit.equals(other.commit)
+        && otherCommit.equals(other.otherCommit)
+        && submitType.equals(other.submitType)
+        && contentMerge == other.contentMerge;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(commit, otherCommit, submitType, contentMerge);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCache.java
similarity index 71%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCache.java
index 8e1b340..bf7a5dd 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCache.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.change;
+package com.google.gerrit.server.query.change;
 
-public class SubmitInput {
-  boolean wait_for_merge;
+import com.google.gerrit.common.Nullable;
 
-  public static SubmitInput waitForMerge() {
-    SubmitInput in = new SubmitInput();
-    in.wait_for_merge = true;
-    return in;
-  }
+public interface ConflictsCache {
+
+  public void put(ConflictKey key, Boolean value);
+
+  @Nullable
+  public Boolean getIfPresent(ConflictKey key);
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java
new file mode 100644
index 0000000..1b3473e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsCacheImpl.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.common.cache.Cache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+@Singleton
+public class ConflictsCacheImpl implements ConflictsCache {
+  public final static String NAME = "conflicts";
+
+  public static Module module() {
+    return new CacheModule() {
+      @Override
+      protected void configure() {
+        persist(NAME, ConflictKey.class, Boolean.class)
+            .maximumWeight(37400);
+        bind(ConflictsCache.class).to(ConflictsCacheImpl.class);
+      }
+    };
+  }
+
+  private final Cache<ConflictKey, Boolean> conflictsCache;
+
+  @Inject
+  public ConflictsCacheImpl(
+      @Named(NAME) Cache<ConflictKey, Boolean> conflictsCache) {
+    this.conflictsCache = conflictsCache;
+  }
+
+  @Override
+  public void put(ConflictKey key, Boolean value) {
+    conflictsCache.put(key, value);
+  }
+
+  @Override
+  public Boolean getIfPresent(ConflictKey key) {
+    return conflictsCache.getIfPresent(key);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
new file mode 100644
index 0000000..ee6587d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -0,0 +1,259 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.git.strategy.SubmitStrategy;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+class ConflictsPredicate extends OrPredicate<ChangeData> {
+  private final String value;
+
+  ConflictsPredicate(Arguments args, String value, List<Change> changes)
+      throws OrmException {
+    super(predicates(args, value, changes));
+    this.value = value;
+  }
+
+  private static List<Predicate<ChangeData>> predicates(final Arguments args,
+      String value, List<Change> changes) throws OrmException {
+    List<Predicate<ChangeData>> changePredicates =
+        Lists.newArrayListWithCapacity(changes.size());
+    final Provider<ReviewDb> db = args.db;
+    for (final Change c : changes) {
+      final ChangeDataCache changeDataCache = new ChangeDataCache(
+          c, db, args.changeDataFactory, args.projectCache);
+      List<String> files = args.changeDataFactory.create(db.get(), c)
+          .currentFilePaths();
+      List<Predicate<ChangeData>> filePredicates =
+          Lists.newArrayListWithCapacity(files.size());
+      for (String file : files) {
+        filePredicates.add(
+            new EqualsPathPredicate(ChangeQueryBuilder.FIELD_PATH, file));
+      }
+
+      List<Predicate<ChangeData>> predicatesForOneChange =
+          Lists.newArrayListWithCapacity(5);
+      predicatesForOneChange.add(
+          not(new LegacyChangeIdPredicate(args, c.getId())));
+      predicatesForOneChange.add(
+          new ProjectPredicate(c.getProject().get()));
+      predicatesForOneChange.add(
+          new RefPredicate(c.getDest().get()));
+      predicatesForOneChange.add(or(filePredicates));
+      predicatesForOneChange.add(new OperatorPredicate<ChangeData>(
+          ChangeQueryBuilder.FIELD_CONFLICTS, value) {
+
+        @Override
+        public boolean match(ChangeData object) throws OrmException {
+          Change otherChange = object.change();
+          if (otherChange == null) {
+            return false;
+          }
+          if (!otherChange.getDest().equals(c.getDest())) {
+            return false;
+          }
+          SubmitType submitType = getSubmitType(otherChange, object);
+          if (submitType == null) {
+            return false;
+          }
+          ObjectId other = ObjectId.fromString(
+              object.currentPatchSet().getRevision().get());
+          ConflictKey conflictsKey =
+              new ConflictKey(changeDataCache.getTestAgainst(), other, submitType,
+                  changeDataCache.getProjectState().isUseContentMerge());
+          Boolean conflicts = args.conflictsCache.getIfPresent(conflictsKey);
+          if (conflicts != null) {
+            return conflicts;
+          }
+          try {
+            Repository repo =
+                args.repoManager.openRepository(otherChange.getProject());
+            try {
+              RevWalk rw = new RevWalk(repo) {
+                @Override
+                protected RevCommit createCommit(AnyObjectId id) {
+                  return new CodeReviewCommit(id);
+                }
+              };
+              try {
+                RevFlag canMergeFlag = rw.newFlag("CAN_MERGE");
+                CodeReviewCommit commit =
+                    (CodeReviewCommit) rw.parseCommit(changeDataCache.getTestAgainst());
+                SubmitStrategy strategy =
+                    args.submitStrategyFactory.create(submitType,
+                        db.get(), repo, rw, null, canMergeFlag,
+                        getAlreadyAccepted(repo, rw, commit),
+                        otherChange.getDest());
+                CodeReviewCommit otherCommit =
+                    (CodeReviewCommit) rw.parseCommit(other);
+                otherCommit.add(canMergeFlag);
+                conflicts = !strategy.dryRun(commit, otherCommit);
+                args.conflictsCache.put(conflictsKey, conflicts);
+                return conflicts;
+              } catch (MergeException e) {
+                throw new IllegalStateException(e);
+              } catch (NoSuchProjectException e) {
+                throw new IllegalStateException(e);
+              } finally {
+                rw.release();
+              }
+            } finally {
+              repo.close();
+            }
+          } catch (IOException e) {
+            throw new IllegalStateException(e);
+          }
+        }
+
+        @Override
+        public int getCost() {
+          return 5;
+        }
+
+        private SubmitType getSubmitType(Change change, ChangeData cd) throws OrmException {
+          try {
+            final SubmitTypeRecord r =
+                args.changeControlGenericFactory.controlFor(change,
+                    args.userFactory.create(change.getOwner()))
+                    .getSubmitTypeRecord(db.get(), cd.currentPatchSet(), cd);
+            if (r.status != SubmitTypeRecord.Status.OK) {
+              return null;
+            }
+            return r.type;
+          } catch (NoSuchChangeException e) {
+            return null;
+          }
+        }
+
+        private Set<RevCommit> getAlreadyAccepted(Repository repo, RevWalk rw,
+            CodeReviewCommit tip) throws MergeException {
+          Set<RevCommit> alreadyAccepted = Sets.newHashSet();
+
+          if (tip != null) {
+            alreadyAccepted.add(tip);
+          }
+
+          try {
+            for (ObjectId id : changeDataCache.getAlreadyAccepted(repo)) {
+              try {
+                alreadyAccepted.add(rw.parseCommit(id));
+              } catch (IncorrectObjectTypeException iote) {
+                // Not a commit? Skip over it.
+              }
+            }
+          } catch (IOException e) {
+            throw new MergeException(
+                "Failed to determine already accepted commits.", e);
+          }
+
+          return alreadyAccepted;
+        }
+      });
+      changePredicates.add(and(predicatesForOneChange));
+    }
+    return changePredicates;
+  }
+
+  @Override
+  public String toString() {
+    return ChangeQueryBuilder.FIELD_CONFLICTS + ":" + value;
+  }
+
+  private static class ChangeDataCache {
+    private final Change change;
+    private final Provider<ReviewDb> db;
+    private final ChangeData.Factory changeDataFactory;
+    private final ProjectCache projectCache;
+
+    private ObjectId testAgainst;
+    private ProjectState projectState;
+    private Set<ObjectId> alreadyAccepted;
+
+    ChangeDataCache(Change change, Provider<ReviewDb> db,
+        ChangeData.Factory changeDataFactory, ProjectCache projectCache) {
+      this.change = change;
+      this.db = db;
+      this.changeDataFactory = changeDataFactory;
+      this.projectCache = projectCache;
+    }
+
+    ObjectId getTestAgainst()
+        throws OrmException {
+      if (testAgainst == null) {
+        testAgainst = ObjectId.fromString(
+            changeDataFactory.create(db.get(), change)
+                .currentPatchSet().getRevision().get());
+      }
+      return testAgainst;
+    }
+
+    ProjectState getProjectState() {
+      if (projectState == null) {
+        projectState = projectCache.get(change.getProject());
+        if (projectState == null) {
+          throw new IllegalStateException(
+              new NoSuchProjectException(change.getProject()));
+        }
+      }
+      return projectState;
+    }
+
+    Set<ObjectId> getAlreadyAccepted(Repository repo) {
+      if (alreadyAccepted == null) {
+        alreadyAccepted = Sets.newHashSet();
+        for (Ref r : repo.getAllRefs().values()) {
+          if (r.getName().startsWith(Constants.R_HEADS)
+              || r.getName().startsWith(Constants.R_TAGS)) {
+            if (r.getObjectId() != null) {
+              alreadyAccepted.add(r.getObjectId());
+            }
+          }
+        }
+      }
+      return alreadyAccepted;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
index 002dc99..e5fc51d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
@@ -14,40 +14,33 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
-
-import java.util.Collections;
-import java.util.List;
 
 class EqualsFilePredicate extends IndexPredicate<ChangeData> {
-  private final Provider<ReviewDb> db;
-  private final PatchListCache cache;
+  static Predicate<ChangeData> create(Arguments args, String value) {
+    Predicate<ChangeData> eqPath =
+        new EqualsPathPredicate(ChangeQueryBuilder.FIELD_FILE, value);
+    if (!args.indexes.getSearchIndex().getSchema().getFields().containsKey(
+        ChangeField.FILE_PART.getName())) {
+      return eqPath;
+    }
+    return Predicate.or(eqPath, new EqualsFilePredicate(value));
+  }
+
   private final String value;
 
-  EqualsFilePredicate(Provider<ReviewDb> db, PatchListCache plc, String value) {
-    super(ChangeField.FILE, value);
-    this.db = db;
-    this.cache = plc;
+  private EqualsFilePredicate(String value) {
+    super(ChangeField.FILE_PART, ChangeQueryBuilder.FIELD_FILE, value);
     this.value = value;
   }
 
   @Override
   public boolean match(ChangeData object) throws OrmException {
-    List<String> files = object.currentFilePaths(db, cache);
-    if (files != null) {
-      return Collections.binarySearch(files, value) >= 0;
-    } else {
-      // The ChangeData can't do expensive lookups right now. Bypass
-      // them and include the result anyway. We might be able to do
-      // a narrow later on to a smaller set.
-      //
-      return true;
-    }
+    return ChangeField.getFileParts(object).contains(value);
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index af9a07a..54e5a7d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -42,25 +42,22 @@
   private final Account.Id account;
   private final AccountGroup.UUID group;
 
-  EqualsLabelPredicate(ProjectCache projectCache,
-      ChangeControl.GenericFactory ccFactory,
-      IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
-      String label, int expVal, Account.Id account,
-      AccountGroup.UUID group) {
+  EqualsLabelPredicate(LabelPredicate.Args args, String label, int expVal,
+      Account.Id account) {
     super(ChangeField.LABEL, ChangeField.formatLabel(label, expVal, account));
-    this.ccFactory = ccFactory;
-    this.projectCache = projectCache;
-    this.userFactory = userFactory;
-    this.dbProvider = dbProvider;
+    this.ccFactory = args.ccFactory;
+    this.projectCache = args.projectCache;
+    this.userFactory = args.userFactory;
+    this.dbProvider = args.dbProvider;
+    this.group = args.group;
     this.label = label;
     this.expVal = expVal;
     this.account = account;
-    this.group = group;
   }
 
   @Override
   public boolean match(ChangeData object) throws OrmException {
-    Change c = object.change(dbProvider);
+    Change c = object.change();
     if (c == null) {
       // The change has disappeared.
       //
@@ -74,7 +71,7 @@
     }
     LabelType labelType = type(project.getLabelTypes(), label);
     boolean hasVote = false;
-    for (PatchSetApproval p : object.currentApprovals(dbProvider)) {
+    for (PatchSetApproval p : object.currentApprovals()) {
       if (labelType.matches(p)) {
         hasVote = true;
         if (match(c, p.getValue(), p.getAccountId(), labelType)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsPathPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsPathPredicate.java
new file mode 100644
index 0000000..055b6d5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsPathPredicate.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gwtorm.server.OrmException;
+
+import java.util.Collections;
+import java.util.List;
+
+class EqualsPathPredicate extends IndexPredicate<ChangeData> {
+  private final String value;
+
+  EqualsPathPredicate(String fieldName, String value) {
+    super(ChangeField.PATH, fieldName, value);
+    this.value = value;
+  }
+
+  @Override
+  public boolean match(ChangeData object) throws OrmException {
+    List<String> files = object.currentFilePaths();
+    return files != null && Collections.binarySearch(files, value) >= 0;
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
index d5260a5..6d44b96 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
@@ -17,30 +17,29 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.ListResultSet;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
 
 import java.util.ArrayList;
 import java.util.HashSet;
 
 class HasDraftByPredicate extends OperatorPredicate<ChangeData> implements
     ChangeDataSource {
-  private final Provider<ReviewDb> db;
+  private final Arguments args;
   private final Account.Id accountId;
 
-  HasDraftByPredicate(Provider<ReviewDb> db, Account.Id accountId) {
+  HasDraftByPredicate(Arguments args, Account.Id accountId) {
     super(ChangeQueryBuilder.FIELD_DRAFTBY, accountId.toString());
-    this.db = db;
+    this.args = args;
     this.accountId = accountId;
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    for (PatchLineComment c : object.comments(db)) {
+    for (PatchLineComment c : object.comments()) {
       if (c.getStatus() == PatchLineComment.Status.DRAFT
           && c.getAuthor().equals(accountId)) {
         return true;
@@ -52,14 +51,14 @@
   @Override
   public ResultSet<ChangeData> read() throws OrmException {
     HashSet<Change.Id> ids = new HashSet<Change.Id>();
-    for (PatchLineComment sc : db.get().patchComments()
+    for (PatchLineComment sc : args.db.get().patchComments()
         .draftByAuthor(accountId)) {
       ids.add(sc.getKey().getParentKey().getParentKey().getParentKey());
     }
 
     ArrayList<ChangeData> r = new ArrayList<ChangeData>(ids.size());
     for (Change.Id id : ids) {
-      r.add(new ChangeData(id));
+      r.add(args.changeDataFactory.create(args.db.get(), id));
     }
     return new ListResultSet<ChangeData>(r);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java
new file mode 100644
index 0000000..787b90e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gwtorm.server.OrmException;
+
+class IsMergeablePredicate extends IndexPredicate<ChangeData> {
+  IsMergeablePredicate() {
+    super(ChangeField.MERGEABLE, "1");
+  }
+
+  @Override
+  public boolean match(ChangeData object) throws OrmException {
+    Change c = object.change();
+    return c != null && c.isMergeable();
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
index 6832e4e..2bf41bb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
@@ -17,30 +17,25 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 class IsReviewedPredicate extends IndexPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
-
-  IsReviewedPredicate(Provider<ReviewDb> dbProvider) {
+  IsReviewedPredicate() {
     super(ChangeField.REVIEWED, "1");
-    this.dbProvider = dbProvider;
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change c = object.change(dbProvider);
+    Change c = object.change();
     if (c == null) {
       return false;
     }
 
     PatchSet.Id current = c.currentPatchSetId();
-    for (PatchSetApproval p : object.approvals(dbProvider)) {
-      if (p.getPatchSetId().equals(current) && p.getValue() != 0) {
+    for (PatchSetApproval p : object.approvals().get(current)) {
+      if (p.getValue() != 0) {
         return true;
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
index 9976bfd..d25d5a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
@@ -16,14 +16,13 @@
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.query.OrPredicate;
 import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
 
 import java.util.List;
 import java.util.Set;
@@ -38,21 +37,21 @@
   }
 
   private static List<Predicate<ChangeData>> predicates(
-      Provider<ReviewDb> db,
+      Arguments args,
       Set<Change.Id> ids) {
     List<Predicate<ChangeData>> r = Lists.newArrayListWithCapacity(ids.size());
     for (Change.Id id : ids) {
-      r.add(new LegacyChangeIdPredicate(db, id));
+      r.add(new LegacyChangeIdPredicate(args, id));
     }
     return r;
   }
 
-  private final Provider<ReviewDb> db;
+  private final Arguments args;
   private final CurrentUser user;
 
-  IsStarredByPredicate(Provider<ReviewDb> db, CurrentUser user) {
-    super(predicates(db, user.getStarredChanges()));
-    this.db = db;
+  IsStarredByPredicate(Arguments args, CurrentUser user) {
+    super(predicates(args, user.getStarredChanges()));
+    this.args = args;
     this.user = user;
   }
 
@@ -63,8 +62,8 @@
 
   @Override
   public ResultSet<ChangeData> read() throws OrmException {
-    return ChangeDataResultSet.change( //
-        db.get().changes().get(user.getStarredChanges()));
+    return ChangeDataResultSet.change(args.changeDataFactory, args.db,
+        args.db.get().changes().get(user.getStarredChanges()));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
index 8992318..7f91699 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
@@ -30,7 +30,7 @@
       return ((IdentifiedUser) user).getAccountId().toString();
     }
     if (user instanceof SingleGroupUser) {
-      return "group:" + ((SingleGroupUser) user).getEffectiveGroups().getKnownGroups() //
+      return "group:" + user.getEffectiveGroups().getKnownGroups() //
           .iterator().next().toString();
     }
     return user.toString();
@@ -54,7 +54,7 @@
       return true;
     }
     try {
-      Change c = cd.change(db);
+      Change c = cd.change();
       if (c == null) {
         return false;
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index a6a344d..c1e99a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -74,9 +74,7 @@
       }
 
       if (p != null && f != null) {
-        @SuppressWarnings("unchecked")
-        Predicate<ChangeData> andPredicate = and(p, f);
-        r.add(andPredicate);
+        r.add(and(p, f));
       } else if (p != null) {
         r.add(p);
       } else if (f != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index b3ea6b4..60f7ffa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.OrPredicate;
 import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.util.LabelVote;
 import com.google.inject.Provider;
 
 import java.util.List;
@@ -33,30 +34,42 @@
 public class LabelPredicate extends OrPredicate<ChangeData> {
   private static final int MAX_LABEL_VALUE = 4;
 
-  private static enum Test {
-    EQ, GT_EQ, LT_EQ;
+  static class Args {
+    final ProjectCache projectCache;
+    final ChangeControl.GenericFactory ccFactory;
+    final IdentifiedUser.GenericFactory userFactory;
+    final Provider<ReviewDb> dbProvider;
+    final String value;
+    final Set<Account.Id> accounts;
+    final AccountGroup.UUID group;
 
-    boolean isEq() {
-      return EQ.equals(this);
+    private Args(
+        ProjectCache projectCache,
+        ChangeControl.GenericFactory ccFactory,
+        IdentifiedUser.GenericFactory userFactory,
+        Provider<ReviewDb> dbProvider,
+        String value,
+        Set<Account.Id> accounts,
+        AccountGroup.UUID group) {
+      this.projectCache = projectCache;
+      this.ccFactory = ccFactory;
+      this.userFactory = userFactory;
+      this.dbProvider = dbProvider;
+      this.value = value;
+      this.accounts = accounts;
+      this.group = group;
     }
+  }
 
-    boolean isGtEq() {
-      return GT_EQ.equals(this);
-    }
+  private static class Parsed {
+    private final String label;
+    private final String test;
+    private final int expVal;
 
-    static Test op(String op) {
-      if ("=".equals(op)) {
-        return EQ;
-
-      } else if (">=".equals(op)) {
-        return GT_EQ;
-
-      } else if ("<=".equals(op)) {
-        return LT_EQ;
-
-      } else {
-        throw new IllegalArgumentException("Unsupported operation " + op);
-      }
+    private Parsed(String label, String test, int expVal) {
+      this.label = label;
+      this.test = test;
+      this.expVal = expVal;
     }
   }
 
@@ -66,59 +79,79 @@
       ChangeControl.GenericFactory ccFactory,
       IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
       String value, Set<Account.Id> accounts, AccountGroup.UUID group) {
-    super(predicates(projectCache, ccFactory, userFactory, dbProvider, value,
-        accounts, group));
+    super(predicates(new Args(projectCache, ccFactory, userFactory, dbProvider,
+        value, accounts, group)));
     this.value = value;
   }
 
-  private static List<Predicate<ChangeData>> predicates(
-      ProjectCache projectCache, ChangeControl.GenericFactory ccFactory,
-      IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
-      String value, Set<Account.Id> accounts, AccountGroup.UUID group) {
-    String label;
-    Test test;
-    int expVal;
-    Matcher m1 = Pattern.compile("(=|>=|<=)([+-]?\\d+)$").matcher(value);
-    Matcher m2 = Pattern.compile("([+-]\\d+)$").matcher(value);
-    if (m1.find()) {
-      label = value.substring(0, m1.start());
-      test = Test.op(m1.group(1));
-      expVal = value(m1.group(2));
+  private static List<Predicate<ChangeData>> predicates(Args args) {
+    String v = args.value;
+    Parsed parsed = null;
 
-    } else if (m2.find()) {
-      label = value.substring(0, m2.start());
-      test = Test.EQ;
-      expVal = value(m2.group(1));
-
-    } else {
-      label = value;
-      test = Test.EQ;
-      expVal = 1;
+    try {
+      LabelVote lv = LabelVote.parse(v);
+      parsed = new Parsed(lv.getLabel(), "=", lv.getValue());
+    } catch (IllegalArgumentException e) {
+      // Try next format.
     }
 
-    List<Predicate<ChangeData>> r = Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
-    if (test.isEq()) {
-      if (expVal != 0) {
-        r.add(equalsLabelPredicate(projectCache, ccFactory, userFactory,
-            dbProvider, label, expVal, accounts, group));
+    try {
+      LabelVote lv = LabelVote.parseWithEquals(v);
+      parsed = new Parsed(lv.getLabel(), "=", lv.getValue());
+    } catch (IllegalArgumentException e) {
+      // Try next format.
+    }
+
+    if (parsed == null) {
+      Matcher m = Pattern.compile("(>|>=|=|<|<=)([+-]?\\d+)$").matcher(v);
+      if (m.find()) {
+        parsed = new Parsed(v.substring(0, m.start()), m.group(1),
+            value(m.group(2)));
       } else {
-        r.add(noLabelQuery(projectCache, ccFactory, userFactory,
-            dbProvider, label, accounts, group));
+        parsed = new Parsed(v, "=", 1);
       }
-    } else {
-      for (int i = test.isGtEq() ? expVal : neg(expVal); i <= MAX_LABEL_VALUE; i++) {
-        if (i != 0) {
-          r.add(equalsLabelPredicate(projectCache, ccFactory, userFactory,
-              dbProvider, label, test.isGtEq() ? i : neg(i), accounts, group));
-        } else {
-          r.add(noLabelQuery(projectCache, ccFactory, userFactory,
-              dbProvider, label, accounts, group));
-        }
-      }
+    }
+
+    int min, max;
+    switch (parsed.test) {
+      case "=":
+      default:
+        min = max = parsed.expVal;
+        break;
+      case ">":
+        min = parsed.expVal + 1;
+        max = MAX_LABEL_VALUE;
+        break;
+      case ">=":
+        min = parsed.expVal;
+        max = MAX_LABEL_VALUE;
+        break;
+      case "<":
+        min = -MAX_LABEL_VALUE;
+        max = parsed.expVal - 1;
+        break;
+      case "<=":
+        min = -MAX_LABEL_VALUE;
+        max = parsed.expVal;
+        break;
+    }
+    List<Predicate<ChangeData>> r =
+        Lists.newArrayListWithCapacity(max - min + 1);
+    for (int i = min; i <= max; i++) {
+      r.add(onePredicate(args, parsed.label, i));
     }
     return r;
   }
 
+  private static Predicate<ChangeData> onePredicate(Args args, String label,
+      int expVal) {
+    if (expVal != 0) {
+      return equalsLabelPredicate(args, label, expVal);
+    } else {
+      return noLabelQuery(args, label);
+    }
+  }
+
   private static int value(String value) {
     if (value.startsWith("+")) {
       value = value.substring(1);
@@ -126,38 +159,24 @@
     return Integer.parseInt(value);
   }
 
-  private static int neg(int value) {
-    return -1 * value;
-  }
-
-  private static Predicate<ChangeData> noLabelQuery(ProjectCache projectCache,
-      ChangeControl.GenericFactory ccFactory,
-      IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
-      String label, Set<Account.Id> accounts, AccountGroup.UUID group) {
+  private static Predicate<ChangeData> noLabelQuery(Args args, String label) {
     List<Predicate<ChangeData>> r =
         Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
     for (int i = 1; i <= MAX_LABEL_VALUE; i++) {
-      r.add(not(equalsLabelPredicate(projectCache, ccFactory, userFactory,
-          dbProvider, label, i, accounts, group)));
-      r.add(not(equalsLabelPredicate(projectCache, ccFactory, userFactory,
-          dbProvider, label, neg(i), accounts, group)));
+      r.add(not(equalsLabelPredicate(args, label, i)));
+      r.add(not(equalsLabelPredicate(args, label, -i)));
     }
     return and(r);
   }
 
-  private static Predicate<ChangeData> equalsLabelPredicate(
-      ProjectCache projectCache, ChangeControl.GenericFactory ccFactory,
-      IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
-      String label, int expVal, Set<Account.Id> accounts,
-      AccountGroup.UUID group) {
-    if (accounts == null || accounts.isEmpty()) {
-      return new EqualsLabelPredicate(projectCache, ccFactory, userFactory,
-          dbProvider, label, expVal, null, group);
+  private static Predicate<ChangeData> equalsLabelPredicate(Args args,
+      String label, int expVal) {
+    if (args.accounts == null || args.accounts.isEmpty()) {
+      return new EqualsLabelPredicate(args, label, expVal, null);
     } else {
       List<Predicate<ChangeData>> r = Lists.newArrayList();
-      for (Account.Id a : accounts) {
-        r.add(new EqualsLabelPredicate(projectCache, ccFactory, userFactory,
-            dbProvider, label, expVal, a, group));
+      for (Account.Id a : args.accounts) {
+        r.add(new EqualsLabelPredicate(args, label, expVal, a));
       }
       return or(r);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
index 48ae48f..c62d7ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
@@ -15,24 +15,23 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.ListResultSet;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
 
 import java.util.Collections;
 
 class LegacyChangeIdPredicate extends IndexPredicate<ChangeData> implements
     ChangeDataSource {
-  private final Provider<ReviewDb> db;
+  private final Arguments args;
   private final Change.Id id;
 
-  LegacyChangeIdPredicate(Provider<ReviewDb> db, Change.Id id) {
+  LegacyChangeIdPredicate(Arguments args, Change.Id id) {
     super(ChangeField.LEGACY_ID, ChangeQueryBuilder.FIELD_CHANGE, id.toString());
-    this.db = db;
+    this.args = args;
     this.id = id;
   }
 
@@ -43,10 +42,10 @@
 
   @Override
   public ResultSet<ChangeData> read() throws OrmException {
-    Change c = db.get().changes().get(id);
+    Change c = args.db.get().changes().get(id);
     if (c != null) {
-      return new ListResultSet<ChangeData>( //
-          Collections.singletonList(new ChangeData(c)));
+      return new ListResultSet<ChangeData>(Collections.singletonList(
+          args.changeDataFactory.create(args.db.get(), c)));
     } else {
       return new ListResultSet<ChangeData>(Collections.<ChangeData> emptyList());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyMessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyMessagePredicate.java
deleted file mode 100644
index 6b6d1e5..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyMessagePredicate.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.query.change;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.inject.Provider;
-
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.revwalk.filter.MessageRevFilter;
-import org.eclipse.jgit.revwalk.filter.RevFilter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-/**
- * Predicate to match changes that contains specified text in commit messages
- * body.
- */
-public class LegacyMessagePredicate extends RevWalkPredicate {
-
-  private static final Logger log = LoggerFactory
-      .getLogger(LegacyMessagePredicate.class);
-
-  private final RevFilter rFilter;
-
-  public LegacyMessagePredicate(Provider<ReviewDb> db,
-      GitRepositoryManager repoManager, String text) {
-    super(db, repoManager, ChangeQueryBuilder.FIELD_MESSAGE, text);
-    this.rFilter = MessageRevFilter.create(text);
-  }
-
-  @Override
-  public boolean match(Repository repo, RevWalk rw, Arguments args) {
-    try {
-      return rFilter.include(rw, rw.parseCommit(args.objectId));
-    } catch (MissingObjectException e) {
-      log.error(args.projectName.get() + "\" commit does not exist.", e);
-    } catch (IncorrectObjectTypeException e) {
-      log.error(args.projectName.get() + "\" revision is not a commit.", e);
-    } catch (IOException e) {
-      log.error(
-          "Could not search for commit message in \"" + args.projectName.get()
-              + "\" repository.", e);
-    }
-    return false;
-  }
-
-  @Override
-  public int getCost() {
-    return 1;
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
index 64e6b10..04bdb1e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
@@ -14,35 +14,33 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.ChangeIndex;
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 /**
  * Predicate to match changes that contains specified text in commit messages
  * body.
  */
 class MessagePredicate extends IndexPredicate<ChangeData> {
-  private final Provider<ReviewDb> db;
+  private final Arguments args;
   private final ChangeIndex index;
 
-  MessagePredicate(Provider<ReviewDb> db, ChangeIndex index, String value) {
+  MessagePredicate(Arguments args, ChangeIndex index, String value) {
     super(ChangeField.COMMIT_MESSAGE, value);
-    this.db = db;
+    this.args = args;
     this.index = index;
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   public boolean match(ChangeData object) throws OrmException {
     try {
       for (ChangeData cData : index.getSource(
-          Predicate.and(new LegacyChangeIdPredicate(db, object.getId()), this), 1)
+          Predicate.and(new LegacyChangeIdPredicate(args, object.getId()), this), 0, 1)
           .read()) {
         if (cData.getId().equals(object.getId())) {
           return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
index 10f2d35..87ebffb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
@@ -16,19 +16,15 @@
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 class OwnerPredicate extends IndexPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
   private final Account.Id id;
 
-  OwnerPredicate(Provider<ReviewDb> dbProvider, Account.Id id) {
+  OwnerPredicate(Account.Id id) {
     super(ChangeField.OWNER, id.toString());
-    this.dbProvider = dbProvider;
     this.id = id;
   }
 
@@ -38,7 +34,7 @@
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
+    Change change = object.change();
     return change != null && id.equals(change.getOwner());
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
index 05c4cbf..a0c1235 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
@@ -41,7 +41,7 @@
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    final Change change = object.change(dbProvider);
+    final Change change = object.change();
     if (change == null) {
       return false;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java
index d9ff80c..e411cf9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Paginated.java
@@ -21,4 +21,6 @@
   int limit();
 
   ResultSet<ChangeData> restart(ChangeData last) throws OrmException;
+
+  ResultSet<ChangeData> restart(int start) throws OrmException;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
new file mode 100644
index 0000000..822ffc2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.project.ListChildProjects;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectJson.ProjectInfo;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+import java.util.List;
+
+class ParentProjectPredicate extends OrPredicate<ChangeData> {
+  private final String value;
+
+  ParentProjectPredicate(Provider<ReviewDb> dbProvider,
+      ProjectCache projectCache, Provider<ListChildProjects> listChildProjects,
+      Provider<CurrentUser> self, String value) {
+    super(predicates(dbProvider, projectCache, listChildProjects, self, value));
+    this.value = value;
+  }
+
+  private static List<Predicate<ChangeData>> predicates(
+      Provider<ReviewDb> dbProvider, ProjectCache projectCache,
+      Provider<ListChildProjects> listChildProjects,
+      Provider<CurrentUser> self, String value) {
+    ProjectState projectState = projectCache.get(new Project.NameKey(value));
+    if (projectState == null) {
+      return Collections.emptyList();
+    }
+
+    List<Predicate<ChangeData>> r = Lists.newArrayList();
+    r.add(new ProjectPredicate(projectState.getProject().getName()));
+    ListChildProjects children = listChildProjects.get();
+    children.setRecursive(true);
+    for (ProjectInfo p : children.apply(new ProjectResource(
+        projectState.controlFor(self.get())))) {
+      r.add(new ProjectPredicate(p.name));
+    }
+    return r;
+  }
+
+  @Override
+  public String toString() {
+    return ChangeQueryBuilder.FIELD_PARENTPROJECT + ":" + value;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
index fe8d937..872b854 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
@@ -16,18 +16,13 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 class ProjectPredicate extends IndexPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
-
-  ProjectPredicate(Provider<ReviewDb> dbProvider, String id) {
+  ProjectPredicate(String id) {
     super(ChangeField.PROJECT, id);
-    this.dbProvider = dbProvider;
   }
 
   Project.NameKey getValueKey() {
@@ -36,7 +31,7 @@
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
+    Change change = object.change();
     if (change == null) {
       return false;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 4b6a5a6..41c2e23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.common.collect.Lists;
-import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.common.ListChangesOption;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -45,7 +45,7 @@
   private boolean reverse;
   private EnumSet<ListChangesOption> options;
 
-  @Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", multiValued = true, usage = "Query string")
+  @Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "Query string")
   private List<String> queries;
 
   @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "Maximum number of results to return")
@@ -53,7 +53,7 @@
     imp.setLimit(limit);
   }
 
-  @Option(name = "-o", multiValued = true, usage = "Output options per change")
+  @Option(name = "-o", usage = "Output options per change")
   public void addOption(ListChangesOption o) {
     options.add(o);
   }
@@ -80,6 +80,11 @@
     imp.setSortkeyBefore(key);
   }
 
+  @Option(name = "-S", metaVar = "CNT", usage = "Number of changes to skip")
+  public void setStart(int start) {
+    imp.setStart(start);
+  }
+
   @Inject
   QueryChanges(ChangeJson json, QueryProcessor qp, Provider<CurrentUser> user) {
     this.json = json;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index 2f3fe54..3320886 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -22,11 +23,11 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.data.ChangeAttribute;
 import com.google.gerrit.server.data.PatchSetAttribute;
 import com.google.gerrit.server.data.QueryStatsAttribute;
 import com.google.gerrit.server.events.EventFactory;
-import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.Predicate;
@@ -66,7 +67,7 @@
         @Override
         public int compare(ChangeData a, ChangeData b) {
           try {
-            return a.change(db).getSortKey().compareTo(b.change(db).getSortKey());
+            return a.change().getSortKey().compareTo(b.change().getSortKey());
           } catch (OrmException e) {
             return 0;
           }
@@ -78,7 +79,7 @@
         @Override
         public int compare(ChangeData a, ChangeData b) {
           try {
-            return b.change(db).getSortKey().compareTo(a.change(db).getSortKey());
+            return b.change().getSortKey().compareTo(a.change().getSortKey());
           } catch (OrmException e) {
             return 0;
           }
@@ -86,7 +87,7 @@
       };
 
   public static enum OutputFormat {
-    TEXT, JSON;
+    TEXT, JSON
   }
 
   private final Gson gson = new Gson();
@@ -97,13 +98,13 @@
   private final ChangeQueryBuilder queryBuilder;
   private final ChangeQueryRewriter queryRewriter;
   private final Provider<ReviewDb> db;
-  private final GitRepositoryManager repoManager;
-  private final ChangeControl.GenericFactory changeControlFactory;
+  private final TrackingFooters trackingFooters;
   private final CurrentUser user;
   private final int maxLimit;
 
   private OutputFormat outputFormat = OutputFormat.TEXT;
   private int limit;
+  private int start;
   private String sortkeyAfter;
   private String sortkeyBefore;
   private boolean includePatchSets;
@@ -114,6 +115,7 @@
   private boolean includeCommitMessage;
   private boolean includeDependencies;
   private boolean includeSubmitRecords;
+  private boolean includeAllReviewers;
 
   private OutputStream outputStream = DisabledOutputStream.INSTANCE;
   private PrintWriter out;
@@ -123,14 +125,12 @@
   QueryProcessor(EventFactory eventFactory,
       ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
       ChangeQueryRewriter queryRewriter, Provider<ReviewDb> db,
-      GitRepositoryManager repoManager,
-      ChangeControl.GenericFactory changeControlFactory) {
+      TrackingFooters trackingFooters) {
     this.eventFactory = eventFactory;
     this.queryBuilder = queryBuilder.create(currentUser);
     this.queryRewriter = queryRewriter;
     this.db = db;
-    this.repoManager = repoManager;
-    this.changeControlFactory = changeControlFactory;
+    this.trackingFooters = trackingFooters;
     this.user = currentUser;
     this.maxLimit = currentUser.getCapabilities()
       .getRange(GlobalCapability.QUERY_LIMIT)
@@ -146,6 +146,10 @@
     limit = n;
   }
 
+  void setStart(int n) {
+    start = n;
+  }
+
   void setSortkeyAfter(String sortkey) {
     sortkeyAfter = sortkey;
   }
@@ -202,6 +206,10 @@
     includeSubmitRecords = on;
   }
 
+  public void setIncludeAllReviewers(boolean on) {
+    includeAllReviewers = on;
+  }
+
   public void setOutput(OutputStream out, OutputFormat fmt) {
     this.outputStream = out;
     this.outputFormat = fmt;
@@ -236,21 +244,19 @@
     // Parse and rewrite all queries.
     List<Integer> limits = Lists.newArrayListWithCapacity(cnt);
     List<ChangeDataSource> sources = Lists.newArrayListWithCapacity(cnt);
-    for (int i = 0; i < cnt; i++) {
-      Predicate<ChangeData> q = parseQuery(queries.get(i), visibleToMe);
-      Predicate<ChangeData> s = queryRewriter.rewrite(q);
+    for (String query : queries) {
+      Predicate<ChangeData> q = parseQuery(query, visibleToMe);
+      Predicate<ChangeData> s = queryRewriter.rewrite(q, start);
       if (!(s instanceof ChangeDataSource)) {
-        @SuppressWarnings("unchecked")
-        Predicate<ChangeData> o = Predicate.and(queryBuilder.status_open(), q);
-        q = o;
-        s = queryRewriter.rewrite(q);
+        q = Predicate.and(queryBuilder.status_open(), q);
+        s = queryRewriter.rewrite(q, start);
       }
       if (!(s instanceof ChangeDataSource)) {
         throw new QueryParseException("invalid query: " + s);
       }
 
       // Don't trust QueryRewriter to have left the visible predicate.
-      AndSource a = new AndSource(db, ImmutableList.of(s, visibleToMe));
+      AndSource a = new AndSource(ImmutableList.of(s, visibleToMe), start);
       limits.add(limit(q));
       sources.add(a);
     }
@@ -260,12 +266,15 @@
     for (ChangeDataSource s : sources) {
       matches.add(s.read());
     }
-    sources = null;
 
     List<List<ChangeData>> out = Lists.newArrayListWithCapacity(cnt);
     for (int i = 0; i < cnt; i++) {
       List<ChangeData> results = matches.get(i).toList();
-      Collections.sort(results, sortkeyAfter != null ? cmpAfter : cmpBefore);
+      if (sortkeyAfter != null) {
+        Collections.sort(results, cmpAfter);
+      } else if (sortkeyBefore != null) {
+        Collections.sort(results, cmpBefore);
+      }
       if (results.size() > maxLimit) {
         moreResults = true;
       }
@@ -300,17 +309,23 @@
         List<ChangeData> results = queryChanges(queryString);
         ChangeAttribute c = null;
         for (ChangeData d : results) {
-          ChangeControl cc = d.changeControl();
-          if (cc == null || cc.getCurrentUser() != user) {
-            cc = changeControlFactory.controlFor(d.change(db), user);
-          }
+          ChangeControl cc = d.changeControl().forUser(user);
+
           LabelTypes labelTypes = cc.getLabelTypes();
-          c = eventFactory.asChangeAttribute(d.getChange());
-          eventFactory.extend(c, d.getChange());
-          eventFactory.addTrackingIds(c, d.trackingIds(db));
+          c = eventFactory.asChangeAttribute(d.change());
+          eventFactory.extend(c, d.change());
+
+          if (!trackingFooters.isEmpty()) {
+            eventFactory.addTrackingIds(c,
+                trackingFooters.extract(d.commitFooters()));
+          }
+
+          if (includeAllReviewers) {
+            eventFactory.addAllReviewers(c, d.notes());
+          }
 
           if (includeSubmitRecords) {
-            PatchSet.Id psId = d.getChange().currentPatchSetId();
+            PatchSet.Id psId = d.change().currentPatchSetId();
             PatchSet patchSet = db.get().patchSets().get(psId);
             List<SubmitRecord> submitResult = cc.canSubmit( //
                 db.get(), patchSet, null, false, true, true);
@@ -318,46 +333,46 @@
           }
 
           if (includeCommitMessage) {
-            eventFactory.addCommitMessage(c, d.commitMessage(repoManager, db));
+            eventFactory.addCommitMessage(c, d.commitMessage());
           }
 
           if (includePatchSets) {
             if (includeFiles) {
-              eventFactory.addPatchSets(c, d.patches(db),
-                includeApprovals ? d.approvalsMap(db).asMap() : null,
-                includeFiles, d.change(db), labelTypes);
+              eventFactory.addPatchSets(c, d.patches(),
+                includeApprovals ? d.approvals().asMap() : null,
+                includeFiles, d.change(), labelTypes);
             } else {
-              eventFactory.addPatchSets(c, d.patches(db),
-                  includeApprovals ? d.approvalsMap(db).asMap() : null,
+              eventFactory.addPatchSets(c, d.patches(),
+                  includeApprovals ? d.approvals().asMap() : null,
                   labelTypes);
             }
           }
 
           if (includeCurrentPatchSet) {
-            PatchSet current = d.currentPatchSet(db);
+            PatchSet current = d.currentPatchSet();
             if (current != null) {
               c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
               eventFactory.addApprovals(c.currentPatchSet,
-                  d.currentApprovals(db), labelTypes);
+                  d.currentApprovals(), labelTypes);
 
               if (includeFiles) {
                 eventFactory.addPatchSetFileNames(c.currentPatchSet,
-                    d.change(db), d.currentPatchSet(db));
+                    d.change(), d.currentPatchSet());
               }
             }
           }
 
           if (includeComments) {
-            eventFactory.addComments(c, d.messages(db));
+            eventFactory.addComments(c, d.messages());
             if (includePatchSets) {
               for (PatchSetAttribute attribute : c.patchSets) {
-                eventFactory.addPatchSetComments(attribute,  d.comments(db));
+                eventFactory.addPatchSetComments(attribute,  d.comments());
               }
             }
           }
 
           if (includeDependencies) {
-            eventFactory.addDependencies(c, d.getChange());
+            eventFactory.addDependencies(c, d.change());
           }
 
           show(c);
@@ -401,17 +416,14 @@
   }
 
   private int limit(Predicate<ChangeData> s) {
-    int n = ChangeQueryBuilder.hasLimit(s)
-        ? ChangeQueryBuilder.getLimit(s)
-        : maxLimit;
+    int n = Objects.firstNonNull(ChangeQueryBuilder.getLimit(s), maxLimit);
     return limit > 0 ? Math.min(n, limit) + 1 : n + 1;
   }
 
-  @SuppressWarnings("unchecked")
   private Predicate<ChangeData> parseQuery(String queryString,
       final Predicate<ChangeData> visibleToMe) throws QueryParseException {
     Predicate<ChangeData> q = queryBuilder.parse(queryString);
-    if (!ChangeQueryBuilder.hasSortKey(q)) {
+    if (queryBuilder.supportsSortKey() && !ChangeQueryBuilder.hasSortKey(q)) {
       if (sortkeyBefore != null) {
         q = Predicate.and(q, queryBuilder.sortkey_before(sortkeyBefore));
       } else if (sortkeyAfter != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
index df0150f..8a43fb1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
@@ -15,23 +15,18 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 class RefPredicate extends IndexPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
-
-  RefPredicate(Provider<ReviewDb> dbProvider, String ref) {
+  RefPredicate(String ref) {
     super(ChangeField.REF, ref);
-    this.dbProvider = dbProvider;
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
+    Change change = object.change();
     if (change == null) {
       return false;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
similarity index 84%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
index 1ce9139..d073002 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexFilePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
@@ -14,12 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.RegexPredicate;
-import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 import dk.brics.automaton.Automaton;
 import dk.brics.automaton.RegExp;
@@ -28,9 +25,7 @@
 import java.util.Collections;
 import java.util.List;
 
-class RegexFilePredicate extends RegexPredicate<ChangeData> {
-  private final Provider<ReviewDb> db;
-  private final PatchListCache cache;
+class RegexPathPredicate extends RegexPredicate<ChangeData> {
   private final RunAutomaton pattern;
 
   private final String prefixBegin;
@@ -38,10 +33,8 @@
   private final int prefixLen;
   private final boolean prefixOnly;
 
-  RegexFilePredicate(Provider<ReviewDb> db, PatchListCache plc, String re) {
-    super(ChangeField.FILE, re);
-    this.db = db;
-    this.cache = plc;
+  RegexPathPredicate(String fieldName, String re) {
+    super(ChangeField.PATH, re);
 
     if (re.startsWith("^")) {
       re = re.substring(1);
@@ -69,7 +62,7 @@
 
   @Override
   public boolean match(ChangeData object) throws OrmException {
-    List<String> files = object.currentFilePaths(db, cache);
+    List<String> files = object.currentFilePaths();
     if (files != null) {
       int begin, end;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
index b6c724d..df4fff9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
@@ -16,20 +16,17 @@
 
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.RegexPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 import dk.brics.automaton.RegExp;
 import dk.brics.automaton.RunAutomaton;
 
 class RegexProjectPredicate extends RegexPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
   private final RunAutomaton pattern;
 
-  RegexProjectPredicate(Provider<ReviewDb> dbProvider, String re) {
+  RegexProjectPredicate(String re) {
     super(ChangeField.PROJECT, re);
 
     if (re.startsWith("^")) {
@@ -40,13 +37,12 @@
       re = re.substring(0, re.length() - 1);
     }
 
-    this.dbProvider = dbProvider;
     this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
+    Change change = object.change();
     if (change == null) {
       return false;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
index 22fb49b..31625df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
@@ -15,20 +15,17 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.RegexPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 import dk.brics.automaton.RegExp;
 import dk.brics.automaton.RunAutomaton;
 
 class RegexRefPredicate extends RegexPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
   private final RunAutomaton pattern;
 
-  RegexRefPredicate(Provider<ReviewDb> dbProvider, String re) {
+  RegexRefPredicate(String re) {
     super(ChangeField.REF, re);
 
     if (re.startsWith("^")) {
@@ -39,13 +36,12 @@
       re = re.substring(0, re.length() - 1);
     }
 
-    this.dbProvider = dbProvider;
     this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
+    Change change = object.change();
     if (change == null) {
       return false;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
index 51b9c48..3a9604f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
@@ -15,20 +15,17 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.RegexPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 import dk.brics.automaton.RegExp;
 import dk.brics.automaton.RunAutomaton;
 
 class RegexTopicPredicate extends RegexPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
   private final RunAutomaton pattern;
 
-  RegexTopicPredicate(Provider<ReviewDb> dbProvider, String re) {
+  RegexTopicPredicate(String re) {
     super(ChangeField.TOPIC, re);
 
     if (re.startsWith("^")) {
@@ -39,13 +36,12 @@
       re = re.substring(0, re.length() - 1);
     }
 
-    this.dbProvider = dbProvider;
     this.pattern = new RunAutomaton(new RegExp(re).toAutomaton());
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
+    Change change = object.change();
     if (change == null || change.getTopic() == null) {
       return false;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java
index 58c0fee..fd57a44 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RevWalkPredicate.java
@@ -75,7 +75,7 @@
 
   @Override
   public boolean match(ChangeData object) throws OrmException {
-    final PatchSet patchSet = object.currentPatchSet(db);
+    final PatchSet patchSet = object.currentPatchSet();
     if (patchSet == null) {
       return false;
     }
@@ -90,7 +90,7 @@
       return false;
     }
 
-    Change change = object.change(db);
+    Change change = object.change();
     if (change == null) {
       return false;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
index 9e9d8bf..004de72 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -15,21 +15,19 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 class ReviewerPredicate extends IndexPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
   private final Account.Id id;
+  private boolean allowDrafts;
 
-  ReviewerPredicate(Provider<ReviewDb> dbProvider, Account.Id id) {
+  ReviewerPredicate(Account.Id id, boolean allowDrafts) {
     super(ChangeField.REVIEWER, id.toString());
-    this.dbProvider = dbProvider;
     this.id = id;
+    this.allowDrafts = allowDrafts;
   }
 
   Account.Id getAccountId() {
@@ -38,8 +36,12 @@
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    for (PatchSetApproval p : object.approvals(dbProvider)) {
-      if (id.equals(p.getAccountId())) {
+    if (!allowDrafts &&
+        object.change().getStatus() == Change.Status.DRAFT) {
+      return false;
+    }
+    for (Account.Id accountId : object.reviewers().values()) {
+      if (id.equals(accountId)) {
         return true;
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
index 421c6c6..a29ac62 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.query.OperatorPredicate;
@@ -41,9 +41,8 @@
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    for (PatchSetApproval p : object.approvals(dbProvider)) {
-      final IdentifiedUser reviewer = userFactory.create(dbProvider,
-        p.getAccountId());
+    for (Account.Id accountId : object.reviewers().values()) {
+      IdentifiedUser reviewer = userFactory.create(dbProvider, accountId);
       if (reviewer.getEffectiveGroups().contains(uuid)) {
         return true;
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
index c612945..6fa11fd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
@@ -29,6 +29,10 @@
 import com.google.inject.Provider;
 
 public abstract class SortKeyPredicate extends IndexPredicate<ChangeData> {
+  public static boolean hasSortKeyField(Schema<ChangeData> schema) {
+    return sortkeyFieldOrNull(schema) != null;
+  }
+
   @SuppressWarnings("deprecation")
   private static long parseSortKey(Schema<ChangeData> schema, String value) {
     FieldDef<ChangeData, ?> field = schema.getFields().get(SORTKEY.getName());
@@ -40,7 +44,8 @@
   }
 
   @SuppressWarnings("deprecation")
-  private static FieldDef<ChangeData, ?> sortkeyField(Schema<ChangeData> schema) {
+  private static FieldDef<ChangeData, ?> sortkeyFieldOrNull(
+      Schema<ChangeData> schema) {
     if (schema == null) {
       return ChangeField.LEGACY_SORTKEY;
     }
@@ -48,9 +53,13 @@
     if (f != null) {
       return f;
     }
+    return schema.getFields().get(ChangeField.LEGACY_SORTKEY.getName());
+  }
+
+  private static FieldDef<ChangeData, ?> sortkeyField(Schema<ChangeData> schema) {
     return checkNotNull(
-        schema.getFields().get(ChangeField.LEGACY_SORTKEY.getName()),
-        "schema missing sortkey field, found: %s", schema.getFields().keySet());
+        sortkeyFieldOrNull(schema),
+        "schema missing sortkey field, found: %s", schema);
   }
 
   protected final Schema<ChangeData> schema;
@@ -90,7 +99,7 @@
 
     @Override
     public boolean match(ChangeData cd) throws OrmException {
-      Change change = cd.change(dbProvider);
+      Change change = cd.change();
       return change != null && change.getSortKey().compareTo(getValue()) < 0;
     }
 
@@ -118,7 +127,7 @@
 
     @Override
     public boolean match(ChangeData cd) throws OrmException {
-      Change change = cd.change(dbProvider);
+      Change change = cd.change();
       return change != null && change.getSortKey().compareTo(getValue()) > 0;
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SqlRewriterImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SqlRewriterImpl.java
deleted file mode 100644
index fcbc344..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SqlRewriterImpl.java
+++ /dev/null
@@ -1,659 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.query.change;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ChangeAccess;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.query.IntPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryRewriter;
-import com.google.gerrit.server.query.RewritePredicate;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder.LimitPredicate;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.name.Named;
-
-import java.util.Collection;
-
-public class SqlRewriterImpl extends BasicChangeRewrites
-    implements ChangeQueryRewriter {
-  private static final QueryRewriter.Definition<ChangeData, SqlRewriterImpl> mydef =
-      new QueryRewriter.Definition<ChangeData, SqlRewriterImpl>(
-          SqlRewriterImpl.class, BUILDER);
-
-  @Inject
-  @VisibleForTesting
-  public SqlRewriterImpl(Provider<ReviewDb> dbProvider) {
-    super(mydef, dbProvider, null);
-  }
-
-  @Override
-  public Predicate<ChangeData> and(Collection<? extends Predicate<ChangeData>> l) {
-    return hasSource(l) ? new AndSource(dbProvider, l) : super.and(l);
-  }
-
-  @Override
-  public Predicate<ChangeData> or(Collection<? extends Predicate<ChangeData>> l) {
-    return hasSource(l) ? new OrSource(l) : super.or(l);
-  }
-
-  @Rewrite("status:open P=(project:*) B=(ref:*)")
-  public Predicate<ChangeData> r05_byBranchOpen(
-      @Named("P") final ProjectPredicate p,
-      @Named("B") final RefPredicate b) {
-    return new ChangeSource(500) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a)
-          throws OrmException {
-        return a.byBranchOpenAll(
-            new Branch.NameKey(p.getValueKey(), b.getValue()));
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen()
-            && p.match(cd)
-            && b.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:merged P=(project:*) B=(ref:*) S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r05_byBranchMergedPrev(
-      @Named("P") final ProjectPredicate p,
-      @Named("B") final RefPredicate b,
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byBranchClosedPrev(Change.Status.MERGED.getCode(), //
-            new Branch.NameKey(p.getValueKey(), b.getValue()), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && p.match(cd) //
-            && b.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:merged P=(project:*) B=(ref:*) S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r05_byBranchMergedNext(
-      @Named("P") final ProjectPredicate p,
-      @Named("B") final RefPredicate b,
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byBranchClosedNext(Change.Status.MERGED.getCode(), //
-            new Branch.NameKey(p.getValueKey(), b.getValue()), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && p.match(cd) //
-            && b.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:open P=(project:*) S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectOpenPrev(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(500, s.getValue(), l.intValue()) {
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectOpenPrev(p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() //
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:open P=(project:*) S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectOpenNext(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(500, s.getValue(), l.intValue()) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectOpenNext(p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() //
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:merged P=(project:*) S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectMergedPrev(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectClosedPrev(Change.Status.MERGED.getCode(), //
-            p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:merged P=(project:*) S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectMergedNext(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectClosedNext(Change.Status.MERGED.getCode(), //
-            p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:abandoned P=(project:*) S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectAbandonedPrev(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectClosedPrev(Change.Status.ABANDONED.getCode(), //
-            p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:abandoned P=(project:*) S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r10_byProjectAbandonedNext(
-      @Named("P") final ProjectPredicate p,
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(40000, s.getValue(), l.intValue()) {
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.byProjectClosedNext(Change.Status.ABANDONED.getCode(), //
-            p.getValueKey(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
-            && p.match(cd) //
-            && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:open S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byOpenPrev(
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(2000, s.getValue(), l.intValue()) {
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allOpenPrev(key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() && s.match(cd);
-      }
-    };
-  }
-
-  @Rewrite("status:open S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byOpenNext(
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(2000, s.getValue(), l.intValue()) {
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allOpenNext(key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:merged S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byMergedPrev(
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
-      {
-        init("r20_byMergedPrev", s, l);
-      }
-
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allClosedPrev(Change.Status.MERGED.getCode(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:merged S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byMergedNext(
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
-      {
-        init("r20_byMergedNext", s, l);
-      }
-
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allClosedNext(Change.Status.MERGED.getCode(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.MERGED
-            && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:abandoned S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byAbandonedPrev(
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
-      {
-        init("r20_byAbandonedPrev", s, l);
-      }
-
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allClosedPrev(Change.Status.ABANDONED.getCode(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
-            && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:abandoned S=(sortkey_before:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byAbandonedNext(
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return new PaginatedSource(50000, s.getValue(), l.intValue()) {
-      {
-        init("r20_byAbandonedNext", s, l);
-      }
-
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-          throws OrmException {
-        return a.allClosedNext(Change.Status.ABANDONED.getCode(), key, limit);
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.ABANDONED
-            && s.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:closed S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byClosedPrev(
-      @Named("S") final SortKeyPredicate.After s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return or(r20_byMergedPrev(s, l), r20_byAbandonedPrev(s, l));
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:closed S=(sortkey_after:*) L=(limit:*)")
-  public Predicate<ChangeData> r20_byClosedNext(
-      @Named("S") final SortKeyPredicate.Before s,
-      @Named("L") final IntPredicate<ChangeData> l) {
-    return or(r20_byMergedNext(s, l), r20_byAbandonedNext(s, l));
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:open O=(owner:*)")
-  public Predicate<ChangeData> r25_byOwnerOpen(
-      @Named("O") final OwnerPredicate o) {
-    return new ChangeSource(50) {
-      {
-        init("r25_byOwnerOpen", o);
-      }
-
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-        return a.byOwnerOpen(o.getAccountId());
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isOpen() && o.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:closed O=(owner:*)")
-  public Predicate<ChangeData> r25_byOwnerClosed(
-      @Named("O") final OwnerPredicate o) {
-    return new ChangeSource(5000) {
-      {
-        init("r25_byOwnerClosed", o);
-      }
-
-      @SuppressWarnings("deprecation")
-      @Override
-      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-        return a.byOwnerClosedAll(o.getAccountId());
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus().isClosed() && o.match(cd);
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("O=(owner:*)")
-  public Predicate<ChangeData> r26_byOwner(@Named("O") OwnerPredicate o) {
-    return or(r25_byOwnerOpen(o), r25_byOwnerClosed(o));
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:open R=(reviewer:*)")
-  public Predicate<ChangeData> r30_byReviewerOpen(
-      @Named("R") final ReviewerPredicate r) {
-    return new Source() {
-      {
-        init("r30_byReviewerOpen", r);
-      }
-
-      @SuppressWarnings("deprecation")
-      @Override
-      public ResultSet<ChangeData> read() throws OrmException {
-        return ChangeDataResultSet.patchSetApproval(dbProvider.get()
-            .patchSetApprovals().openByUser(r.getAccountId()));
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        Change change = cd.change(dbProvider);
-        return change != null && change.getStatus().isOpen() && r.match(cd);
-      }
-
-      @Override
-      public int getCardinality() {
-        return 50;
-      }
-
-      @Override
-      public int getCost() {
-        return ChangeCosts.cost(ChangeCosts.APPROVALS_SCAN, getCardinality());
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("status:closed R=(reviewer:*)")
-  public Predicate<ChangeData> r30_byReviewerClosed(
-      @Named("R") final ReviewerPredicate r) {
-    return new Source() {
-      {
-        init("r30_byReviewerClosed", r);
-      }
-
-      @SuppressWarnings("deprecation")
-      @Override
-      public ResultSet<ChangeData> read() throws OrmException {
-        return ChangeDataResultSet.patchSetApproval(dbProvider.get()
-            .patchSetApprovals().closedByUserAll(r.getAccountId()));
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        Change change = cd.change(dbProvider);
-        return change != null && change.getStatus().isClosed() && r.match(cd);
-      }
-
-      @Override
-      public int getCardinality() {
-        return 5000;
-      }
-
-      @Override
-      public int getCost() {
-        return ChangeCosts.cost(ChangeCosts.APPROVALS_SCAN, getCardinality());
-      }
-    };
-  }
-
-  @SuppressWarnings("unchecked")
-  @Rewrite("R=(reviewer:*)")
-  public Predicate<ChangeData> r31_byReviewer(
-      @Named("R") final ReviewerPredicate r) {
-    return or(r30_byReviewerOpen(r), r30_byReviewerClosed(r));
-  }
-
-  @Rewrite("status:closed")
-  public Predicate<ChangeData> r99_allClosed() {
-    return r20_byClosedNext(
-        new SortKeyPredicate.Before(null, dbProvider, "z"),
-        // MySQL Connector/J 5.1.21 has a hard limit on 50M rows.
-        new LimitPredicate(50000000));
-  }
-
-  @Rewrite("status:submitted")
-  public Predicate<ChangeData> r99_allSubmitted() {
-    return new ChangeSource(50) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-        return a.allSubmitted();
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return cd.change(dbProvider).getStatus() == Change.Status.SUBMITTED;
-      }
-    };
-  }
-
-  @Rewrite("P=(project:*)")
-  public Predicate<ChangeData> r99_byProject(
-      @Named("P") final ProjectPredicate p) {
-    return new ChangeSource(1000000) {
-      @Override
-      ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-        return a.byProject(p.getValueKey());
-      }
-
-      @Override
-      public boolean match(ChangeData cd) throws OrmException {
-        return p.match(cd);
-      }
-    };
-  }
-
-  private static boolean hasSource(Collection<? extends Predicate<ChangeData>> l) {
-    for (Predicate<ChangeData> p : l) {
-      if (p instanceof ChangeDataSource) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private abstract static class Source extends RewritePredicate<ChangeData>
-      implements ChangeDataSource {
-    @Override
-    public boolean hasChange() {
-      return false;
-    }
-  }
-
-  private abstract class ChangeSource extends Source {
-    private final int cardinality;
-
-    ChangeSource(int card) {
-      this.cardinality = card;
-    }
-
-    abstract ResultSet<Change> scan(ChangeAccess a) throws OrmException;
-
-    @Override
-    public ResultSet<ChangeData> read() throws OrmException {
-      return ChangeDataResultSet.change(scan(dbProvider.get().changes()));
-    }
-
-    @Override
-    public boolean hasChange() {
-      return true;
-    }
-
-    @Override
-    public int getCardinality() {
-      return cardinality;
-    }
-
-    @Override
-    public int getCost() {
-      return ChangeCosts.cost(ChangeCosts.CHANGES_SCAN, getCardinality());
-    }
-  }
-
-  private abstract class PaginatedSource extends ChangeSource implements
-      Paginated {
-    private final String startKey;
-    private final int limit;
-
-    PaginatedSource(int card, String start, int lim) {
-      super(card);
-      this.startKey = start;
-      this.limit = lim;
-    }
-
-    @Override
-    public int limit() {
-      return limit;
-    }
-
-    @Override
-    ResultSet<Change> scan(ChangeAccess a) throws OrmException {
-      return scan(a, startKey, limit);
-    }
-
-    @Override
-    public ResultSet<ChangeData> restart(ChangeData last) throws OrmException {
-      return ChangeDataResultSet.change(scan(dbProvider.get().changes(), //
-          last.change(dbProvider).getSortKey(), //
-          limit));
-    }
-
-    abstract ResultSet<Change> scan(ChangeAccess a, String key, int limit)
-        throws OrmException;
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
index 9393fe1..ee4e2ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
@@ -15,23 +15,18 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Provider;
 
 class TopicPredicate extends IndexPredicate<ChangeData> {
-  private final Provider<ReviewDb> dbProvider;
-
-  TopicPredicate(Provider<ReviewDb> dbProvider, String topic) {
+  TopicPredicate(String topic) {
     super(ChangeField.TOPIC, topic);
-    this.dbProvider = dbProvider;
   }
 
   @Override
   public boolean match(final ChangeData object) throws OrmException {
-    Change change = object.change(dbProvider);
+    Change change = object.change();
     if (change == null) {
       return false;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
index 89e5ba9..9e60a4225 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
@@ -15,65 +15,45 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.TrackingId;
-import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gwtorm.server.ListResultSet;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
 
-import java.util.ArrayList;
-import java.util.HashSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-class TrackingIdPredicate extends IndexPredicate<ChangeData> implements
-    ChangeDataSource {
-  private final Provider<ReviewDb> db;
+import java.io.IOException;
 
-  TrackingIdPredicate(Provider<ReviewDb> db, String trackingId) {
+class TrackingIdPredicate extends IndexPredicate<ChangeData> {
+  private static final Logger log = LoggerFactory.getLogger(TrackingIdPredicate.class);
+
+  private final TrackingFooters trackingFooters;
+
+  TrackingIdPredicate(TrackingFooters trackingFooters, String trackingId) {
     super(ChangeField.TR, trackingId);
-    this.db = db;
+    this.trackingFooters = trackingFooters;
   }
 
   @Override
-  public boolean match(final ChangeData object) throws OrmException {
-    for (TrackingId c : object.trackingIds(db)) {
-      if (getValue().equals(c.getTrackingId())) {
-        return true;
+  public boolean match(ChangeData object) throws OrmException {
+    Change c = object.change();
+    if (c != null) {
+      try {
+        return trackingFooters.extract(object.commitFooters())
+            .values().contains(getValue());
+      } catch (NoSuchChangeException | IOException e) {
+        log.warn("Cannot extract footers from " + c.getChangeId(), e);
       }
     }
     return false;
   }
 
-  @SuppressWarnings("deprecation")
-  @Override
-  public ResultSet<ChangeData> read() throws OrmException {
-    HashSet<Change.Id> ids = new HashSet<Change.Id>();
-    for (TrackingId sc : db.get().trackingIds() //
-        .byTrackingId(new TrackingId.Id(getValue()))) {
-      ids.add(sc.getChangeId());
-    }
-
-    ArrayList<ChangeData> r = new ArrayList<ChangeData>(ids.size());
-    for (Change.Id id : ids) {
-      r.add(new ChangeData(id));
-    }
-    return new ListResultSet<ChangeData>(r);
-  }
-
-  @Override
-  public boolean hasChange() {
-    return false;
-  }
-
-  @Override
-  public int getCardinality() {
-    return ChangeCosts.CARD_TRACKING_IDS;
-  }
-
   @Override
   public int getCost() {
-    return ChangeCosts.cost(ChangeCosts.TR_SCAN, getCardinality());
+    return ChangeCosts.cost(
+        ChangeCosts.TR_SCAN,
+        ChangeCosts.CARD_TRACKING_IDS);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index 93238a8..f592530 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -14,6 +14,10 @@
 
 package com.google.gerrit.server.schema;
 
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.group.SystemGroupBackend.PROJECT_OWNERS;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.common.data.AccessSection;
@@ -24,8 +28,8 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.AllProjectsName;
@@ -33,6 +37,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -65,15 +70,9 @@
     this.allProjectsName = allProjectsName;
     this.serverUser = serverUser;
 
-    this.anonymous = new GroupReference(
-        AccountGroup.ANONYMOUS_USERS,
-        "Anonymous Users");
-    this.registered = new GroupReference(
-        AccountGroup.REGISTERED_USERS,
-        "Registered Users");
-    this.owners = new GroupReference(
-        AccountGroup.PROJECT_OWNERS,
-        "Project Owners");
+    this.anonymous = SystemGroupBackend.getGroup(ANONYMOUS_USERS);
+    this.registered = SystemGroupBackend.getGroup(REGISTERED_USERS);
+    this.owners = SystemGroupBackend.getGroup(PROJECT_OWNERS);
   }
 
   public AllProjectsCreator setAdministrators(GroupReference admin) {
@@ -99,7 +98,7 @@
         initAllProjects(git);
 
         RefUpdate u = git.updateRef(Constants.HEAD);
-        u.link(GitRepositoryManager.REF_CONFIG);
+        u.link(RefNames.REFS_CONFIG);
       } catch (RepositoryNotFoundException err) {
         String name = allProjectsName.get();
         throw new IOException("Cannot create repository " + name, err);
@@ -133,7 +132,7 @@
     AccessSection all = config.getAccessSection(AccessSection.ALL, true);
     AccessSection heads = config.getAccessSection(AccessSection.HEADS, true);
     AccessSection tags = config.getAccessSection("refs/tags/*", true);
-    AccessSection meta = config.getAccessSection(GitRepositoryManager.REF_CONFIG, true);
+    AccessSection meta = config.getAccessSection(RefNames.REFS_CONFIG, true);
     AccessSection magic = config.getAccessSection("refs/for/" + AccessSection.ALL, true);
 
     grant(config, cap, GlobalCapability.ADMINISTRATE_SERVER, admin);
@@ -214,8 +213,8 @@
         new LabelValue((short) 2, "Looks good to me, approved"),
         new LabelValue((short) 1, "Looks good to me, but someone else must approve"),
         new LabelValue((short) 0, "No score"),
-        new LabelValue((short) -1, "I would prefer that you didn't submit this"),
-        new LabelValue((short) -2, "Do not submit")));
+        new LabelValue((short) -1, "I would prefer this is not merged as is"),
+        new LabelValue((short) -2, "This shall not be merged")));
     type.setAbbreviation("CR");
     type.setCopyMinScore(true);
     c.getLabelSections().put(type.getName(), type);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
index c96ed42..49ac452 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
@@ -19,6 +19,7 @@
 
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.persistence.DataSourceInterceptor;
 import com.google.gerrit.server.config.ConfigSection;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -32,6 +33,8 @@
 import org.apache.commons.dbcp.BasicDataSource;
 import org.eclipse.jgit.lib.Config;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.sql.SQLException;
 import java.util.Properties;
 
@@ -84,7 +87,7 @@
   }
 
   public static enum Context {
-    SINGLE_USER, MULTI_USER;
+    SINGLE_USER, MULTI_USER
   }
 
   private DataSource open(final SitePaths site, final Config cfg,
@@ -102,6 +105,7 @@
 
     String username = dbs.optional("username");
     String password = dbs.optional("password");
+    String interceptor = dbs.optional("dataSourceInterceptorClass");
 
     boolean usePool;
     if (context == Context.SINGLE_USER) {
@@ -126,7 +130,7 @@
       ds.setMaxWait(ConfigUtil.getTimeUnit(cfg, "database", null,
           "poolmaxwait", MILLISECONDS.convert(30, SECONDS), MILLISECONDS));
       ds.setInitialSize(ds.getMinIdle());
-      return ds;
+      return intercept(interceptor, ds);
 
     } else {
       // Don't use the connection pool.
@@ -141,10 +145,26 @@
         if (password != null) {
           p.setProperty("password", password);
         }
-        return new SimpleDataSource(p);
+        return intercept(interceptor, new SimpleDataSource(p));
       } catch (SQLException se) {
         throw new ProvisionException("Database unavailable", se);
       }
     }
   }
+
+  private DataSource intercept(String interceptor, DataSource ds) {
+    if (interceptor == null) {
+      return ds;
+    }
+    try {
+      Constructor<?> c = Class.forName(interceptor).getConstructor();
+      DataSourceInterceptor datasourceInterceptor =
+          (DataSourceInterceptor) c.newInstance();
+      return datasourceInterceptor.intercept("reviewDb", ds);
+    } catch (ClassNotFoundException | SecurityException | NoSuchMethodException
+        | IllegalArgumentException | InstantiationException
+        | IllegalAccessException | InvocationTargetException e) {
+      throw new ProvisionException("Cannot intercept datasource", e);
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java
index 14cb780..ecaaf5e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceType.java
@@ -28,7 +28,7 @@
 
   /**
    * Return a ScriptRunner that runs the index script. Must not return
-   * <code>null</code>, but may return a ScriptRunner that does nothing.
+   * {@code null}, but may return a ScriptRunner that does nothing.
    *
    * @throws IOException
    */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index 0e89a75..589d177 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -48,9 +48,6 @@
   private final int versionNbr;
 
   private AccountGroup admin;
-  private AccountGroup anonymous;
-  private AccountGroup registered;
-  private AccountGroup owners;
   private AccountGroup batch;
 
   @Inject
@@ -110,45 +107,17 @@
   private SystemConfig initSystemConfig(final ReviewDb c) throws OrmException {
     admin = newGroup(c, "Administrators", null);
     admin.setDescription("Gerrit Site Administrators");
-    admin.setType(AccountGroup.Type.INTERNAL);
     c.accountGroups().insert(Collections.singleton(admin));
     c.accountGroupNames().insert(
         Collections.singleton(new AccountGroupName(admin)));
 
-    anonymous =
-        newGroup(c, "Anonymous Users", AccountGroup.ANONYMOUS_USERS);
-    anonymous.setDescription("Any user, signed-in or not");
-    anonymous.setOwnerGroupUUID(admin.getGroupUUID());
-    anonymous.setType(AccountGroup.Type.SYSTEM);
-    c.accountGroups().insert(Collections.singleton(anonymous));
-    c.accountGroupNames().insert(
-        Collections.singleton(new AccountGroupName(anonymous)));
-
-    registered =
-        newGroup(c, "Registered Users", AccountGroup.REGISTERED_USERS);
-    registered.setDescription("Any signed-in user");
-    registered.setOwnerGroupUUID(admin.getGroupUUID());
-    registered.setType(AccountGroup.Type.SYSTEM);
-    c.accountGroups().insert(Collections.singleton(registered));
-    c.accountGroupNames().insert(
-        Collections.singleton(new AccountGroupName(registered)));
-
     batch = newGroup(c, "Non-Interactive Users", null);
     batch.setDescription("Users who perform batch actions on Gerrit");
     batch.setOwnerGroupUUID(admin.getGroupUUID());
-    batch.setType(AccountGroup.Type.INTERNAL);
     c.accountGroups().insert(Collections.singleton(batch));
     c.accountGroupNames().insert(
         Collections.singleton(new AccountGroupName(batch)));
 
-    owners = newGroup(c, "Project Owners", AccountGroup.PROJECT_OWNERS);
-    owners.setDescription("Any owner of the project");
-    owners.setOwnerGroupUUID(admin.getGroupUUID());
-    owners.setType(AccountGroup.Type.SYSTEM);
-    c.accountGroups().insert(Collections.singleton(owners));
-    c.accountGroupNames().insert(
-        Collections.singleton(new AccountGroupName(owners)));
-
     final SystemConfig s = SystemConfig.create();
     try {
       s.sitePath = site_path.getCanonicalPath();
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 2882d00..8800929 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_84> C = Schema_84.class;
+  public static final Class<Schema_93> C = Schema_93.class;
 
   public static class Module extends AbstractModule {
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
index e4aae2c..cbcda9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_53.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
@@ -43,6 +44,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.schema.Schema_77.LegacyLabelTypes;
 import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.server.OrmException;
@@ -124,14 +126,14 @@
     List<AccountGroup> groups = db.accountGroups().all().toList();
     for (AccountGroup g : groups) {
       if (g.getId().equals(systemConfig.ownerGroupId)) {
-        g.setGroupUUID(AccountGroup.PROJECT_OWNERS);
+        g.setGroupUUID(SystemGroupBackend.PROJECT_OWNERS);
         projectOwners = GroupReference.forGroup(g);
 
       } else if (g.getId().equals(systemConfig.anonymousGroupId)) {
-        g.setGroupUUID(AccountGroup.ANONYMOUS_USERS);
+        g.setGroupUUID(SystemGroupBackend.ANONYMOUS_USERS);
 
       } else if (g.getId().equals(systemConfig.registeredGroupId)) {
-        g.setGroupUUID(AccountGroup.REGISTERED_USERS);
+        g.setGroupUUID(SystemGroupBackend.REGISTERED_USERS);
 
       } else {
         g.setGroupUUID(GroupUUID.make(g.getName(), serverUser));
@@ -185,7 +187,7 @@
         // Grant out read on the config branch by default.
         //
         if (config.getProject().getNameKey().equals(systemConfig.wildProjectName)) {
-          AccessSection meta = config.getAccessSection(GitRepositoryManager.REF_CONFIG, true);
+          AccessSection meta = config.getAccessSection(RefNames.REFS_CONFIG, true);
           Permission read = meta.getPermission(READ, true);
           read.getRule(config.resolve(projectOwners), true);
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java
index 3ba77ec..bcd5f40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_56.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.schema;
 
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
@@ -44,8 +45,8 @@
     keysOne = new HashSet<String>();
     keysTwo = new HashSet<String>();
 
-    keysOne.add(GitRepositoryManager.REF_CONFIG);
-    keysTwo.add(GitRepositoryManager.REF_CONFIG);
+    keysOne.add(RefNames.REFS_CONFIG);
+    keysTwo.add(RefNames.REFS_CONFIG);
     keysTwo.add(Constants.HEAD);
   }
 
@@ -73,10 +74,10 @@
           try {
             RefUpdate update = git.updateRef(Constants.HEAD);
             update.disableRefLog();
-            update.link(GitRepositoryManager.REF_CONFIG);
+            update.link(RefNames.REFS_CONFIG);
           } catch (IOException err) {
             ui.message("warning: " + name.get() + ": Cannot update HEAD to "
-                + GitRepositoryManager.REF_CONFIG + ": " + err.getMessage());
+                + RefNames.REFS_CONFIG + ": " + err.getMessage());
           }
         }
       } finally {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java
index 624a51b..636e0c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_65.java
@@ -17,7 +17,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.primitives.Longs;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.ContributorAgreement;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -448,7 +447,7 @@
           @Override
           public int compare(
               AccountGroupAgreement a1, AccountGroupAgreement a2) {
-            return Longs.compare(a1.getTime(), a2.getTime());
+            return Long.compare(a1.getTime(), a2.getTime());
           }
         });
         return groupAgreements;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java
index fa56966..9b2bff4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_69.java
@@ -193,11 +193,8 @@
 
     for (AccountGroup.UUID uuid : resolveToUpdate) {
       AccountGroup group = resolveGroups.get(uuid);
-      group.setType(AccountGroup.Type.INTERNAL);
-      toUpdate.add(group);
-
       ui.message(String.format(
-          "*** Group has no DN and is inuse. Updated to be INTERNAL: %s",
+          "*** Group has no DN and is in use: %s",
           group.getName()));
     }
 
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
index 03b33a0..3868e66 100644
--- 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
@@ -14,48 +14,12 @@
 
 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_81.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_81.java
index bc3b390..1e3b2b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_81.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_81.java
@@ -80,7 +80,7 @@
         public boolean accept(File pathname) {
           String n = pathname.getName();
           return (n.endsWith(".jar") || n.endsWith(".jar.disabled"))
-              && pathname.isFile() && n.indexOf("replication") >= 0;
+              && pathname.isFile() && n.contains("replication");
         }
       });
     }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_85.java
similarity index 72%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_85.java
index 8e1b340..e24e67c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_85.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.change;
+package com.google.gerrit.server.schema;
 
-public class SubmitInput {
-  boolean wait_for_merge;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 
-  public static SubmitInput waitForMerge() {
-    SubmitInput in = new SubmitInput();
-    in.wait_for_merge = true;
-    return in;
+public class Schema_85 extends SchemaVersion {
+  @Inject
+  Schema_85(Provider<Schema_84> prior) {
+    super(prior);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_86.java
similarity index 72%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_86.java
index 8e1b340..d758189 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_86.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.change;
+package com.google.gerrit.server.schema;
 
-public class SubmitInput {
-  boolean wait_for_merge;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 
-  public static SubmitInput waitForMerge() {
-    SubmitInput in = new SubmitInput();
-    in.wait_for_merge = true;
-    return in;
+public class Schema_86 extends SchemaVersion {
+  @Inject
+  Schema_86(Provider<Schema_85> prior) {
+    super(prior);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java
new file mode 100644
index 0000000..451f5ed
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class Schema_87 extends SchemaVersion {
+  @Inject
+  Schema_87(Provider<Schema_86> prior) {
+    super(prior);
+  }
+
+  @Override
+  protected void migrateData(ReviewDb db, UpdateUI ui)
+      throws OrmException, SQLException {
+    for (AccountGroup.Id id : scanSystemGroups(db)) {
+      AccountGroup group = db.accountGroups().get(id);
+      if (group != null
+          && SystemGroupBackend.isSystemGroup(group.getGroupUUID())) {
+        db.accountGroups().delete(Collections.singleton(group));
+        db.accountGroupNames().deleteKeys(
+            Collections.singleton(group.getNameKey()));
+      }
+    }
+  }
+
+  private Set<AccountGroup.Id> scanSystemGroups(ReviewDb db)
+      throws SQLException {
+    JdbcSchema s = (JdbcSchema) db;
+    Statement stmt = s.getConnection().createStatement();
+    try {
+      ResultSet rs =
+          stmt.executeQuery("SELECT group_id FROM account_groups WHERE group_type = 'SYSTEM'");
+      Set<AccountGroup.Id> ids = new HashSet<>();
+      while (rs.next()) {
+        ids.add(new AccountGroup.Id(rs.getInt(1)));
+      }
+      return ids;
+    } finally {
+      stmt.close();
+    }
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_88.java
similarity index 72%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_88.java
index 8e1b340..0a7f14c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_88.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.change;
+package com.google.gerrit.server.schema;
 
-public class SubmitInput {
-  boolean wait_for_merge;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 
-  public static SubmitInput waitForMerge() {
-    SubmitInput in = new SubmitInput();
-    in.wait_for_merge = true;
-    return in;
+public class Schema_88 extends SchemaVersion {
+  @Inject
+  Schema_88(Provider<Schema_87> prior) {
+    super(prior);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_89.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_89.java
new file mode 100644
index 0000000..34f6b60
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_89.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.sql.DialectMySQL;
+import com.google.gwtorm.schema.sql.SqlDialect;
+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_89 extends SchemaVersion {
+  @Inject
+  Schema_89(Provider<Schema_88> prior) {
+    super(prior);
+  }
+
+  @Override
+  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException,
+      SQLException {
+    SqlDialect dialect = ((JdbcSchema) db).getDialect();
+    Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+    try {
+      for (String name : ImmutableList.of(
+          "patch_set_approvals_openByUser",
+          "patch_set_approvals_closedByU")) {
+        if (dialect instanceof DialectMySQL) {
+          stmt.executeUpdate("DROP INDEX " + name + " ON patch_set_approvals");
+        } else {
+          stmt.executeUpdate("DROP INDEX " + name);
+        }
+      }
+    } finally {
+      stmt.close();
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_90.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_90.java
new file mode 100644
index 0000000..c117509
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_90.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_90 extends SchemaVersion {
+  @Inject
+  Schema_90(Provider<Schema_89> prior) {
+    super(prior);
+  }
+
+  @Override
+  protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
+    Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+    try {
+      stmt.executeUpdate("UPDATE accounts set size_bar_in_change_table = 'Y'");
+    } finally {
+      stmt.close();
+    }
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_91.java
similarity index 72%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_91.java
index 8e1b340..173793e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_91.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.change;
+package com.google.gerrit.server.schema;
 
-public class SubmitInput {
-  boolean wait_for_merge;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 
-  public static SubmitInput waitForMerge() {
-    SubmitInput in = new SubmitInput();
-    in.wait_for_merge = true;
-    return in;
+public class Schema_91 extends SchemaVersion {
+  @Inject
+  Schema_91(Provider<Schema_90> prior) {
+    super(prior);
   }
 }
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/MyExtension.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_92.java
similarity index 66%
rename from gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/MyExtension.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_92.java
index ebdbb26..5f5c141 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/MyExtension.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_92.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Google
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package ${package};
+package com.google.gerrit.server.schema;
 
-import com.google.gerrit.extensions.webui.GwtPlugin;
-import com.google.gerrit.extensions.annotations.Listen;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 
-@Listen
-public class MyExtension extends GwtPlugin {
-  public MyExtension() {
-    super("hello_gwt_plugins");
+public class Schema_92 extends SchemaVersion {
+  @Inject
+  Schema_92(Provider<Schema_91> prior) {
+    super(prior);
   }
 }
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/MyExtension.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_93.java
similarity index 66%
copy from gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/MyExtension.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_93.java
index ebdbb26..3132aa4 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/src/main/java/MyExtension.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_93.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Google
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package ${package};
+package com.google.gerrit.server.schema;
 
-import com.google.gerrit.extensions.webui.GwtPlugin;
-import com.google.gerrit.extensions.annotations.Listen;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 
-@Listen
-public class MyExtension extends GwtPlugin {
-  public MyExtension() {
-    super("hello_gwt_plugins");
+public class Schema_93 extends SchemaVersion {
+  @Inject
+  Schema_93(Provider<Schema_92> prior) {
+    super(prior);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
index 254a780..12c80f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java
@@ -37,7 +37,7 @@
 
   static final ScriptRunner NOOP = new ScriptRunner(null, null) {
     void run(final ReviewDb db) {
-    };
+    }
   };
 
   ScriptRunner(final String scriptName, final InputStream script) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshAddressesModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshAddressesModule.java
index 4c1283a..36e7e8c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshAddressesModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshAddressesModule.java
@@ -44,7 +44,7 @@
   @Provides
   @Singleton
   @SshListenAddresses
-  List<SocketAddress> getListenAddresses(@GerritServerConfig Config cfg) {
+  public List<SocketAddress> getListenAddresses(@GerritServerConfig Config cfg) {
     List<SocketAddress> listen = Lists.newArrayListWithExpectedSize(2);
     String[] want = cfg.getStringList("sshd", null, "listenaddress");
     if (want == null || want.length == 0) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/tools/ToolsCatalog.java b/gerrit-server/src/main/java/com/google/gerrit/server/tools/ToolsCatalog.java
index dd874b2..a2b0ad1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/tools/ToolsCatalog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/tools/ToolsCatalog.java
@@ -140,7 +140,7 @@
   /** A file served out of the tools root directory. */
   public static class Entry {
     public static enum Type {
-      DIR, FILE;
+      DIR, FILE
     }
 
     private final Type type;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/HostPlatform.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/HostPlatform.java
index 09594e2..ea4b7f9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/HostPlatform.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/HostPlatform.java
@@ -33,7 +33,7 @@
           }
         });
     return osDotName != null
-        && osDotName.toLowerCase().indexOf("windows") != -1;
+        && osDotName.toLowerCase().contains("windows");
   }
 
   private HostPlatform() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
new file mode 100644
index 0000000..13bc766
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/LabelVote.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.util;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+
+/** A single vote on a label, consisting of a label name and a value. */
+public class LabelVote {
+  public static LabelVote parse(String text) {
+    checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
+    if (text.charAt(0) == '-') {
+      return new LabelVote(text.substring(1), (short) 0);
+    }
+    short sign = 0;
+    int i;
+    for (i = text.length() - 1; i >= 0; i--) {
+      int c = text.charAt(i);
+      if (c == '-') {
+        sign = (short) -1;
+        break;
+      } else if (c == '+') {
+        sign = (short) 1;
+        break;
+      } else if (!('0' <= c && c <= '9')) {
+        break;
+      }
+    }
+    if (sign == 0) {
+      return new LabelVote(text, (short) 1);
+    }
+    return new LabelVote(text.substring(0, i),
+        (short)(sign * Short.parseShort(text.substring(i + 1))));
+  }
+
+  public static LabelVote parseWithEquals(String text) {
+    checkArgument(!Strings.isNullOrEmpty(text), "Empty label vote");
+    int e = text.lastIndexOf('=');
+    checkArgument(e >= 0, "Label vote missing '=': %s", text);
+    return new LabelVote(text.substring(0, e),
+        Short.parseShort(text.substring(e + 1), text.length()));
+  }
+
+  private final String name;
+  private final short value;
+
+  public LabelVote(String name, short value) {
+    this.name = LabelType.checkNameInternal(name);
+    this.value = value;
+  }
+
+  public LabelVote(PatchSetApproval psa) {
+    this(psa.getLabel(), psa.getValue());
+  }
+
+  public String getLabel() {
+    return name;
+  }
+
+  public short getValue() {
+    return value;
+  }
+
+  public String format() {
+    if (value == (short) 0) {
+      return '-' + name;
+    } else if (value < 0) {
+      return name + value;
+    } else {
+      return name + '+' + value;
+    }
+  }
+
+  public String formatWithEquals() {
+    if (value <= (short) 0) {
+      return name + '=' + value;
+    } else {
+      return name + "=+" + value;
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o instanceof LabelVote) {
+      LabelVote l = (LabelVote) o;
+      return Objects.equal(name, l.name)
+          && value == l.value;
+    }
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    return format();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/LogUtil.java
similarity index 66%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/util/LogUtil.java
index 8e1b340..ba39a26 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/LogUtil.java
@@ -12,14 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.change;
+package com.google.gerrit.server.util;
 
-public class SubmitInput {
-  boolean wait_for_merge;
+import com.google.common.base.Strings;
 
-  public static SubmitInput waitForMerge() {
-    SubmitInput in = new SubmitInput();
-    in.wait_for_merge = true;
-    return in;
+public class LogUtil {
+
+  private static final String LOG4J_CONFIGURATION = "log4j.configuration";
+
+  public static boolean shouldConfigureLogSystem() {
+    return Strings.isNullOrEmpty(System.getProperty(LOG4J_CONFIGURATION));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/MagicBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/MagicBranch.java
index 60844ee..6e1952b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/MagicBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/MagicBranch.java
@@ -51,7 +51,7 @@
         || refName.startsWith(NEW_CHANGE);
   }
 
-  /** Returns the ref name prefix for a magic branch, <code>null</code> if the branch is not magic */
+  /** Returns the ref name prefix for a magic branch, {@code null} if the branch is not magic */
   public static String getMagicRefNamePrefix(String refName) {
     if (refName.startsWith(NEW_DRAFT_CHANGE)) {
       return NEW_DRAFT_CHANGE;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
index fbd8236..fbb3e93 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
@@ -97,7 +97,7 @@
           }
 
           final String urlExtractedPath = new URI(url).getPath();
-          String projectName = urlExtractedPath;
+          String projectName;
           int fromIndex = urlExtractedPath.length() - 1;
           while (fromIndex > 0) {
             fromIndex = urlExtractedPath.lastIndexOf('/', fromIndex - 1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/GroupCreationValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/GroupCreationValidationListener.java
new file mode 100644
index 0000000..11db3ee
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/GroupCreationValidationListener.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.validators;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.server.account.CreateGroupArgs;
+
+/**
+ * Listener to provide validation on group creation.
+ */
+@ExtensionPoint
+public interface GroupCreationValidationListener {
+  /**
+   * Group creation validation.
+   *
+   * Invoked by Gerrit just before a new group is going to be created.
+   *
+   * @param args arguments for the group creation
+   * @throws ValidationException if validation fails
+   */
+  public void validateNewGroup(CreateGroupArgs args)
+      throws ValidationException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/ProjectCreationValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/ProjectCreationValidationListener.java
new file mode 100644
index 0000000..d3c69c1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/ProjectCreationValidationListener.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.validators;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.server.project.CreateProjectArgs;
+
+/**
+ * Listener to provide validation on project creation.
+ */
+@ExtensionPoint
+public interface ProjectCreationValidationListener {
+  /**
+   * Project creation validation.
+   *
+   * Invoked by Gerrit just before a new project is going to be created.
+   *
+   * @param args arguments for the project creation
+   * @throws ValidationException if validation fails
+   */
+  public void validateNewProject(CreateProjectArgs args)
+      throws ValidationException;
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/ValidationException.java
similarity index 60%
copy from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/validators/ValidationException.java
index c48f968..53ded1f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/group/GroupInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/ValidationException.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 The Android Open Source Project
+// Copyright (C) 2014 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,14 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.group;
+package com.google.gerrit.server.validators;
 
-public class GroupInfo {
-  public String id;
-  public String name;
-  public String url;
-  public GroupOptionsInfo options;
-  public String description;
-  public Integer group_id;
-  public String owner_id;
+public class ValidationException extends Exception {
+  private static final long serialVersionUID = 1L;
+
+  public ValidationException(String reason) {
+    super(reason);
+  }
+
+  public ValidationException(String reason, Throwable why) {
+    super(reason, why);
+  }
 }
diff --git a/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java b/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
index bee046c..8e91262 100644
--- a/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
@@ -4,13 +4,10 @@
 
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
-import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.rules.StoredValues;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.util.Providers;
 
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
 import com.googlecode.prolog_cafe.lang.JavaException;
@@ -41,20 +38,11 @@
 
     Term listHead = Prolog.Nil;
     try {
-      ReviewDb db = StoredValues.REVIEW_DB.get(engine);
-      PatchSet patchSet = StoredValues.PATCH_SET.get(engine);
-      ChangeData cd = StoredValues.CHANGE_DATA.getOrNull(engine);
+      ChangeData cd = StoredValues.CHANGE_DATA.get(engine);
       LabelTypes types =
           StoredValues.CHANGE_CONTROL.get(engine).getLabelTypes();
 
-      Iterable<PatchSetApproval> approvals;
-      if (cd != null) {
-        approvals = cd.currentApprovals(Providers.of(db));
-      } else {
-        approvals = db.patchSetApprovals().byPatchSet(patchSet.getId());
-      }
-
-      for (PatchSetApproval a : approvals) {
+      for (PatchSetApproval a : cd.currentApprovals()) {
         if (a.getValue() == 0) {
           continue;
         }
diff --git a/gerrit-server/src/main/java/gerrit/PRED_change_branch_1.java b/gerrit-server/src/main/java/gerrit/PRED_change_branch_1.java
index 51396ed..b835b34 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_change_branch_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_change_branch_1.java
@@ -15,7 +15,6 @@
 package gerrit;
 
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.rules.StoredValues;
 
 import com.googlecode.prolog_cafe.lang.Operation;
@@ -36,12 +35,11 @@
     engine.setB0();
     Term a1 = arg1.dereference();
 
-    Change change = StoredValues.CHANGE.get(engine);
-    Branch.NameKey name = change.getDest();
+    Branch.NameKey name = StoredValues.getChange(engine).getDest();
 
     if (!a1.unify(SymbolTerm.create(name.get()), engine.trail)) {
       return engine.fail();
     }
     return cont;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_change_owner_1.java b/gerrit-server/src/main/java/gerrit/PRED_change_owner_1.java
index b127fff..51502f8 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_change_owner_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_change_owner_1.java
@@ -15,7 +15,6 @@
 package gerrit;
 
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.rules.StoredValues;
 
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
@@ -40,12 +39,11 @@
     engine.setB0();
     Term a1 = arg1.dereference();
 
-    Change change = StoredValues.CHANGE.get(engine);
-    Account.Id ownerId = change.getOwner();
+    Account.Id ownerId = StoredValues.getChange(engine).getOwner();
 
     if (!a1.unify(new StructureTerm(user, new IntegerTerm(ownerId.get())), engine.trail)) {
       return engine.fail();
     }
     return cont;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_change_project_1.java b/gerrit-server/src/main/java/gerrit/PRED_change_project_1.java
index fb9f865..29c6704 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_change_project_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_change_project_1.java
@@ -14,7 +14,6 @@
 
 package gerrit;
 
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.rules.StoredValues;
 
@@ -36,12 +35,11 @@
     engine.setB0();
     Term a1 = arg1.dereference();
 
-    Change change = StoredValues.CHANGE.get(engine);
-    Project.NameKey name = change.getProject();
+    Project.NameKey name = StoredValues.getChange(engine).getProject();
 
     if (!a1.unify(SymbolTerm.create(name.get()), engine.trail)) {
       return engine.fail();
     }
     return cont;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_change_topic_1.java b/gerrit-server/src/main/java/gerrit/PRED_change_topic_1.java
index 34885f9..7ba648f 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_change_topic_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_change_topic_1.java
@@ -36,7 +36,7 @@
     Term a1 = arg1.dereference();
 
     Term topicTerm = Prolog.Nil;
-    Change change = StoredValues.CHANGE.get(engine);
+    Change change = StoredValues.getChange(engine);
     String topic = change.getTopic();
     if (topic != null) {
       topicTerm = SymbolTerm.create(topic);
@@ -47,4 +47,4 @@
     }
     return cont;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
index a1b966f7..8d1e983 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
@@ -11,6 +11,7 @@
 runAs = Run As
 runGC = Run Garbage Collection
 streamEvents = Stream Events
+viewAllAccounts = View All Accounts
 viewCaches = View Caches
 viewConnections = View Connections
 viewQueue = View Queue
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommitMessageEdited.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommitMessageEdited.vm
deleted file mode 100644
index f583101..0000000
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommitMessageEdited.vm
+++ /dev/null
@@ -1,54 +0,0 @@
-## Copyright (C) 2012 The Android Open Source Project
-##
-## Licensed under the Apache License, Version 2.0 (the "License");
-## you may not use this file except in compliance with the License.
-## You may obtain a copy of the License at
-##
-## http://www.apache.org/licenses/LICENSE-2.0
-##
-## Unless required by applicable law or agreed to in writing, software
-## distributed under the License is distributed on an "AS IS" BASIS,
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-## See the License for the specific language governing permissions and
-## limitations under the License.
-##
-##
-## Template Type:
-## -------------
-## This is a velocity mail template, see: http://velocity.apache.org and the
-## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
-##
-## Template File Names and extensions:
-## ----------------------------------
-## Gerrit will use templates ending in ".vm" but will ignore templates ending
-## in ".vm.example".  If a .vm template does not exist, the default internal
-## gerrit template which is the same as the .vm.example will be used.  If you
-## want to override the default template, copy the .vm.example file to a .vm
-## file and edit it appropriately.
-##
-## This Template:
-## --------------
-## The CommitMessageUpdated.vm template will determine the contents of the email
-## related to a user editing the commit message for a change through the Gerrit UI.
-## It is a ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm.
-##
-#if($email.reviewerNames)
-Hello $email.joinStrings($email.reviewerNames, ', '),
-
-I'd like you to reexamine a change.#if($email.changeUrl)  Please visit
-
-    $email.changeUrl
-
-to look at the new patch set with edited commit message (#$patchSet.patchSetId).
-#end
-#else
-$fromName has created a new patch set by editing the commit message in Gerrit (#$patchSet.patchSetId).
-#end
-
-Change subject: $change.subject
-......................................................................
-
-$email.changeDetail
-#if($email.sshHost)
-  git pull ssh://$email.sshHost/$projectName $patchSet.refName
-#end
diff --git a/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
index 0d0ab1a..d5db854 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/rules/GerritCommonTest.java
@@ -15,21 +15,25 @@
 package com.google.gerrit.rules;
 
 import static com.google.gerrit.common.data.Permission.LABEL;
-import static com.google.gerrit.server.project.Util.value;
 import static com.google.gerrit.server.project.Util.category;
-import static com.google.gerrit.server.project.Util.REGISTERED;
 import static com.google.gerrit.server.project.Util.grant;
+import static com.google.gerrit.server.project.Util.value;
 
-import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.Util;
-import com.google.gerrit.server.util.TimeUtil;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.Util;
+import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.testutil.InMemoryRepositoryManager;
 import com.google.inject.AbstractModule;
 
+import org.junit.Before;
+import org.junit.Test;
+
 import java.util.Arrays;
 
 public class GerritCommonTest extends PrologTestCase {
@@ -46,9 +50,8 @@
   private ProjectConfig local;
   private Util util;
 
-  @Override
+  @Before
   public void setUp() throws Exception {
-    super.setUp();
     util = new Util();
     load("gerrit", "gerrit_common_test.pl", new AbstractModule() {
       @Override
@@ -65,14 +68,14 @@
     });
 
     local = new ProjectConfig(localKey);
-    local.createInMemory();
+    local.load(InMemoryRepositoryManager.newRepository(localKey));
     Q.setRefPatterns(Arrays.asList("refs/heads/develop"));
 
     local.getLabelSections().put(V.getName(), V);
     local.getLabelSections().put(Q.getName(), Q);
     util.add(local);
-    grant(local, LABEL + V.getName(), -1, +1, REGISTERED, "refs/heads/*");
-    grant(local, LABEL + Q.getName(), -1, +1, REGISTERED, "refs/heads/master");
+    grant(local, LABEL + V.getName(), -1, +1, SystemGroupBackend.REGISTERED_USERS, "refs/heads/*");
+    grant(local, LABEL + Q.getName(), -1, +1, SystemGroupBackend.REGISTERED_USERS, "refs/heads/master");
   }
 
   @Override
@@ -82,10 +85,10 @@
             new Account.Id(2),
             new Branch.NameKey(localKey, "refs/heads/master"),
             TimeUtil.nowTs());
-    env.set(StoredValues.CHANGE, change);
     env.set(StoredValues.CHANGE_CONTROL, util.user(local).controlFor(change));
   }
 
+  @Test
   public void testGerritCommon() {
     runPrologBasedTests();
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
index df39003..19edaf4 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/rules/PrologTestCase.java
@@ -29,8 +29,6 @@
 import com.googlecode.prolog_cafe.lang.Term;
 import com.googlecode.prolog_cafe.lang.VariableTerm;
 
-import junit.framework.TestCase;
-
 import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -41,9 +39,13 @@
 import java.util.Arrays;
 import java.util.List;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 
 /** Base class for any tests written in Prolog. */
-public abstract class PrologTestCase extends TestCase {
+public abstract class PrologTestCase {
   private static final SymbolTerm test_1 = SymbolTerm.intern("test", 1);
 
   private String pkg;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
index 24f3386..0bbec8a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/StringUtilTest.java
@@ -14,13 +14,16 @@
 
 package com.google.gerrit.server;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
-public class StringUtilTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+
+public class StringUtilTest {
   /**
    * Test the boundary condition that the first character of a string
    * should be escaped.
    */
+  @Test
   public void testEscapeFirstChar() {
     assertEquals(StringUtil.escapeString("\tLeading tab"), "\\tLeading tab");
   }
@@ -29,6 +32,7 @@
    * Test the boundary condition that the last character of a string
    * should be escaped.
    */
+  @Test
   public void testEscapeLastChar() {
     assertEquals(StringUtil.escapeString("Trailing tab\t"), "Trailing tab\\t");
   }
@@ -37,6 +41,7 @@
    * Test that various forms of input strings are escaped (or left as-is)
    * in the expected way.
    */
+  @Test
   public void testEscapeString() {
     final String[] testPairs =
       { "", "",
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
index 18b3c98..6c24f00 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/change/CommentsTest.java
@@ -18,6 +18,9 @@
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableMap;
@@ -46,9 +49,9 @@
 import com.google.inject.Injector;
 import com.google.inject.TypeLiteral;
 
-import junit.framework.TestCase;
-
 import org.easymock.IAnswer;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.sql.Timestamp;
 import java.util.ArrayList;
@@ -56,7 +59,7 @@
 import java.util.List;
 import java.util.Map;
 
-public class CommentsTest extends TestCase {
+public class CommentsTest {
 
   private Injector injector;
   private RevisionResource revRes1;
@@ -65,8 +68,8 @@
   private PatchLineComment plc2;
   private PatchLineComment plc3;
 
-  @Override
-  protected void setUp() throws Exception {
+  @Before
+  public void setUp() throws Exception {
     @SuppressWarnings("unchecked")
     final DynamicMap<RestView<CommentResource>> views =
         createMock(DynamicMap.class);
@@ -130,6 +133,7 @@
     injector = Guice.createInjector(mod);
   }
 
+  @Test
   public void testListComments() throws Exception {
     // test ListComments for patch set 1
     assertListComments(injector, revRes1, ImmutableMap.of(
@@ -140,6 +144,7 @@
         Collections.<String, ArrayList<PatchLineComment>>emptyMap());
   }
 
+  @Test
   public void testGetComment() throws Exception {
     // test GetComment for existing comment
     assertGetComment(injector, revRes1, plc1, plc1.getKey().get());
@@ -166,7 +171,7 @@
       if (expected == null) {
         fail("Expected no comment");
       }
-      CommentInfo actual = (CommentInfo) getComment.apply(commentRes);
+      CommentInfo actual = getComment.apply(commentRes);
       assertComment(expected, actual);
     } catch (ResourceNotFoundException e) {
       if (expected != null) {
@@ -186,9 +191,9 @@
     assertNotNull(actual);
     assertEquals(expected.size(), actual.size());
     assertEquals(expected.keySet(), actual.keySet());
-    for (String filename : expected.keySet()) {
-      List<PatchLineComment> expectedComments = expected.get(filename);
-      List<CommentInfo> actualComments = actual.get(filename);
+    for (Map.Entry<String, ArrayList<PatchLineComment>> entry : expected.entrySet()) {
+      List<PatchLineComment> expectedComments = entry.getValue();
+      List<CommentInfo> actualComments = actual.get(entry.getKey());
       assertNotNull(actualComments);
       assertEquals(expectedComments.size(), actualComments.size());
       for (int i = 0; i < expectedComments.size(); i++) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
index 5d72916..cc19811 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
@@ -14,17 +14,19 @@
 
 package com.google.gerrit.server.config;
 
+import org.junit.Test;
+
 import static java.util.concurrent.TimeUnit.DAYS;
 import static java.util.concurrent.TimeUnit.HOURS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 import static java.util.concurrent.TimeUnit.SECONDS;
-
-import junit.framework.TestCase;
+import static org.junit.Assert.assertEquals;
 
 import java.util.concurrent.TimeUnit;
 
-public class ConfigUtilTest extends TestCase {
+public class ConfigUtilTest {
+  @Test
   public void testTimeUnit() {
     assertEquals(ms(0, MILLISECONDS), parse("0"));
     assertEquals(ms(2, MILLISECONDS), parse("2ms"));
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
index 0087df6..5fdecf0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/SitePathsTest.java
@@ -14,15 +14,23 @@
 
 package com.google.gerrit.server.config;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import com.google.gerrit.server.util.HostPlatform;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 
-public class SitePathsTest extends TestCase {
+public class SitePathsTest {
+  @Test
   public void testCreate_NotExisting() throws IOException {
     final File root = random();
     final SitePaths site = new SitePaths(root);
@@ -31,6 +39,7 @@
     assertEquals(new File(root, "etc"), site.etc_dir);
   }
 
+  @Test
   public void testCreate_Empty() throws IOException {
     final File root = random();
     try {
@@ -44,6 +53,7 @@
     }
   }
 
+  @Test
   public void testCreate_NonEmpty() throws IOException {
     final File root = random();
     final File txt = new File(root, "test.txt");
@@ -60,6 +70,7 @@
     }
   }
 
+  @Test
   public void testCreate_NotDirectory() throws IOException {
     final File root = random();
     try {
@@ -75,6 +86,7 @@
     }
   }
 
+  @Test
   public void testResolve() throws IOException {
     final File root = random();
     final SitePaths site = new SitePaths(root);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
new file mode 100644
index 0000000..5412c67
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
@@ -0,0 +1,226 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import static com.google.gerrit.common.data.Permission.forLabel;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.project.Util.category;
+import static com.google.gerrit.server.project.Util.grant;
+import static com.google.gerrit.server.project.Util.value;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.LabelNormalizer.Result;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.schema.SchemaCreator;
+import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.testutil.InMemoryDatabase;
+import com.google.gerrit.testutil.InMemoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+import org.eclipse.jgit.lib.Repository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+/** Unit tests for {@link LabelNormalizer}. */
+public class LabelNormalizerTest {
+  @Inject private AccountManager accountManager;
+  @Inject private AllProjectsName allProjects;
+  @Inject private GitRepositoryManager repoManager;
+  @Inject private IdentifiedUser.RequestFactory userFactory;
+  @Inject private InMemoryDatabase schemaFactory;
+  @Inject private LabelNormalizer norm;
+  @Inject private MetaDataUpdate.User metaDataUpdateFactory;
+  @Inject private ProjectCache projectCache;
+  @Inject private SchemaCreator schemaCreator;
+
+  private LifecycleManager lifecycle;
+  private ReviewDb db;
+  private Account.Id userId;
+  private IdentifiedUser user;
+  private Change change;
+
+  @Before
+  public void setUpInjector() throws Exception {
+    Injector injector = Guice.createInjector(new InMemoryModule());
+    injector.injectMembers(this);
+    lifecycle = new LifecycleManager();
+    lifecycle.add(injector);
+    lifecycle.start();
+
+    db = schemaFactory.open();
+    schemaCreator.create(db);
+    userId = accountManager.authenticate(AuthRequest.forUser("user"))
+        .getAccountId();
+    user = userFactory.create(userId);
+
+    configureProject();
+    setUpChange();
+  }
+
+  private void configureProject() throws Exception {
+    ProjectConfig pc = loadAllProjects();
+    for (AccessSection sec : pc.getAccessSections()) {
+      for (String label : pc.getLabelSections().keySet()) {
+        sec.removePermission(forLabel(label));
+      }
+    }
+    LabelType lt = category("Verified",
+        value(1, "Verified"),
+        value(0, "No score"),
+        value(-1, "Fails"));
+    pc.getLabelSections().put(lt.getName(), lt);
+    save(pc);
+  }
+
+  private void setUpChange() throws Exception {
+    change = new Change(
+        new Change.Key("Iabcd1234abcd1234abcd1234abcd1234abcd1234"),
+        new Change.Id(1), userId,
+        new Branch.NameKey(allProjects, "refs/heads/master"),
+        TimeUtil.nowTs());
+    ChangeUtil.computeSortKey(change);
+    PatchSetInfo ps = new PatchSetInfo(new PatchSet.Id(change.getId(), 1));
+    ps.setSubject("Test change");
+    change.setCurrentPatchSet(ps);
+    db.changes().insert(ImmutableList.of(change));
+  }
+
+  @After
+  public void tearDown() {
+    if (lifecycle != null) {
+      lifecycle.stop();
+    }
+    if (db != null) {
+      db.close();
+    }
+    InMemoryDatabase.drop(schemaFactory);
+  }
+
+  @Test
+  public void normalizeByPermission() throws Exception {
+    ProjectConfig pc = loadAllProjects();
+    grant(pc, forLabel("Code-Review"), -1, 1, REGISTERED_USERS, "refs/heads/*");
+    grant(pc, forLabel("Verified"), -1, 1, REGISTERED_USERS, "refs/heads/*");
+    save(pc);
+
+    PatchSetApproval cr = psa(userId, "Code-Review", 2);
+    PatchSetApproval v = psa(userId, "Verified", 1);
+    assertEquals(new Result(
+          list(v),
+          list(copy(cr, 1)),
+          list()),
+        norm.normalize(change, list(cr, v)));
+  }
+
+  @Test
+  public void normalizeByType() throws Exception {
+    ProjectConfig pc = loadAllProjects();
+    grant(pc, forLabel("Code-Review"), -5, 5, REGISTERED_USERS, "refs/heads/*");
+    grant(pc, forLabel("Verified"), -5, 5, REGISTERED_USERS, "refs/heads/*");
+    save(pc);
+
+    PatchSetApproval cr = psa(userId, "Code-Review", 5);
+    PatchSetApproval v = psa(userId, "Verified", 5);
+    assertEquals(new Result(
+          list(),
+          list(copy(cr, 2), copy(v, 1)),
+          list()),
+        norm.normalize(change, list(cr, v)));
+  }
+
+  @Test
+  public void emptyPermissionRangeOmitsResult() throws Exception {
+    PatchSetApproval cr = psa(userId, "Code-Review", 1);
+    PatchSetApproval v = psa(userId, "Verified", 1);
+    assertEquals(new Result(
+          list(),
+          list(),
+          list(cr, v)),
+        norm.normalize(change, list(cr, v)));
+  }
+
+  @Test
+  public void explicitZeroVoteOnNonEmptyRangeIsPresent() throws Exception {
+    ProjectConfig pc = loadAllProjects();
+    grant(pc, forLabel("Code-Review"), -1, 1, REGISTERED_USERS, "refs/heads/*");
+    save(pc);
+
+    PatchSetApproval cr = psa(userId, "Code-Review", 0);
+    PatchSetApproval v = psa(userId, "Verified", 0);
+    assertEquals(new Result(
+          list(cr),
+          list(),
+          list(v)),
+        norm.normalize(change, list(cr, v)));
+  }
+
+  private ProjectConfig loadAllProjects() throws Exception {
+    Repository repo = repoManager.openRepository(allProjects);
+    try {
+      ProjectConfig pc = new ProjectConfig(allProjects);
+      pc.load(repo);
+      return pc;
+    } finally {
+      repo.close();
+    }
+  }
+
+  private void save(ProjectConfig pc) throws Exception {
+    MetaDataUpdate md =
+        metaDataUpdateFactory.create(pc.getProject().getNameKey(), user);
+    pc.commit(md);
+    projectCache.evict(pc.getProject().getNameKey());
+  }
+
+  private PatchSetApproval psa(Account.Id accountId, String label, int value) {
+    return new PatchSetApproval(
+        new PatchSetApproval.Key(
+          change.currentPatchSetId(), accountId, new LabelId(label)),
+        (short) value, TimeUtil.nowTs());
+  }
+
+  private PatchSetApproval copy(PatchSetApproval src, int newValue) {
+    PatchSetApproval result =
+        new PatchSetApproval(src.getKey().getParentKey(), src);
+    result.setValue((short) newValue);
+    return result;
+  }
+
+  private static List<PatchSetApproval> list(PatchSetApproval... psas) {
+    return ImmutableList.<PatchSetApproval> copyOf(psas);
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
index 0d5207a..1a9f74b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -208,12 +209,12 @@
     md.setMessage("Edit\n");
     cfg.commit(md);
 
-    Ref ref = db.getRef(GitRepositoryManager.REF_CONFIG);
+    Ref ref = db.getRef(RefNames.REFS_CONFIG);
     return util.getRevWalk().parseCommit(ref.getObjectId());
   }
 
   private void update(RevCommit rev) throws Exception {
-    RefUpdate u = db.updateRef(GitRepositoryManager.REF_CONFIG);
+    RefUpdate u = db.updateRef(RefNames.REFS_CONFIG);
     u.disableRefLog();
     u.setNewObjectId(rev);
     switch (u.forceUpdate()) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
index e9a4cf3..33ba36e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
@@ -612,11 +612,10 @@
     final Change submittedChange = new Change(
         new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1),
         new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs());
-    codeReviewCommit.change = submittedChange;
 
     final Map<Change.Id, CodeReviewCommit> mergedCommits =
         new HashMap<Change.Id, CodeReviewCommit>();
-    mergedCommits.put(codeReviewCommit.change.getId(), codeReviewCommit);
+    mergedCommits.put(submittedChange.getId(), codeReviewCommit);
 
     final List<Change> submitted = new ArrayList<Change>();
     submitted.add(submittedChange);
@@ -643,7 +642,7 @@
         subscribers);
 
     expect(repoManager.openRepository(targetBranchNameKey.getParentKey()))
-        .andReturn(targetRepository);
+        .andReturn(targetRepository).anyTimes();
 
     Capture<RefUpdate> ruCapture = new Capture<RefUpdate>();
     gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()),
@@ -716,11 +715,10 @@
     final Change submittedChange = new Change(
         new Change.Key(sourceMergeTip.toObjectId().getName()), new Change.Id(1),
         new Account.Id(1), sourceBranchNameKey, TimeUtil.nowTs());
-    codeReviewCommit.change = submittedChange;
 
     final Map<Change.Id, CodeReviewCommit> mergedCommits =
         new HashMap<Change.Id, CodeReviewCommit>();
-    mergedCommits.put(codeReviewCommit.change.getId(), codeReviewCommit);
+    mergedCommits.put(submittedChange.getId(), codeReviewCommit);
 
     final List<Change> submitted = new ArrayList<Change>();
     submitted.add(submittedChange);
@@ -747,7 +745,7 @@
         subscribers);
 
     expect(repoManager.openRepository(targetBranchNameKey.getParentKey()))
-        .andReturn(targetRepository);
+        .andReturn(targetRepository).anyTimes();
 
     Capture<RefUpdate> ruCapture = new Capture<RefUpdate>();
     gitRefUpdated.fire(eq(targetBranchNameKey.getParentKey()),
@@ -820,7 +818,7 @@
    * subscriptions.
    * <p>
    * In this test a commit is created and considered merged to
-   * <code>mergedBranch</code> branch.
+   * {@code mergedBranch} branch.
    * </p>
    * <p>
    * The destination project the commit was merged is not considered to be a
@@ -828,7 +826,7 @@
    * </p>
    *
    * @param gitModulesFileContent The .gitmodules file content.
-   * @param mergedBranch The {@link Branch.NameKey} instance representing the
+   * @param mergedBranch The {@code Branch.NameKey} instance representing the
    *        project/branch the commit was merged.
    * @param extractedSubscriptions The subscription rows extracted from
    *        gitmodules file.
@@ -846,7 +844,7 @@
    * Subscriptions table.
    * <p>
    * In this test a commit is created and considered merged to
-   * <code>mergedBranch</code> branch.
+   * {@code mergedBranch} branch.
    * </p>
    * <p>
    * The destination project the commit was merged is not considered to be a
@@ -854,12 +852,12 @@
    * </p>
    *
    * @param gitModulesFileContent The .gitmodules file content.
-   * @param mergedBranch The {@link Branch.NameKey} instance representing the
+   * @param mergedBranch The {@code Branch.NameKey} instance representing the
    *        project/branch the commit was merged.
    * @param extractedSubscriptions The subscription rows extracted from
    *        gitmodules file.
    * @param previousSubscriptions The subscription rows to be considering as
-   *        existing and pointing as target to the <code>mergedBranch</code>
+   *        existing and pointing as target to the {@code mergedBranch}
    *        before updating the table.
    * @throws Exception If an exception occurs.
    */
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java
index d8a0f65..4db3b27 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeIndex.java
@@ -30,8 +30,8 @@
   static Schema<ChangeData> V2 = new Schema<ChangeData>(2, false,
     ImmutableList.of(
       ChangeField.STATUS,
-      ChangeField.FILE,
-      ChangeField.SORTKEY));
+      ChangeField.PATH,
+      ChangeField.UPDATED));
 
   private static class Source implements ChangeDataSource {
     private final Predicate<ChangeData> p;
@@ -88,8 +88,8 @@
   }
 
   @Override
-  public ChangeDataSource getSource(Predicate<ChangeData> p, int limit)
-      throws QueryParseException {
+  public ChangeDataSource getSource(Predicate<ChangeData> p, int start,
+      int limit) throws QueryParseException {
     return new FakeIndex.Source(p);
   }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
index 63e62a0..2079d3d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
@@ -26,7 +26,8 @@
         new FakeQueryBuilder.Definition<ChangeData, FakeQueryBuilder>(
           FakeQueryBuilder.class),
         new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
-          null, null, null, null, null, indexes),
+          null, null, null, null, null, null, null, null, indexes, null, null,
+          null, null),
         null);
   }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
index d9016e9..c8275e8 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexRewriteTest.java
@@ -19,65 +19,70 @@
 import static com.google.gerrit.reviewdb.client.Change.Status.MERGED;
 import static com.google.gerrit.reviewdb.client.Change.Status.NEW;
 import static com.google.gerrit.reviewdb.client.Change.Status.SUBMITTED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.query.AndPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.RewritePredicate;
 import com.google.gerrit.server.query.change.AndSource;
+import com.google.gerrit.server.query.change.BasicChangeRewrites;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.OrSource;
-import com.google.gerrit.server.query.change.SqlRewriterImpl;
 
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
 
+import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.Set;
 
-@SuppressWarnings("unchecked")
-public class IndexRewriteTest extends TestCase {
+public class IndexRewriteTest {
   private FakeIndex index;
   private IndexCollection indexes;
   private ChangeQueryBuilder queryBuilder;
   private IndexRewriteImpl rewrite;
 
-  @Override
+  @Before
   public void setUp() throws Exception {
-    super.setUp();
     index = new FakeIndex(FakeIndex.V2);
     indexes = new IndexCollection();
     indexes.setSearchIndex(index);
     queryBuilder = new FakeQueryBuilder(indexes);
     rewrite = new IndexRewriteImpl(
         indexes,
-        null,
-        new IndexRewriteImpl.BasicRewritesImpl(null, indexes),
-        new SqlRewriterImpl(null));
+        new BasicChangeRewrites(null));
   }
 
+  @Test
   public void testIndexPredicate() throws Exception {
     Predicate<ChangeData> in = parse("file:a");
     assertEquals(query(in), rewrite(in));
   }
 
+  @Test
   public void testNonIndexPredicate() throws Exception {
     Predicate<ChangeData> in = parse("foo:a");
     assertSame(in, rewrite(in));
   }
 
+  @Test
   public void testIndexPredicates() throws Exception {
     Predicate<ChangeData> in = parse("file:a file:b");
     assertEquals(query(in), rewrite(in));
   }
 
+  @Test
   public void testNonIndexPredicates() throws Exception {
     Predicate<ChangeData> in = parse("foo:a OR foo:b");
     assertEquals(in, rewrite(in));
   }
 
+  @Test
   public void testOneIndexPredicate() throws Exception {
     Predicate<ChangeData> in = parse("foo:a file:b");
     Predicate<ChangeData> out = rewrite(in);
@@ -87,14 +92,16 @@
         out.getChildren());
   }
 
+  @Test
   public void testThreeLevelTreeWithAllIndexPredicates() throws Exception {
     Predicate<ChangeData> in =
         parse("-status:abandoned (status:open OR status:merged)");
     assertEquals(
         query(parse("status:new OR status:submitted OR status:draft OR status:merged")),
-        rewrite.rewrite(in));
+        rewrite.rewrite(in, 0));
   }
 
+  @Test
   public void testThreeLevelTreeWithSomeIndexPredicates() throws Exception {
     Predicate<ChangeData> in = parse("-foo:a (file:b OR file:c)");
     Predicate<ChangeData> out = rewrite(in);
@@ -104,6 +111,7 @@
         out.getChildren());
   }
 
+  @Test
   public void testMultipleIndexPredicates() throws Exception {
     Predicate<ChangeData> in =
         parse("file:a OR foo:b OR file:c OR foo:d");
@@ -115,6 +123,7 @@
         out.getChildren());
   }
 
+  @Test
   public void testIndexAndNonIndexPredicates() throws Exception {
     Predicate<ChangeData> in = parse("status:new bar:p file:a");
     Predicate<ChangeData> out = rewrite(in);
@@ -125,6 +134,7 @@
         out.getChildren());
   }
 
+  @Test
   public void testDuplicateCompoundNonIndexOnlyPredicates() throws Exception {
     Predicate<ChangeData> in =
         parse("(status:new OR status:draft) bar:p file:a");
@@ -136,6 +146,7 @@
         out.getChildren());
   }
 
+  @Test
   public void testDuplicateCompoundIndexOnlyPredicates() throws Exception {
     Predicate<ChangeData> in =
         parse("(status:new OR file:a) bar:p file:b");
@@ -147,6 +158,7 @@
         out.getChildren());
   }
 
+  @Test
   public void testLimit() throws Exception {
     Predicate<ChangeData> in = parse("file:a limit:3");
     Predicate<ChangeData> out = rewrite(in);
@@ -157,6 +169,24 @@
         out.getChildren());
   }
 
+  @Test
+  public void testStartIncreasesLimit() throws Exception {
+    Predicate<ChangeData> f = parse("file:a");
+    Predicate<ChangeData> l = parse("limit:3");
+    Predicate<ChangeData> in = and(f, l);
+    assertEquals(and(query(f, 3), l), rewrite.rewrite(in, 0));
+    assertEquals(and(query(f, 4), l), rewrite.rewrite(in, 1));
+    assertEquals(and(query(f, 5), l), rewrite.rewrite(in, 2));
+  }
+
+  @Test
+  public void testStartDoesNotExceedMaxLimit() throws Exception {
+    Predicate<ChangeData> in = parse("file:a");
+    assertEquals(query(in), rewrite.rewrite(in, 0));
+    assertEquals(query(in), rewrite.rewrite(in, 1));
+  }
+
+  @Test
   public void testGetPossibleStatus() throws Exception {
     assertEquals(EnumSet.allOf(Change.Status.class), status("file:a"));
     assertEquals(EnumSet.of(NEW), status("is:new"));
@@ -173,6 +203,7 @@
         status("(is:new is:draft) OR (is:merged OR is:submitted)"));
   }
 
+  @Test
   public void testUnsupportedIndexOperator() throws Exception {
     Predicate<ChangeData> in = parse("status:merged file:a");
     assertEquals(query(in), rewrite(in));
@@ -186,25 +217,18 @@
         out.getChildren());
   }
 
-  public void testNoChangeIndexUsesSqlRewrites() throws Exception {
-    Predicate<ChangeData> in = parse("status:open project:p ref:b");
-    Predicate<ChangeData> out;
-
-    out = rewrite(in);
-    assertTrue(out instanceof AndPredicate || out instanceof IndexedChangeQuery);
-
-    indexes.setSearchIndex(null);
-    out = rewrite(in);
-    assertTrue(out instanceof RewritePredicate);
-  }
-
   private Predicate<ChangeData> parse(String query) throws QueryParseException {
     return queryBuilder.parse(query);
   }
 
+  @SafeVarargs
+  private static AndSource and(Predicate<ChangeData>... preds) {
+    return new AndSource(Arrays.asList(preds));
+  }
+
   private Predicate<ChangeData> rewrite(Predicate<ChangeData> in)
       throws QueryParseException {
-    return rewrite.rewrite(in);
+    return rewrite.rewrite(in, 0);
   }
 
   private IndexedChangeQuery query(Predicate<ChangeData> p)
@@ -214,7 +238,7 @@
 
   private IndexedChangeQuery query(Predicate<ChangeData> p, int limit)
       throws QueryParseException {
-    return new IndexedChangeQuery(null, index, p, limit);
+    return new IndexedChangeQuery(index, p, limit);
   }
 
   private Set<Change.Status> status(String query) throws QueryParseException {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexedChangeQueryTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexedChangeQueryTest.java
deleted file mode 100644
index 81518f5..0000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/IndexedChangeQueryTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.index;
-
-import static com.google.gerrit.server.index.IndexedChangeQuery.replaceSortKeyPredicates;
-
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
-
-import junit.framework.TestCase;
-
-public class IndexedChangeQueryTest extends TestCase {
-  private FakeIndex index;
-  private ChangeQueryBuilder queryBuilder;
-
-  @Override
-  public void setUp() throws Exception {
-    super.setUp();
-    index = new FakeIndex(FakeIndex.V2);
-    IndexCollection indexes = new IndexCollection();
-    indexes.setSearchIndex(index);
-    queryBuilder = new FakeQueryBuilder(indexes);
-  }
-
-  public void testReplaceSortKeyPredicate_NoSortKey() throws Exception {
-    Predicate<ChangeData> p = parse("foo:a bar:b OR (foo:b bar:a)");
-    assertSame(p, replaceSortKeyPredicates(p, "1234"));
-  }
-
-  public void testReplaceSortKeyPredicate_TopLevelSortKey() throws Exception {
-    Predicate<ChangeData> p;
-    p = parse("foo:a bar:b sortkey_before:1234 OR (foo:b bar:a)");
-    assertEquals(parse("foo:a bar:b sortkey_before:5678 OR (foo:b bar:a)"),
-        replaceSortKeyPredicates(p, "5678"));
-    p = parse("foo:a bar:b sortkey_after:1234 OR (foo:b bar:a)");
-    assertEquals(parse("foo:a bar:b sortkey_after:5678 OR (foo:b bar:a)"),
-        replaceSortKeyPredicates(p, "5678"));
-  }
-
-  public void testReplaceSortKeyPredicate_NestedSortKey() throws Exception {
-    Predicate<ChangeData> p;
-    p = parse("foo:a bar:b OR (foo:b bar:a AND sortkey_before:1234)");
-    assertEquals(parse("foo:a bar:b OR (foo:b bar:a sortkey_before:5678)"),
-        replaceSortKeyPredicates(p, "5678"));
-    p = parse("foo:a bar:b OR (foo:b bar:a AND sortkey_after:1234)");
-    assertEquals(parse("foo:a bar:b OR (foo:b bar:a sortkey_after:5678)"),
-        replaceSortKeyPredicates(p, "5678"));
-  }
-
-  private Predicate<ChangeData> parse(String query) throws QueryParseException {
-    return queryBuilder.parse(query);
-  }
-}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java
index 9b6b0df..622b31e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/BasicSerializationTest.java
@@ -14,27 +14,31 @@
 
 package com.google.gerrit.server.ioutil;
 
+import org.junit.Test;
+
 import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt64;
 import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
 import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
 import static com.google.gerrit.server.ioutil.BasicSerialization.writeFixInt64;
 import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
 import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
-
-import junit.framework.TestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
-public class BasicSerializationTest extends TestCase {
+public class BasicSerializationTest {
+  @Test
   public void testReadVarInt32() throws IOException {
     assertEquals(0x00000000, readVarInt32(r(b(0))));
     assertEquals(0x00000003, readVarInt32(r(b(3))));
     assertEquals(0x000000ff, readVarInt32(r(b(0x80 | 0x7f, 0x01))));
   }
 
+  @Test
   public void testWriteVarInt32() throws IOException {
     ByteArrayOutputStream out;
 
@@ -51,6 +55,7 @@
     assertOutput(b(0x80 | 0x7f, 0x01), out);
   }
 
+  @Test
   public void testReadFixInt64() throws IOException {
     assertEquals(0L, readFixInt64(r(b(0, 0, 0, 0, 0, 0, 0, 0))));
     assertEquals(3L, readFixInt64(r(b(0, 0, 0, 0, 0, 0, 0, 3))));
@@ -71,6 +76,7 @@
         0xff, 0xff, 0xff, 0xff))));
   }
 
+  @Test
   public void testWriteFixInt64() throws IOException {
     ByteArrayOutputStream out;
 
@@ -99,6 +105,7 @@
     assertOutput(b(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff), out);
   }
 
+  @Test
   public void testReadString() throws IOException {
     assertNull(readString(r(b(0))));
     assertEquals("a", readString(r(b(1, 'a'))));
@@ -106,6 +113,7 @@
         readString(r(b(7, 'c', 'o', 'f', 'f', 'e', 'e', '4'))));
   }
 
+  @Test
   public void testWriteString() throws IOException {
     ByteArrayOutputStream out;
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
index 3b4005c..02d0582 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/ioutil/ColumnFormatterTest.java
@@ -14,12 +14,13 @@
 
 package com.google.gerrit.server.ioutil;
 
-import junit.framework.TestCase;
+import org.junit.Assert;
+import org.junit.Test;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
 
-public class ColumnFormatterTest extends TestCase {
+public class ColumnFormatterTest {
   /**
    * Holds an in-memory {@link java.io.PrintWriter} object and allows
    * comparisons of its contents to a supplied string via an assert statement.
@@ -35,7 +36,7 @@
 
     public void assertEquals(String str) {
       printWriter.flush();
-      TestCase.assertEquals(stringWriter.toString(), str);
+      Assert.assertEquals(stringWriter.toString(), str);
     }
 
     public PrintWriter getPrintWriter() {
@@ -46,6 +47,7 @@
   /**
    * Test that only lines with at least one column of text emit output.
    */
+  @Test
   public void testEmptyLine() {
     final PrintWriterComparator comparator = new PrintWriterComparator();
     final ColumnFormatter formatter =
@@ -64,6 +66,7 @@
   /**
    * Test that there is no output if no columns are ever added.
    */
+  @Test
   public void testEmptyOutput() {
     final PrintWriterComparator comparator = new PrintWriterComparator();
     final ColumnFormatter formatter =
@@ -78,6 +81,7 @@
    * Test that there is no output (nor any exceptions) if we finalize
    * the output immediately after the creation of the {@link ColumnFormatter}.
    */
+  @Test
   public void testNoNextLine() {
     final PrintWriterComparator comparator = new PrintWriterComparator();
     final ColumnFormatter formatter =
@@ -90,6 +94,7 @@
    * Test that the text in added columns is escaped while the column separator
    * (which of course shouldn't be escaped) is left alone.
    */
+  @Test
   public void testEscapingTakesPlace() {
     final PrintWriterComparator comparator = new PrintWriterComparator();
     final ColumnFormatter formatter =
@@ -106,6 +111,7 @@
    * Test that we get the correct output with multi-line input where the number
    * of columns in each line varies.
    */
+  @Test
   public void testMultiLineDifferentColumnCount() {
     final PrintWriterComparator comparator = new PrintWriterComparator();
     final ColumnFormatter formatter =
@@ -124,6 +130,7 @@
   /**
    * Test that we get the correct output with a single column of input.
    */
+  @Test
   public void testOneColumn() {
     final PrintWriterComparator comparator = new PrintWriterComparator();
     final ColumnFormatter formatter =
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
index fbbd72b..02ebf51 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
@@ -14,53 +14,65 @@
 
 package com.google.gerrit.server.mail;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
 import java.io.UnsupportedEncodingException;
 
-public class AddressTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class AddressTest {
+  @Test
   public void testParse_NameEmail1() {
     final Address a = Address.parse("A U Thor <author@example.com>");
     assertEquals("A U Thor", a.name);
     assertEquals("author@example.com", a.email);
   }
 
+  @Test
   public void testParse_NameEmail2() {
     final Address a = Address.parse("A <a@b>");
     assertEquals("A", a.name);
     assertEquals("a@b", a.email);
   }
 
+  @Test
   public void testParse_NameEmail3() {
     final Address a = Address.parse("<a@b>");
     assertNull(a.name);
     assertEquals("a@b", a.email);
   }
 
+  @Test
   public void testParse_NameEmail4() {
     final Address a = Address.parse("A U Thor<author@example.com>");
     assertEquals("A U Thor", a.name);
     assertEquals("author@example.com", a.email);
   }
 
+  @Test
   public void testParse_NameEmail5() {
     final Address a = Address.parse("A U Thor  <author@example.com>");
     assertEquals("A U Thor", a.name);
     assertEquals("author@example.com", a.email);
   }
 
+  @Test
   public void testParse_Email1() {
     final Address a = Address.parse("author@example.com");
     assertNull(a.name);
     assertEquals("author@example.com", a.email);
   }
 
+  @Test
   public void testParse_Email2() {
     final Address a = Address.parse("a@b");
     assertNull(a.name);
     assertEquals("a@b", a.email);
   }
 
+  @Test
   public void testParseInvalid() {
     assertInvalid("");
     assertInvalid("a");
@@ -88,34 +100,42 @@
     }
   }
 
+  @Test
   public void testToHeaderString_NameEmail1() {
     assertEquals("A <a@a>", format("A", "a@a"));
   }
 
+  @Test
   public void testToHeaderString_NameEmail2() {
     assertEquals("A B <a@a>", format("A B", "a@a"));
   }
 
+  @Test
   public void testToHeaderString_NameEmail3() {
     assertEquals("\"A B. C\" <a@a>", format("A B. C", "a@a"));
   }
 
+  @Test
   public void testToHeaderString_NameEmail4() {
     assertEquals("\"A B, C\" <a@a>", format("A B, C", "a@a"));
   }
 
+  @Test
   public void testToHeaderString_NameEmail5() {
     assertEquals("\"A \\\" C\" <a@a>", format("A \" C", "a@a"));
   }
 
+  @Test
   public void testToHeaderString_NameEmail6() {
     assertEquals("=?UTF-8?Q?A_=E2=82=AC_B?= <a@a>", format("A \u20ac B", "a@a"));
   }
 
+  @Test
   public void testToHeaderString_Email1() {
     assertEquals("a@a", format(null, "a@a"));
   }
 
+  @Test
   public void testToHeaderString_Email2() {
     assertEquals("<a,b@a>", format(null, "a,b@a"));
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
index 721059c..4f20b63 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
@@ -19,6 +19,9 @@
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
@@ -27,21 +30,20 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.util.TimeUtil;
 
-import junit.framework.TestCase;
-
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.util.Collections;
 
-public class FromAddressGeneratorProviderTest extends TestCase {
+public class FromAddressGeneratorProviderTest {
   private Config config;
   private PersonIdent ident;
   private AccountCache accountCache;
 
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
+  @Before
+  public void setUp() throws Exception {
     config = new Config();
     ident = new PersonIdent("NAME", "e@email", 0, 0);
     accountCache = createStrictMock(AccountCache.class);
@@ -56,10 +58,12 @@
     config.setString("sendemail", null, "from", newFrom);
   }
 
+  @Test
   public void testDefaultIsMIXED() {
     assertTrue(create() instanceof FromAddressGeneratorProvider.PatternGen);
   }
 
+  @Test
   public void testSelectUSER() {
     setFrom("USER");
     assertTrue(create() instanceof FromAddressGeneratorProvider.UserGen);
@@ -71,6 +75,7 @@
     assertTrue(create() instanceof FromAddressGeneratorProvider.UserGen);
   }
 
+  @Test
   public void testUSER_FullyConfiguredUser() {
     setFrom("USER");
 
@@ -86,6 +91,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testUSER_NoFullNameUser() {
     setFrom("USER");
 
@@ -100,6 +106,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testUSER_NoPreferredEmailUser() {
     setFrom("USER");
 
@@ -114,6 +121,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testUSER_NullUser() {
     setFrom("USER");
     replay(accountCache);
@@ -124,6 +132,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testSelectSERVER() {
     setFrom("SERVER");
     assertTrue(create() instanceof FromAddressGeneratorProvider.ServerGen);
@@ -135,6 +144,7 @@
     assertTrue(create() instanceof FromAddressGeneratorProvider.ServerGen);
   }
 
+  @Test
   public void testSERVER_FullyConfiguredUser() {
     setFrom("SERVER");
 
@@ -150,6 +160,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testSERVER_NullUser() {
     setFrom("SERVER");
     replay(accountCache);
@@ -160,6 +171,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testSelectMIXED() {
     setFrom("MIXED");
     assertTrue(create() instanceof FromAddressGeneratorProvider.PatternGen);
@@ -171,6 +183,7 @@
     assertTrue(create() instanceof FromAddressGeneratorProvider.PatternGen);
   }
 
+  @Test
   public void testMIXED_FullyConfiguredUser() {
     setFrom("MIXED");
 
@@ -186,6 +199,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testMIXED_NoFullNameUser() {
     setFrom("MIXED");
 
@@ -200,6 +214,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testMIXED_NoPreferredEmailUser() {
     setFrom("MIXED");
 
@@ -214,6 +229,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testMIXED_NullUser() {
     setFrom("MIXED");
     replay(accountCache);
@@ -224,6 +240,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testCUSTOM_FullyConfiguredUser() {
     setFrom("A ${user} B <my.server@email.address>");
 
@@ -239,6 +256,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testCUSTOM_NoFullNameUser() {
     setFrom("A ${user} B <my.server@email.address>");
 
@@ -253,6 +271,7 @@
     verify(accountCache);
   }
 
+  @Test
   public void testCUSTOM_NullUser() {
     setFrom("A ${user} B <my.server@email.address>");
 
@@ -280,9 +299,7 @@
     final Account account = new Account(userId, TimeUtil.nowTs());
     account.setFullName(name);
     account.setPreferredEmail(email);
-    final AccountState s =
-        new AccountState(account, Collections.<AccountGroup.UUID> emptySet(),
-            Collections.<AccountExternalId> emptySet());
-    return s;
+    return new AccountState(account, Collections.<AccountGroup.UUID> emptySet(),
+          Collections.<AccountExternalId> emptySet());
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
new file mode 100644
index 0000000..f2ad057
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -0,0 +1,683 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb;
+
+import static com.google.gerrit.server.notedb.ReviewerState.CC;
+import static com.google.gerrit.server.notedb.ReviewerState.REVIEWER;
+import static com.google.inject.Scopes.SINGLETON;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.config.AnonymousCowardNameProvider;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitModule;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.util.TimeUtil;
+import com.google.gerrit.testutil.FakeAccountCache;
+import com.google.gerrit.testutil.FakeRealm;
+import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.StandardKeyEncoder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.util.Providers;
+
+import org.easymock.EasyMock;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeUtils;
+import org.joda.time.DateTimeUtils.MillisProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class ChangeNotesTest {
+  private static final TimeZone TZ =
+      TimeZone.getTimeZone("America/Los_Angeles");
+
+  private PersonIdent serverIdent;
+  private Project.NameKey project;
+  private InMemoryRepositoryManager repoManager;
+  private InMemoryRepository repo;
+  private FakeAccountCache accountCache;
+  private IdentifiedUser changeOwner;
+  private IdentifiedUser otherUser;
+  private Injector injector;
+  private String systemTimeZone;
+  private volatile long clockStepMs;
+
+  @Before
+  public void setUp() throws Exception {
+    setTimeForTesting();
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+
+    serverIdent = new PersonIdent(
+        "Gerrit Server", "noreply@gerrit.com", TimeUtil.nowTs(), TZ);
+    project = new Project.NameKey("test-project");
+    repoManager = new InMemoryRepositoryManager();
+    repo = repoManager.createRepository(project);
+    accountCache = new FakeAccountCache();
+    Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
+    co.setFullName("Change Owner");
+    co.setPreferredEmail("change@owner.com");
+    accountCache.put(co);
+    Account ou = new Account(new Account.Id(2), TimeUtil.nowTs());
+    ou.setFullName("Other Account");
+    ou.setPreferredEmail("other@account.com");
+    accountCache.put(ou);
+
+    injector = Guice.createInjector(new FactoryModule() {
+      @Override
+      public void configure() {
+        install(new GitModule());
+        bind(NotesMigration.class).toInstance(NotesMigration.allEnabled());
+        bind(GitRepositoryManager.class).toInstance(repoManager);
+        bind(ProjectCache.class).toProvider(Providers.<ProjectCache> of(null));
+        bind(CapabilityControl.Factory.class)
+            .toProvider(Providers.<CapabilityControl.Factory> of(null));
+        bind(Config.class).annotatedWith(GerritServerConfig.class)
+            .toInstance(new Config());
+        bind(String.class).annotatedWith(AnonymousCowardName.class)
+            .toProvider(AnonymousCowardNameProvider.class);
+        bind(String.class).annotatedWith(CanonicalWebUrl.class)
+            .toInstance("http://localhost:8080/");
+        bind(Realm.class).to(FakeRealm.class);
+        bind(GroupBackend.class).to(SystemGroupBackend.class).in(SINGLETON);
+        bind(AccountCache.class).toInstance(accountCache);
+        bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class)
+            .toInstance(serverIdent);
+        bind(GitReferenceUpdated.class)
+            .toInstance(GitReferenceUpdated.DISABLED);
+      }
+    });
+
+    IdentifiedUser.GenericFactory userFactory =
+        injector.getInstance(IdentifiedUser.GenericFactory.class);
+    changeOwner = userFactory.create(co.getId());
+    otherUser = userFactory.create(ou.getId());
+  }
+
+  private void setTimeForTesting() {
+    systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
+    clockStepMs = MILLISECONDS.convert(1, SECONDS);
+    final AtomicLong clockMs = new AtomicLong(
+        new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
+
+    DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
+      @Override
+      public long getMillis() {
+        return clockMs.getAndAdd(clockStepMs);
+      }
+    });
+  }
+
+  @After
+  public void resetTime() {
+    DateTimeUtils.setCurrentMillisSystem();
+    System.setProperty("user.timezone", systemTimeZone);
+  }
+
+  @Test
+  public void approvalsCommitFormatSimple() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putApproval("Verified", (short) 1);
+    update.putApproval("Code-Review", (short) -1);
+    update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+    update.putReviewer(otherUser.getAccount().getId(), CC);
+    update.commit();
+    assertEquals("refs/changes/01/1/meta", update.getRefName());
+
+    RevWalk walk = new RevWalk(repo);
+    try {
+      RevCommit commit = walk.parseCommit(update.getRevision());
+      walk.parseBody(commit);
+      assertEquals("Update patch set 1\n"
+          + "\n"
+          + "Patch-set: 1\n"
+          + "Reviewer: Change Owner <1@gerrit>\n"
+          + "CC: Other Account <2@gerrit>\n"
+          + "Label: Code-Review=-1\n"
+          + "Label: Verified=+1\n",
+          commit.getFullMessage());
+
+      PersonIdent author = commit.getAuthorIdent();
+      assertEquals("Change Owner", author.getName());
+      assertEquals("1@gerrit", author.getEmailAddress());
+      assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
+          author.getWhen());
+      assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
+
+      PersonIdent committer = commit.getCommitterIdent();
+      assertEquals("Gerrit Server", committer.getName());
+      assertEquals("noreply@gerrit.com", committer.getEmailAddress());
+      assertEquals(author.getWhen(), committer.getWhen());
+      assertEquals(author.getTimeZone(), committer.getTimeZone());
+    } finally {
+      walk.release();
+    }
+  }
+
+  @Test
+  public void approvalTombstoneCommitFormat() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.removeApproval("Code-Review");
+    update.commit();
+
+    RevWalk walk = new RevWalk(repo);
+    try {
+      RevCommit commit = walk.parseCommit(update.getRevision());
+      walk.parseBody(commit);
+      assertEquals("Update patch set 1\n"
+          + "\n"
+          + "Patch-set: 1\n"
+          + "Label: -Code-Review\n",
+          commit.getFullMessage());
+    } finally {
+      walk.release();
+    }
+  }
+
+  @Test
+  public void submitCommitFormat() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setSubject("Submit patch set 1");
+
+    update.submit(ImmutableList.of(
+        submitRecord("NOT_READY", null,
+          submitLabel("Verified", "OK", changeOwner.getAccountId()),
+          submitLabel("Code-Review", "NEED", null)),
+        submitRecord("NOT_READY", null,
+          submitLabel("Verified", "OK", changeOwner.getAccountId()),
+          submitLabel("Alternative-Code-Review", "NEED", null))));
+    update.commit();
+
+    RevWalk walk = new RevWalk(repo);
+    try {
+      RevCommit commit = walk.parseCommit(update.getRevision());
+      walk.parseBody(commit);
+      assertEquals("Submit patch set 1\n"
+          + "\n"
+          + "Patch-set: 1\n"
+          + "Status: submitted\n"
+          + "Submitted-with: NOT_READY\n"
+          + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+          + "Submitted-with: NEED: Code-Review\n"
+          + "Submitted-with: NOT_READY\n"
+          + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+          + "Submitted-with: NEED: Alternative-Code-Review\n",
+          commit.getFullMessage());
+
+      PersonIdent author = commit.getAuthorIdent();
+      assertEquals("Change Owner", author.getName());
+      assertEquals("1@gerrit", author.getEmailAddress());
+      assertEquals(new Date(c.getCreatedOn().getTime() + 1000),
+          author.getWhen());
+      assertEquals(TimeZone.getTimeZone("GMT-7:00"), author.getTimeZone());
+
+      PersonIdent committer = commit.getCommitterIdent();
+      assertEquals("Gerrit Server", committer.getName());
+      assertEquals("noreply@gerrit.com", committer.getEmailAddress());
+      assertEquals(author.getWhen(), committer.getWhen());
+      assertEquals(author.getTimeZone(), committer.getTimeZone());
+    } finally {
+      walk.release();
+    }
+  }
+
+  @Test
+  public void submitWithErrorMessage() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setSubject("Submit patch set 1");
+
+    update.submit(ImmutableList.of(
+        submitRecord("RULE_ERROR", "Problem with patch set:\n1")));
+    update.commit();
+
+    RevWalk walk = new RevWalk(repo);
+    try {
+      RevCommit commit = walk.parseCommit(update.getRevision());
+      walk.parseBody(commit);
+      assertEquals("Submit patch set 1\n"
+          + "\n"
+          + "Patch-set: 1\n"
+          + "Status: submitted\n"
+          + "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
+          commit.getFullMessage());
+    } finally {
+      walk.release();
+    }
+  }
+
+  @Test
+  public void approvalsOnePatchSet() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putApproval("Verified", (short) 1);
+    update.putApproval("Code-Review", (short) -1);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    assertEquals(1, notes.getApprovals().keySet().size());
+    List<PatchSetApproval> psas =
+      notes.getApprovals().get(c.currentPatchSetId());
+    assertEquals(2, psas.size());
+
+    assertEquals(c.currentPatchSetId(), psas.get(0).getPatchSetId());
+    assertEquals(1, psas.get(0).getAccountId().get());
+    assertEquals("Code-Review", psas.get(0).getLabel());
+    assertEquals((short) -1, psas.get(0).getValue());
+    assertEquals(truncate(after(c, 1000)), psas.get(0).getGranted());
+
+    assertEquals(c.currentPatchSetId(), psas.get(1).getPatchSetId());
+    assertEquals(1, psas.get(1).getAccountId().get());
+    assertEquals("Verified", psas.get(1).getLabel());
+    assertEquals((short) 1, psas.get(1).getValue());
+    assertEquals(psas.get(0).getGranted(), psas.get(1).getGranted());
+  }
+
+  @Test
+  public void approvalsMultiplePatchSets() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putApproval("Code-Review", (short) -1);
+    update.commit();
+    PatchSet.Id ps1 = c.currentPatchSetId();
+
+    incrementPatchSet(c);
+    update = newUpdate(c, changeOwner);
+    update.putApproval("Code-Review", (short) 1);
+    update.commit();
+    PatchSet.Id ps2 = c.currentPatchSetId();
+
+    ChangeNotes notes = newNotes(c);
+    ListMultimap<PatchSet.Id, PatchSetApproval> psas = notes.getApprovals();
+    assertEquals(2, notes.getApprovals().keySet().size());
+
+    PatchSetApproval psa1 = Iterables.getOnlyElement(psas.get(ps1));
+    assertEquals(ps1, psa1.getPatchSetId());
+    assertEquals(1, psa1.getAccountId().get());
+    assertEquals("Code-Review", psa1.getLabel());
+    assertEquals((short) -1, psa1.getValue());
+    assertEquals(truncate(after(c, 1000)), psa1.getGranted());
+
+    PatchSetApproval psa2 = Iterables.getOnlyElement(psas.get(ps2));
+    assertEquals(ps2, psa2.getPatchSetId());
+    assertEquals(1, psa2.getAccountId().get());
+    assertEquals("Code-Review", psa2.getLabel());
+    assertEquals((short) +1, psa2.getValue());
+    assertEquals(truncate(after(c, 2000)), psa2.getGranted());
+  }
+
+  @Test
+  public void approvalsMultipleApprovals() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putApproval("Code-Review", (short) -1);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    PatchSetApproval psa = Iterables.getOnlyElement(
+        notes.getApprovals().get(c.currentPatchSetId()));
+    assertEquals("Code-Review", psa.getLabel());
+    assertEquals((short) -1, psa.getValue());
+
+    update = newUpdate(c, changeOwner);
+    update.putApproval("Code-Review", (short) 1);
+    update.commit();
+
+    notes = newNotes(c);
+    psa = Iterables.getOnlyElement(
+        notes.getApprovals().get(c.currentPatchSetId()));
+    assertEquals("Code-Review", psa.getLabel());
+    assertEquals((short) 1, psa.getValue());
+  }
+
+  @Test
+  public void approvalsMultipleUsers() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putApproval("Code-Review", (short) -1);
+    update.commit();
+
+    update = newUpdate(c, otherUser);
+    update.putApproval("Code-Review", (short) 1);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    assertEquals(1, notes.getApprovals().keySet().size());
+    List<PatchSetApproval> psas =
+      notes.getApprovals().get(c.currentPatchSetId());
+    assertEquals(2, psas.size());
+
+    assertEquals(c.currentPatchSetId(), psas.get(0).getPatchSetId());
+    assertEquals(1, psas.get(0).getAccountId().get());
+    assertEquals("Code-Review", psas.get(0).getLabel());
+    assertEquals((short) -1, psas.get(0).getValue());
+    assertEquals(truncate(after(c, 1000)), psas.get(0).getGranted());
+
+    assertEquals(c.currentPatchSetId(), psas.get(1).getPatchSetId());
+    assertEquals(2, psas.get(1).getAccountId().get());
+    assertEquals("Code-Review", psas.get(1).getLabel());
+    assertEquals((short) 1, psas.get(1).getValue());
+    assertEquals(truncate(after(c, 2000)), psas.get(1).getGranted());
+  }
+
+  @Test
+  public void approvalsTombstone() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putApproval("Not-For-Long", (short) 1);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    PatchSetApproval psa = Iterables.getOnlyElement(
+        notes.getApprovals().get(c.currentPatchSetId()));
+    assertEquals(1, psa.getAccountId().get());
+    assertEquals("Not-For-Long", psa.getLabel());
+    assertEquals((short) 1, psa.getValue());
+
+    update = newUpdate(c, changeOwner);
+    update.removeApproval("Not-For-Long");
+    update.commit();
+
+    notes = newNotes(c);
+    assertTrue(notes.getApprovals().isEmpty());
+  }
+
+  @Test
+  public void multipleReviewers() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+    update.putReviewer(otherUser.getAccount().getId(), REVIEWER);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    assertEquals(ImmutableSetMultimap.of(
+          REVIEWER, new Account.Id(1),
+          REVIEWER, new Account.Id(2)),
+        notes.getReviewers());
+  }
+
+  @Test
+  public void reviewerTypes() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+    update.putReviewer(otherUser.getAccount().getId(), CC);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    assertEquals(ImmutableSetMultimap.of(
+          REVIEWER, new Account.Id(1),
+          CC, new Account.Id(2)),
+        notes.getReviewers());
+  }
+
+  @Test
+  public void oneReviewerMultipleTypes() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putReviewer(otherUser.getAccount().getId(), REVIEWER);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    assertEquals(ImmutableSetMultimap.of(
+          REVIEWER, new Account.Id(2)),
+        notes.getReviewers());
+
+    update = newUpdate(c, otherUser);
+    update.putReviewer(otherUser.getAccount().getId(), CC);
+    update.commit();
+
+    notes = newNotes(c);
+    assertEquals(ImmutableSetMultimap.of(
+          CC, new Account.Id(2)),
+        notes.getReviewers());
+  }
+
+  @Test
+  public void removeReviewer() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putReviewer(otherUser.getAccount().getId(), REVIEWER);
+    update.commit();
+
+    update = newUpdate(c, changeOwner);
+    update.putApproval("Code-Review", (short) 1);
+    update.commit();
+
+    update = newUpdate(c, otherUser);
+    update.putApproval("Code-Review", (short) 1);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    List<PatchSetApproval> psas =
+        notes.getApprovals().get(c.currentPatchSetId());
+    assertEquals(2, psas.size());
+    assertEquals(changeOwner.getAccount().getId(), psas.get(0).getAccountId());
+    assertEquals(otherUser.getAccount().getId(), psas.get(1).getAccountId());
+
+    update = newUpdate(c, changeOwner);
+    update.removeReviewer(otherUser.getAccount().getId());
+    update.commit();
+
+    notes = newNotes(c);
+    psas = notes.getApprovals().get(c.currentPatchSetId());
+    assertEquals(1, psas.size());
+    assertEquals(changeOwner.getAccount().getId(), psas.get(0).getAccountId());
+  }
+
+  @Test
+  public void submitRecords() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setSubject("Submit patch set 1");
+
+    update.submit(ImmutableList.of(
+        submitRecord("NOT_READY", null,
+          submitLabel("Verified", "OK", changeOwner.getAccountId()),
+          submitLabel("Code-Review", "NEED", null)),
+        submitRecord("NOT_READY", null,
+          submitLabel("Verified", "OK", changeOwner.getAccountId()),
+          submitLabel("Alternative-Code-Review", "NEED", null))));
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    List<SubmitRecord> recs = notes.getSubmitRecords();
+    assertEquals(2, recs.size());
+    assertEquals(submitRecord("NOT_READY", null,
+        submitLabel("Verified", "OK", changeOwner.getAccountId()),
+        submitLabel("Code-Review", "NEED", null)), recs.get(0));
+    assertEquals(submitRecord("NOT_READY", null,
+        submitLabel("Verified", "OK", changeOwner.getAccountId()),
+        submitLabel("Alternative-Code-Review", "NEED", null)), recs.get(1));
+  }
+
+  @Test
+  public void latestSubmitRecordsOnly() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setSubject("Submit patch set 1");
+    update.submit(ImmutableList.of(
+        submitRecord("OK", null,
+          submitLabel("Code-Review", "OK", otherUser.getAccountId()))));
+    update.commit();
+
+    incrementPatchSet(c);
+    update = newUpdate(c, changeOwner);
+    update.setSubject("Submit patch set 2");
+    update.submit(ImmutableList.of(
+        submitRecord("OK", null,
+          submitLabel("Code-Review", "OK", changeOwner.getAccountId()))));
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    assertEquals(submitRecord("OK", null,
+          submitLabel("Code-Review", "OK", changeOwner.getAccountId())),
+        Iterables.getOnlyElement(notes.getSubmitRecords()));
+  }
+
+  @Test
+  public void multipleUpdatesInBatch() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update1 = newUpdate(c, changeOwner);
+    update1.putApproval("Verified", (short) 1);
+
+    ChangeUpdate update2 = newUpdate(c, otherUser);
+    update2.putApproval("Code-Review", (short) 2);
+
+    BatchMetaDataUpdate batch = update1.openUpdate();
+    try {
+      batch.write(update1, new CommitBuilder());
+      batch.write(update2, new CommitBuilder());
+      batch.commit();
+    } finally {
+      batch.close();
+    }
+
+    ChangeNotes notes = newNotes(c);
+    List<PatchSetApproval> psas =
+        notes.getApprovals().get(c.currentPatchSetId());
+    assertEquals(2, psas.size());
+
+    assertEquals(changeOwner.getAccount().getId(), psas.get(0).getAccountId());
+    assertEquals("Verified", psas.get(0).getLabel());
+    assertEquals((short) 1, psas.get(0).getValue());
+
+    assertEquals(otherUser.getAccount().getId(), psas.get(1).getAccountId());
+    assertEquals("Code-Review", psas.get(1).getLabel());
+    assertEquals((short) 2, psas.get(1).getValue());
+  }
+
+  private Change newChange() {
+    Change.Id changeId = new Change.Id(1);
+    Change c = new Change(
+        new Change.Key("Iabcd1234abcd1234abcd1234abcd1234abcd1234"),
+        changeId,
+        changeOwner.getAccount().getId(),
+        new Branch.NameKey(project, "master"),
+        TimeUtil.nowTs());
+    incrementPatchSet(c);
+    return c;
+  }
+
+  private ChangeUpdate newUpdate(Change c, final IdentifiedUser user)
+      throws Exception {
+    return injector.createChildInjector(new FactoryModule() {
+      @Override
+      public void configure() {
+        factory(ChangeUpdate.Factory.class);
+        bind(IdentifiedUser.class).toInstance(user);
+      }
+    }).getInstance(ChangeUpdate.Factory.class).create(
+        stubChangeControl(c, user), TimeUtil.nowTs(),
+        Ordering.<String> natural());
+  }
+
+  private ChangeNotes newNotes(Change c) throws OrmException {
+    return new ChangeNotes(repoManager, c).load();
+  }
+
+  private static void incrementPatchSet(Change change) {
+    PatchSet.Id curr = change.currentPatchSetId();
+    PatchSetInfo ps = new PatchSetInfo(new PatchSet.Id(
+        change.getId(), curr != null ? curr.get() + 1 : 1));
+    ps.setSubject("Change subject");
+    change.setCurrentPatchSet(ps);
+  }
+
+  private static Timestamp truncate(Timestamp ts) {
+    return new Timestamp((ts.getTime() / 1000) * 1000);
+  }
+
+  private static Timestamp after(Change c, long millis) {
+    return new Timestamp(c.getCreatedOn().getTime() + millis);
+  }
+
+  private ChangeControl stubChangeControl(Change c, IdentifiedUser user) {
+    ChangeControl ctl = EasyMock.createNiceMock(ChangeControl.class);
+    expect(ctl.getChange()).andStubReturn(c);
+    expect(ctl.getCurrentUser()).andStubReturn(user);
+    EasyMock.replay(ctl);
+    return ctl;
+  }
+
+  private static SubmitRecord submitRecord(String status,
+      String errorMessage, SubmitRecord.Label... labels) {
+    SubmitRecord rec = new SubmitRecord();
+    rec.status = SubmitRecord.Status.valueOf(status);
+    rec.errorMessage = errorMessage;
+    if (labels.length > 0) {
+      rec.labels = ImmutableList.copyOf(labels);
+    }
+    return rec;
+  }
+
+  private static SubmitRecord.Label submitLabel(String name, String status,
+      Account.Id appliedBy) {
+    SubmitRecord.Label label = new SubmitRecord.Label();
+    label.label = name;
+    label.status = SubmitRecord.Label.Status.valueOf(status);
+    label.appliedBy = appliedBy;
+    return label;
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java
index 7df0696..af60ea8 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListEntryTest.java
@@ -12,14 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-
 package com.google.gerrit.server.patch;
 
 import com.google.gerrit.reviewdb.client.Patch;
+import org.junit.Test;
 
-import junit.framework.TestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
-public class PatchListEntryTest extends TestCase {
+public class PatchListEntryTest {
+  @Test
   public void testEmpty1() {
     final String name = "empty-file";
     final PatchListEntry e = PatchListEntry.empty(name);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index fb9165e..5b5b318 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -20,12 +20,16 @@
 import static com.google.gerrit.common.data.Permission.PUSH;
 import static com.google.gerrit.common.data.Permission.READ;
 import static com.google.gerrit.common.data.Permission.SUBMIT;
-import static com.google.gerrit.server.project.Util.ANONYMOUS;
-import static com.google.gerrit.server.project.Util.REGISTERED;
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static com.google.gerrit.server.project.Util.ADMIN;
 import static com.google.gerrit.server.project.Util.DEVS;
-import static com.google.gerrit.server.project.Util.grant;
 import static com.google.gerrit.server.project.Util.doNotInherit;
+import static com.google.gerrit.server.project.Util.grant;
+import static com.google.gerrit.testutil.InMemoryRepositoryManager.newRepository;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.google.gerrit.common.data.Capable;
 import com.google.gerrit.common.data.PermissionRange;
@@ -34,9 +38,10 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.ProjectConfig;
 
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
 
-public class RefControlTest extends TestCase {
+public class RefControlTest {
   private static void assertOwner(String ref, ProjectControl u) {
     assertTrue("OWN " + ref, u.controlForRef(ref).isOwner());
   }
@@ -54,14 +59,14 @@
     util = new Util();
   }
 
-  @Override
+  @Before
   public void setUp() throws Exception {
-    super.setUp();
     local = new ProjectConfig(localKey);
-    local.createInMemory();
+    local.load(newRepository(localKey));
     util.add(local);
   }
 
+  @Test
   public void testOwnerProject() {
     grant(local, OWNER, ADMIN, "refs/*");
 
@@ -72,6 +77,7 @@
     assertTrue("is owner", uAdmin.isOwner());
   }
 
+  @Test
   public void testBranchDelegation1() {
     grant(local, OWNER, ADMIN, "refs/*");
     grant(local, OWNER, DEVS, "refs/heads/x/*");
@@ -88,6 +94,7 @@
     assertNotOwner("refs/heads/master", uDev);
   }
 
+  @Test
   public void testBranchDelegation2() {
     grant(local, OWNER, ADMIN, "refs/*");
     grant(local, OWNER, DEVS, "refs/heads/x/*");
@@ -116,10 +123,11 @@
     assertNotOwner("refs/heads/master", uFix);
   }
 
+  @Test
   public void testInheritRead_SingleBranchDeniesUpload() {
-    grant(util.getParentConfig(), READ, REGISTERED, "refs/*");
-    grant(util.getParentConfig(), PUSH, REGISTERED, "refs/for/refs/*");
-    grant(local, READ, REGISTERED, "refs/heads/foobar");
+    grant(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
+    grant(util.getParentConfig(), PUSH, REGISTERED_USERS, "refs/for/refs/*");
+    grant(local, READ, REGISTERED_USERS, "refs/heads/foobar");
     doNotInherit(local, READ, "refs/heads/foobar");
     doNotInherit(local, PUSH, "refs/for/refs/heads/foobar");
 
@@ -133,9 +141,10 @@
         u.controlForRef("refs/heads/foobar").canUpload());
   }
 
+  @Test
   public void testBlockPushDrafts() {
-    grant(util.getParentConfig(), PUSH, REGISTERED, "refs/for/refs/*");
-    grant(util.getParentConfig(), PUSH, ANONYMOUS, "refs/drafts/*")
+    grant(util.getParentConfig(), PUSH, REGISTERED_USERS, "refs/for/refs/*");
+    grant(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/drafts/*")
         .setBlock();
 
     ProjectControl u = util.user(local);
@@ -145,8 +154,9 @@
         u.controlForRef("refs/drafts/refs/heads/master").isBlocked(PUSH));
   }
 
+  @Test
   public void testBlockPushDraftsUnblockAdmin() {
-    grant(util.getParentConfig(), PUSH, ANONYMOUS, "refs/drafts/*")
+    grant(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/drafts/*")
         .setBlock();
     grant(util.getParentConfig(), PUSH, ADMIN, "refs/drafts/*");
 
@@ -158,10 +168,11 @@
             .isBlocked(PUSH));
   }
 
+  @Test
   public void testInheritRead_SingleBranchDoesNotOverrideInherited() {
-    grant(util.getParentConfig(), READ, REGISTERED, "refs/*");
-    grant(util.getParentConfig(), PUSH, REGISTERED, "refs/for/refs/*");
-    grant(local, READ, REGISTERED, "refs/heads/foobar");
+    grant(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
+    grant(util.getParentConfig(), PUSH, REGISTERED_USERS, "refs/for/refs/*");
+    grant(local, READ, REGISTERED_USERS, "refs/heads/foobar");
 
     ProjectControl u = util.user(local);
     assertTrue("can upload", u.canPushToAtLeastOneRef() == Capable.OK);
@@ -173,29 +184,32 @@
         u.controlForRef("refs/heads/foobar").canUpload());
   }
 
-  public void testInheritDuplicateSections() {
+  @Test
+  public void testInheritDuplicateSections() throws Exception {
     grant(util.getParentConfig(), READ, ADMIN, "refs/*");
     grant(local, READ, DEVS, "refs/heads/*");
     local.getProject().setParentName(util.getParentConfig().getProject().getName());
     assertTrue("a can read", util.user(local, "a", ADMIN).isVisible());
 
     local = new ProjectConfig(new Project.NameKey("local"));
-    local.createInMemory();
+    local.load(newRepository(localKey));
     grant(local, READ, DEVS, "refs/*");
     assertTrue("d can read", util.user(local, "d", DEVS).isVisible());
   }
 
+  @Test
   public void testInheritRead_OverrideWithDeny() {
-    grant(util.getParentConfig(), READ, REGISTERED, "refs/*");
-    grant(local, READ, REGISTERED, "refs/*").setDeny();
+    grant(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
+    grant(local, READ, REGISTERED_USERS, "refs/*").setDeny();
 
     ProjectControl u = util.user(local);
     assertFalse("can't read", u.isVisible());
   }
 
+  @Test
   public void testInheritRead_AppendWithDenyOfRef() {
-    grant(util.getParentConfig(), READ, REGISTERED, "refs/*");
-    grant(local, READ, REGISTERED, "refs/heads/*").setDeny();
+    grant(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
+    grant(local, READ, REGISTERED_USERS, "refs/heads/*").setDeny();
 
     ProjectControl u = util.user(local);
     assertTrue("can read", u.isVisible());
@@ -204,10 +218,11 @@
     assertTrue("no master", u.controlForRef("refs/heads/master").isVisible());
   }
 
+  @Test
   public void testInheritRead_OverridesAndDeniesOfRef() {
-    grant(util.getParentConfig(), READ, REGISTERED, "refs/*");
-    grant(local, READ, REGISTERED, "refs/*").setDeny();
-    grant(local, READ, REGISTERED, "refs/heads/*");
+    grant(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
+    grant(local, READ, REGISTERED_USERS, "refs/*").setDeny();
+    grant(local, READ, REGISTERED_USERS, "refs/heads/*");
 
     ProjectControl u = util.user(local);
     assertTrue("can read", u.isVisible());
@@ -216,10 +231,11 @@
     assertTrue("can read", u.controlForRef("refs/heads/foobar").isVisible());
   }
 
+  @Test
   public void testInheritSubmit_OverridesAndDeniesOfRef() {
-    grant(util.getParentConfig(), SUBMIT, REGISTERED, "refs/*");
-    grant(local, SUBMIT, REGISTERED, "refs/*").setDeny();
-    grant(local, SUBMIT, REGISTERED, "refs/heads/*");
+    grant(util.getParentConfig(), SUBMIT, REGISTERED_USERS, "refs/*");
+    grant(local, SUBMIT, REGISTERED_USERS, "refs/*").setDeny();
+    grant(local, SUBMIT, REGISTERED_USERS, "refs/heads/*");
 
     ProjectControl u = util.user(local);
     assertFalse("can't submit", u.controlForRef("refs/foobar").canSubmit());
@@ -227,8 +243,9 @@
     assertTrue("can submit", u.controlForRef("refs/heads/foobar").canSubmit());
   }
 
+  @Test
   public void testCannotUploadToAnyRef() {
-    grant(util.getParentConfig(), READ, REGISTERED, "refs/*");
+    grant(util.getParentConfig(), READ, REGISTERED_USERS, "refs/*");
     grant(local, READ, DEVS, "refs/heads/*");
     grant(local, PUSH, DEVS, "refs/for/refs/heads/*");
 
@@ -238,6 +255,7 @@
         u.controlForRef("refs/heads/master").canUpload());
   }
 
+  @Test
   public void testUsernamePatternNonRegex() {
     grant(local, READ, DEVS, "refs/sb/${username}/heads/*");
 
@@ -246,6 +264,7 @@
     assertTrue("d can read", d.controlForRef("refs/sb/d/heads/foobar").isVisible());
   }
 
+  @Test
   public void testUsernamePatternWithRegex() {
     grant(local, READ, DEVS, "^refs/sb/${username}/heads/.*");
 
@@ -254,23 +273,38 @@
     assertTrue("d can read", d.controlForRef("refs/sb/dev/heads/foobar").isVisible());
   }
 
+  @Test
+  public void testUsernameEmailPatternWithRegex() {
+    grant(local, READ, DEVS, "^refs/sb/${username}/heads/.*");
+
+    ProjectControl u = util.user(local, "d.v@ger-rit.org", DEVS);
+    ProjectControl d = util.user(local, "dev@ger-rit.org", DEVS);
+    assertFalse("u can't read",
+        u.controlForRef("refs/sb/dev@ger-rit.org/heads/foobar").isVisible());
+    assertTrue("d can read",
+        d.controlForRef("refs/sb/dev@ger-rit.org/heads/foobar").isVisible());
+  }
+
+  @Test
   public void testSortWithRegex() {
     grant(local, READ, DEVS, "^refs/heads/.*");
-    grant(util.getParentConfig(), READ, ANONYMOUS, "^refs/heads/.*-QA-.*");
+    grant(util.getParentConfig(), READ, ANONYMOUS_USERS, "^refs/heads/.*-QA-.*");
 
     ProjectControl u = util.user(local, DEVS), d = util.user(local, DEVS);
     assertTrue("u can read", u.controlForRef("refs/heads/foo-QA-bar").isVisible());
     assertTrue("d can read", d.controlForRef("refs/heads/foo-QA-bar").isVisible());
   }
 
+  @Test
   public void testBlockRule_ParentBlocksChild() {
     grant(local, PUSH, DEVS, "refs/tags/*");
-    grant(util.getParentConfig(), PUSH, ANONYMOUS, "refs/tags/*").setBlock();
+    grant(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/tags/*").setBlock();
 
     ProjectControl u = util.user(local, DEVS);
     assertFalse("u can't force update tag", u.controlForRef("refs/tags/V10").canForceUpdate());
   }
 
+  @Test
   public void testBlockLabelRange_ParentBlocksChild() {
     grant(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
     grant(util.getParentConfig(), LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*").setBlock();
@@ -284,16 +318,18 @@
     assertFalse("u can't vote 2", range.contains(2));
   }
 
+  @Test
   public void testUnblockNoForce() {
-    grant(local, PUSH, ANONYMOUS, "refs/heads/*").setBlock();
+    grant(local, PUSH, ANONYMOUS_USERS, "refs/heads/*").setBlock();
     grant(local, PUSH, DEVS, "refs/heads/*");
 
     ProjectControl u = util.user(local, DEVS);
     assertTrue("u can push", u.controlForRef("refs/heads/master").canUpdate());
   }
 
+  @Test
   public void testUnblockForce() {
-    PermissionRule r = grant(local, PUSH, ANONYMOUS, "refs/heads/*");
+    PermissionRule r = grant(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
     r.setBlock();
     r.setForce(true);
     grant(local, PUSH, DEVS, "refs/heads/*").setForce(true);
@@ -302,8 +338,9 @@
     assertTrue("u can force push", u.controlForRef("refs/heads/master").canForceUpdate());
   }
 
+  @Test
   public void testUnblockForceWithAllowNoForce_NotPossible() {
-    PermissionRule r = grant(local, PUSH, ANONYMOUS, "refs/heads/*");
+    PermissionRule r = grant(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
     r.setBlock();
     r.setForce(true);
     grant(local, PUSH, DEVS, "refs/heads/*");
@@ -312,32 +349,36 @@
     assertFalse("u can't force push", u.controlForRef("refs/heads/master").canForceUpdate());
   }
 
+  @Test
   public void testUnblockMoreSpecificRef_Fails() {
-    grant(local, PUSH, ANONYMOUS, "refs/heads/*").setBlock();
+    grant(local, PUSH, ANONYMOUS_USERS, "refs/heads/*").setBlock();
     grant(local, PUSH, DEVS, "refs/heads/master");
 
     ProjectControl u = util.user(local, DEVS);
     assertFalse("u can't push", u.controlForRef("refs/heads/master").canUpdate());
   }
 
+  @Test
   public void testUnblockLargerScope_Fails() {
-    grant(local, PUSH, ANONYMOUS, "refs/heads/master").setBlock();
+    grant(local, PUSH, ANONYMOUS_USERS, "refs/heads/master").setBlock();
     grant(local, PUSH, DEVS, "refs/heads/*");
 
     ProjectControl u = util.user(local, DEVS);
     assertFalse("u can't push", u.controlForRef("refs/heads/master").canUpdate());
   }
 
+  @Test
   public void testUnblockInLocal_Fails() {
-    grant(util.getParentConfig(), PUSH, ANONYMOUS, "refs/heads/*").setBlock();
+    grant(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/heads/*").setBlock();
     grant(local, PUSH, fixers, "refs/heads/*");
 
     ProjectControl f = util.user(local, fixers);
     assertFalse("u can't push", f.controlForRef("refs/heads/master").canUpdate());
   }
 
+  @Test
   public void testUnblockInParentBlockInLocal() {
-    grant(util.getParentConfig(), PUSH, ANONYMOUS, "refs/heads/*").setBlock();
+    grant(util.getParentConfig(), PUSH, ANONYMOUS_USERS, "refs/heads/*").setBlock();
     grant(util.getParentConfig(), PUSH, DEVS, "refs/heads/*");
     grant(local, PUSH, DEVS, "refs/heads/*").setBlock();
 
@@ -345,24 +386,27 @@
     assertFalse("u can't push", d.controlForRef("refs/heads/master").canUpdate());
   }
 
+  @Test
   public void testUnblockVisibilityByREGISTEREDUsers() {
-    grant(local, READ, ANONYMOUS, "refs/heads/*").setBlock();
-    grant(local, READ, REGISTERED, "refs/heads/*");
+    grant(local, READ, ANONYMOUS_USERS, "refs/heads/*").setBlock();
+    grant(local, READ, REGISTERED_USERS, "refs/heads/*");
 
-    ProjectControl u = util.user(local, REGISTERED);
+    ProjectControl u = util.user(local, REGISTERED_USERS);
     assertTrue("u can read", u.controlForRef("refs/heads/master").isVisibleByRegisteredUsers());
   }
 
+  @Test
   public void testUnblockInLocalVisibilityByRegisteredUsers_Fails() {
-    grant(util.getParentConfig(), READ, ANONYMOUS, "refs/heads/*").setBlock();
-    grant(local, READ, REGISTERED, "refs/heads/*");
+    grant(util.getParentConfig(), READ, ANONYMOUS_USERS, "refs/heads/*").setBlock();
+    grant(local, READ, REGISTERED_USERS, "refs/heads/*");
 
-    ProjectControl u = util.user(local, REGISTERED);
+    ProjectControl u = util.user(local, REGISTERED_USERS);
     assertFalse("u can't read", u.controlForRef("refs/heads/master").isVisibleByRegisteredUsers());
   }
 
+  @Test
   public void testUnblockForceEditTopicName() {
-    grant(local, EDIT_TOPIC_NAME, ANONYMOUS, "refs/heads/*").setBlock();
+    grant(local, EDIT_TOPIC_NAME, ANONYMOUS_USERS, "refs/heads/*").setBlock();
     grant(local, EDIT_TOPIC_NAME, DEVS, "refs/heads/*").setForce(true);
 
     ProjectControl u = util.user(local, DEVS);
@@ -370,18 +414,20 @@
         .canForceEditTopicName());
   }
 
+  @Test
   public void testUnblockInLocalForceEditTopicName_Fails() {
-    grant(util.getParentConfig(), EDIT_TOPIC_NAME, ANONYMOUS, "refs/heads/*")
+    grant(util.getParentConfig(), EDIT_TOPIC_NAME, ANONYMOUS_USERS, "refs/heads/*")
         .setBlock();
     grant(local, EDIT_TOPIC_NAME, DEVS, "refs/heads/*").setForce(true);
 
-    ProjectControl u = util.user(local, REGISTERED);
+    ProjectControl u = util.user(local, REGISTERED_USERS);
     assertFalse("u can't edit topic name", u.controlForRef("refs/heads/master")
         .canForceEditTopicName());
   }
 
+  @Test
   public void testUnblockRange() {
-    grant(local, LABEL + "Code-Review", -1, +1, ANONYMOUS, "refs/heads/*").setBlock();
+    grant(local, LABEL + "Code-Review", -1, +1, ANONYMOUS_USERS, "refs/heads/*").setBlock();
     grant(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
 
     ProjectControl u = util.user(local, DEVS);
@@ -390,8 +436,9 @@
     assertTrue("u can vote +2", range.contains(2));
   }
 
+  @Test
   public void testUnblockRangeOnMoreSpecificRef_Fails() {
-    grant(local, LABEL + "Code-Review", -1, +1, ANONYMOUS, "refs/heads/*").setBlock();
+    grant(local, LABEL + "Code-Review", -1, +1, ANONYMOUS_USERS, "refs/heads/*").setBlock();
     grant(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/master");
 
     ProjectControl u = util.user(local, DEVS);
@@ -400,8 +447,9 @@
     assertFalse("u can't vote +2", range.contains(-2));
   }
 
+  @Test
   public void testUnblockRangeOnLargerScope_Fails() {
-    grant(local, LABEL + "Code-Review", -1, +1, ANONYMOUS, "refs/heads/master").setBlock();
+    grant(local, LABEL + "Code-Review", -1, +1, ANONYMOUS_USERS, "refs/heads/master").setBlock();
     grant(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
 
     ProjectControl u = util.user(local, DEVS);
@@ -410,8 +458,9 @@
     assertFalse("u can't vote +2", range.contains(-2));
   }
 
+  @Test
   public void testUnblockInLocalRange_Fails() {
-    grant(util.getParentConfig(), LABEL + "Code-Review", -1, 1, ANONYMOUS,
+    grant(util.getParentConfig(), LABEL + "Code-Review", -1, 1, ANONYMOUS_USERS,
         "refs/heads/*").setBlock();
     grant(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
 
@@ -421,4 +470,24 @@
     assertFalse("u can't vote -2", range.contains(-2));
     assertFalse("u can't vote 2", range.contains(2));
   }
+
+  public void testUnblockRangeForChangeOwner() {
+    grant(local, LABEL + "Code-Review", -2, +2, CHANGE_OWNER, "refs/heads/*");
+
+    ProjectControl u = util.user(local, DEVS);
+    PermissionRange range = u.controlForRef("refs/heads/master")
+        .getRange(LABEL + "Code-Review", true);
+    assertTrue("u can vote -2", range.contains(-2));
+    assertTrue("u can vote +2", range.contains(2));
+  }
+
+  public void testUnblockRangeForNotChangeOwner() {
+    grant(local, LABEL + "Code-Review", -2, +2, CHANGE_OWNER, "refs/heads/*");
+
+    ProjectControl u = util.user(local, DEVS);
+    PermissionRange range = u.controlForRef("refs/heads/master")
+        .getRange(LABEL + "Code-Review");
+    assertFalse("u can vote -2", range.contains(-2));
+    assertFalse("u can vote +2", range.contains(2));
+  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
index a99eba1..e4de9ed 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.server.project;
 
+import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.collect.Lists;
@@ -26,22 +29,37 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.NameKey;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.RulesCache;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.config.AnonymousCowardNameProvider;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.CanonicalWebUrlProvider;
 import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.testutil.FakeAccountCache;
+import com.google.gerrit.testutil.InMemoryRepositoryManager;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+import com.google.inject.util.Providers;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -54,17 +72,15 @@
 import java.util.Set;
 
 public class Util {
-  public static AccountGroup.UUID ANONYMOUS = AccountGroup.ANONYMOUS_USERS;
-  public static AccountGroup.UUID REGISTERED = AccountGroup.REGISTERED_USERS;
   public static AccountGroup.UUID ADMIN = new AccountGroup.UUID("test.admin");
   public static AccountGroup.UUID DEVS = new AccountGroup.UUID("test.devs");
 
-  public static LabelType CR = category("Code-Review",
+  public static final LabelType CR = category("Code-Review",
       value(2, "Looks good to me, approved"),
       value(1, "Looks good to me, but someone else must approve"),
       value(0, "No score"),
-      value(-1, "I would prefer that you didn't submit this"),
-      value(-2, "Do not submit"));
+      value(-1, "I would prefer this is not merged as is"),
+      value(-2, "This shall not be merged"));
 
   public static LabelValue value(int value, String text) {
     return new LabelValue((short) value, text);
@@ -114,17 +130,24 @@
   private final Map<Project.NameKey, ProjectState> all;
   private final ProjectCache projectCache;
   private final CapabilityControl.Factory capabilityControlFactory;
+  private final ChangeControl.AssistedFactory changeControlFactory;
   private final PermissionCollection.Factory sectionSorter;
+  private final GitRepositoryManager repoManager;
 
   private final AllProjectsName allProjectsName = new AllProjectsName("parent");
   private final ProjectConfig parent = new ProjectConfig(allProjectsName);
 
   public Util() {
     all = new HashMap<Project.NameKey, ProjectState>();
-    parent.createInMemory();
-    parent.getLabelSections().put(CR.getName(), CR);
-
-    add(parent);
+    repoManager = new InMemoryRepositoryManager();
+    try {
+      Repository repo = repoManager.createRepository(allProjectsName);
+      parent.load(repo);
+      parent.getLabelSections().put(CR.getName(), CR);
+      add(parent);
+    } catch (IOException | ConfigInvalidException e) {
+      throw new RuntimeException(e);
+    }
 
     projectCache = new ProjectCache() {
       @Override
@@ -179,9 +202,21 @@
       protected void configure() {
         bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(
             new Config());
+        bind(ReviewDb.class).toProvider(Providers.<ReviewDb> of(null));
+        bind(GitRepositoryManager.class).toInstance(repoManager);
+        bind(PatchListCache.class)
+            .toProvider(Providers.<PatchListCache> of(null));
 
         factory(CapabilityControl.Factory.class);
+        factory(ChangeControl.AssistedFactory.class);
+        factory(ChangeData.Factory.class);
         bind(ProjectCache.class).toInstance(projectCache);
+        bind(AccountCache.class).toInstance(new FakeAccountCache());
+        bind(GroupBackend.class).to(SystemGroupBackend.class);
+        bind(String.class).annotatedWith(CanonicalWebUrl.class)
+            .toProvider(CanonicalWebUrlProvider.class);
+        bind(String.class).annotatedWith(AnonymousCowardName.class)
+            .toProvider(AnonymousCowardNameProvider.class);
       }
     });
 
@@ -190,6 +225,8 @@
     sectionSorter = new PermissionCollection.Factory(new SectionSortCache(c));
     capabilityControlFactory =
         injector.getInstance(CapabilityControl.Factory.class);
+    changeControlFactory =
+      injector.getInstance(ChangeControl.AssistedFactory.class);
   }
 
   public ProjectConfig getParentConfig() {
@@ -198,15 +235,19 @@
 
   public void add(ProjectConfig pc) {
     PrologEnvironment.Factory envFactory = null;
-    GitRepositoryManager mgr = null;
     ProjectControl.AssistedFactory projectControlFactory = null;
     RulesCache rulesCache = null;
     SitePaths sitePaths = null;
     List<CommentLinkInfo> commentLinks = null;
 
+    try {
+      repoManager.createRepository(pc.getProject().getNameKey());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
     all.put(pc.getProject().getNameKey(), new ProjectState(sitePaths,
-        projectCache, allProjectsName, projectControlFactory, envFactory, mgr,
-        rulesCache, commentLinks, pc));
+        projectCache, allProjectsName, projectControlFactory, envFactory,
+        repoManager, rulesCache, commentLinks, pc));
   }
 
   public ProjectControl user(ProjectConfig local, AccountGroup.UUID... memberOf) {
@@ -219,8 +260,8 @@
 
     return new ProjectControl(Collections.<AccountGroup.UUID> emptySet(),
         Collections.<AccountGroup.UUID> emptySet(), projectCache,
-        sectionSorter, null, canonicalWebUrl, new MockUser(name, memberOf),
-        newProjectState(local));
+        sectionSorter, repoManager, changeControlFactory, canonicalWebUrl,
+        new MockUser(name, memberOf), newProjectState(local));
   }
 
   private ProjectState newProjectState(ProjectConfig local) {
@@ -236,8 +277,8 @@
       super(capabilityControlFactory);
       username = name;
       ArrayList<AccountGroup.UUID> groupIds = Lists.newArrayList(groupId);
-      groupIds.add(REGISTERED);
-      groupIds.add(ANONYMOUS);
+      groupIds.add(REGISTERED_USERS);
+      groupIds.add(ANONYMOUS_USERS);
       groups = new ListGroupMembership(groupIds);
     }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
index 0ec23d5..1cef4b4 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
@@ -14,15 +14,20 @@
 
 package com.google.gerrit.server.query;
 
+import static com.google.common.collect.ImmutableList.of;
 import static com.google.gerrit.server.query.Predicate.and;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-public class AndPredicateTest extends TestCase {
+public class AndPredicateTest {
   private static final class TestPredicate extends OperatorPredicate<String> {
     private TestPredicate(String name, String value) {
       super(name, value);
@@ -43,7 +48,7 @@
     return new TestPredicate(name, value);
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testChildren() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
@@ -53,7 +58,7 @@
     assertSame(b, n.getChild(1));
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testChildrenUnmodifiable() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
@@ -63,27 +68,27 @@
       n.getChildren().clear();
     } catch (RuntimeException e) {
     }
-    assertChildren("clear", n, list(a, b));
+    assertChildren("clear", n, of(a, b));
 
     try {
       n.getChildren().remove(0);
     } catch (RuntimeException e) {
     }
-    assertChildren("remove(0)", n, list(a, b));
+    assertChildren("remove(0)", n, of(a, b));
 
     try {
       n.getChildren().iterator().remove();
     } catch (RuntimeException e) {
     }
-    assertChildren("remove(0)", n, list(a, b));
+    assertChildren("remove(0)", n, of(a, b));
   }
 
   private static void assertChildren(String o, Predicate<String> p,
-      final List<Predicate<String>> l) {
+      List<? extends Predicate<String>> l) {
     assertEquals(o + " did not affect child", l, p.getChildren());
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testToString() {
     final TestPredicate a = f("q", "alice");
     final TestPredicate b = f("q", "bob");
@@ -92,7 +97,7 @@
     assertEquals("(q:alice q:bob q:charlie)", and(a, b, c).toString());
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testEquals() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
@@ -107,7 +112,7 @@
     assertFalse(and(a, c).equals(a));
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testHashCode() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
@@ -118,13 +123,13 @@
     assertFalse(and(a, c).hashCode() == and(a, b).hashCode());
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testCopy() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
     final TestPredicate c = f("author", "charlie");
-    final List<Predicate<String>> s2 = list(a, b);
-    final List<Predicate<String>> s3 = list(a, b, c);
+    final List<TestPredicate> s2 = of(a, b);
+    final List<TestPredicate> s3 = of(a, b, c);
     final Predicate<String> n2 = and(a, b);
 
     assertNotSame(n2, n2.copy(s2));
@@ -137,8 +142,4 @@
       assertEquals("Need at least two predicates", e.getMessage());
     }
   }
-
-  private static List<Predicate<String>> list(final Predicate<String>... predicates) {
-    return Arrays.asList(predicates);
-  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
index a37a336..e31caaf 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
@@ -14,11 +14,16 @@
 
 package com.google.gerrit.server.query;
 
-import junit.framework.TestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
 
 import java.util.Collections;
 
-public class FieldPredicateTest extends TestCase {
+public class FieldPredicateTest {
   private static final class TestPredicate extends OperatorPredicate<String> {
     private TestPredicate(String name, String value) {
       super(name, value);
@@ -39,12 +44,14 @@
     return new TestPredicate(name, value);
   }
 
+  @Test
   public void testToString() {
     assertEquals("author:bob", f("author", "bob").toString());
     assertEquals("author:\"\"", f("author", "").toString());
     assertEquals("owner:\"A U Thor\"", f("owner", "A U Thor").toString());
   }
 
+  @Test
   public void testEquals() {
     assertTrue(f("author", "bob").equals(f("author", "bob")));
     assertFalse(f("author", "bob").equals(f("author", "alice")));
@@ -52,11 +59,13 @@
     assertFalse(f("author", "bob").equals("author"));
   }
 
+  @Test
   public void testHashCode() {
     assertTrue(f("a", "bob").hashCode() == f("a", "bob").hashCode());
     assertFalse(f("a", "bob").hashCode() == f("a", "alice").hashCode());
   }
 
+  @Test
   public void testNameValue() {
     final String name = "author";
     final String value = "alice";
@@ -66,6 +75,7 @@
     assertEquals(0, f.getChildren().size());
   }
 
+  @Test
   public void testCopy() {
     final OperatorPredicate<String> f = f("author", "alice");
     assertSame(f, f.copy(Collections.<Predicate<String>> emptyList()));
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
index 90b9ca7..9df906c 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
@@ -16,13 +16,18 @@
 
 import static com.google.gerrit.server.query.Predicate.and;
 import static com.google.gerrit.server.query.Predicate.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
 import java.util.Collections;
 import java.util.List;
 
-public class NotPredicateTest extends TestCase {
+public class NotPredicateTest {
   private static final class TestPredicate extends OperatorPredicate<String> {
     private TestPredicate(String name, String value) {
       super(name, value);
@@ -43,6 +48,7 @@
     return new TestPredicate(name, value);
   }
 
+  @Test
   public void testNotNot() {
     final TestPredicate p = f("author", "bob");
     final Predicate<String> n = not(p);
@@ -51,6 +57,7 @@
     assertSame(p, not(n));
   }
 
+  @Test
   public void testChildren() {
     final TestPredicate p = f("author", "bob");
     final Predicate<String> n = not(p);
@@ -58,6 +65,7 @@
     assertSame(p, n.getChild(0));
   }
 
+  @Test
   public void testChildrenUnmodifiable() {
     final TestPredicate p = f("author", "bob");
     final Predicate<String> n = not(p);
@@ -87,10 +95,12 @@
     assertSame(o + " did not affect child", c, p.getChild(0));
   }
 
+  @Test
   public void testToString() {
     assertEquals("-author:bob", not(f("author", "bob")).toString());
   }
 
+  @Test
   public void testEquals() {
     assertTrue(not(f("author", "bob")).equals(not(f("author", "bob"))));
     assertFalse(not(f("author", "bob")).equals(not(f("author", "alice"))));
@@ -98,11 +108,13 @@
     assertFalse(not(f("author", "bob")).equals("author"));
   }
 
+  @Test
   public void testHashCode() {
     assertTrue(not(f("a", "b")).hashCode() == not(f("a", "b")).hashCode());
     assertFalse(not(f("a", "b")).hashCode() == not(f("a", "a")).hashCode());
   }
 
+  @Test
   @SuppressWarnings({"rawtypes", "unchecked"})
   public void testCopy() {
     final TestPredicate a = f("author", "alice");
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
index 7f3ce50..01b8588 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
@@ -14,15 +14,20 @@
 
 package com.google.gerrit.server.query;
 
+import static com.google.common.collect.ImmutableList.of;
 import static com.google.gerrit.server.query.Predicate.or;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-public class OrPredicateTest extends TestCase {
+public class OrPredicateTest {
   private static final class TestPredicate extends OperatorPredicate<String> {
     private TestPredicate(String name, String value) {
       super(name, value);
@@ -43,7 +48,7 @@
     return new TestPredicate(name, value);
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testChildren() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
@@ -53,7 +58,7 @@
     assertSame(b, n.getChild(1));
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testChildrenUnmodifiable() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
@@ -63,27 +68,27 @@
       n.getChildren().clear();
     } catch (RuntimeException e) {
     }
-    assertChildren("clear", n, list(a, b));
+    assertChildren("clear", n, of(a, b));
 
     try {
       n.getChildren().remove(0);
     } catch (RuntimeException e) {
     }
-    assertChildren("remove(0)", n, list(a, b));
+    assertChildren("remove(0)", n, of(a, b));
 
     try {
       n.getChildren().iterator().remove();
     } catch (RuntimeException e) {
     }
-    assertChildren("remove(0)", n, list(a, b));
+    assertChildren("remove(0)", n, of(a, b));
   }
 
   private static void assertChildren(String o, Predicate<String> p,
-      final List<Predicate<String>> l) {
+      List<? extends Predicate<String>> l) {
     assertEquals(o + " did not affect child", l, p.getChildren());
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testToString() {
     final TestPredicate a = f("q", "alice");
     final TestPredicate b = f("q", "bob");
@@ -92,7 +97,7 @@
     assertEquals("(q:alice OR q:bob OR q:charlie)", or(a, b, c).toString());
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testEquals() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
@@ -107,7 +112,7 @@
     assertFalse(or(a, c).equals(a));
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testHashCode() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
@@ -118,13 +123,13 @@
     assertFalse(or(a, c).hashCode() == or(a, b).hashCode());
   }
 
-  @SuppressWarnings("unchecked")
+  @Test
   public void testCopy() {
     final TestPredicate a = f("author", "alice");
     final TestPredicate b = f("author", "bob");
     final TestPredicate c = f("author", "charlie");
-    final List<Predicate<String>> s2 = list(a, b);
-    final List<Predicate<String>> s3 = list(a, b, c);
+    final List<TestPredicate> s2 = of(a, b);
+    final List<TestPredicate> s3 = of(a, b, c);
     final Predicate<String> n2 = or(a, b);
 
     assertNotSame(n2, n2.copy(s2));
@@ -137,8 +142,4 @@
       assertEquals("Need at least two predicates", e.getMessage());
     }
   }
-
-  private static <T> List<Predicate<T>> list(final Predicate<T>... predicates) {
-    return Arrays.asList(predicates);
-  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
index 9534d2b..0eca069 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
@@ -13,13 +13,13 @@
 // limitations under the License.
 
 package com.google.gerrit.server.query;
-
-
-import junit.framework.TestCase;
-
 import org.antlr.runtime.tree.Tree;
+import org.junit.Test;
 
-public class QueryParserTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+
+public class QueryParserTest {
+  @Test
   public void testProjectBare() throws QueryParseException {
     Tree r;
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractIndexQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractIndexQueryChangesTest.java
deleted file mode 100644
index 5dacd49..0000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractIndexQueryChangesTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.query.change;
-
-import static org.junit.Assert.assertTrue;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.change.ChangeInserter;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.change.PostReview;
-import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.project.ChangeControl;
-
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.List;
-
-@Ignore
-public abstract class AbstractIndexQueryChangesTest
-    extends AbstractQueryChangesTest {
-  @Test
-  public void byFileExact() throws Exception {
-    TestRepository<InMemoryRepository> repo = createProject("repo");
-    RevCommit commit = repo.parseBody(
-        repo.commit().message("one")
-        .add("file1", "contents1").add("file2", "contents2")
-        .create());
-    Change change = newChange(repo, commit, null, null, null).insert();
-
-    assertTrue(query("file:file").isEmpty());
-    assertResultEquals(change, queryOne("file:file1"));
-    assertResultEquals(change, queryOne("file:file2"));
-  }
-
-  @Test
-  public void byFileRegex() throws Exception {
-    TestRepository<InMemoryRepository> repo = createProject("repo");
-    RevCommit commit = repo.parseBody(
-        repo.commit().message("one")
-        .add("file1", "contents1").add("file2", "contents2")
-        .create());
-    Change change = newChange(repo, commit, null, null, null).insert();
-
-    assertTrue(query("file:file.*").isEmpty());
-    assertResultEquals(change, queryOne("file:^file.*"));
-  }
-
-  @Test
-  public void byComment() throws Exception {
-    TestRepository<InMemoryRepository> repo = createProject("repo");
-    ChangeInserter ins = newChange(repo, null, null, null, null);
-    Change change = ins.insert();
-    ChangeControl ctl = changeControlFactory.controlFor(change, user);
-
-    PostReview.Input input = new PostReview.Input();
-    input.message = "toplevel";
-    PostReview.Comment comment = new PostReview.Comment();
-    comment.line = 1;
-    comment.message = "inline";
-    input.comments = ImmutableMap.<String, List<PostReview.Comment>> of(
-        "Foo.java", ImmutableList.<PostReview.Comment> of(comment));
-    postReview.apply(new RevisionResource(
-        new ChangeResource(ctl), ins.getPatchSet()), input);
-
-    assertTrue(query("comment:foo").isEmpty());
-    assertResultEquals(change, queryOne("comment:toplevel"));
-    assertResultEquals(change, queryOne("comment:inline"));
-  }
-}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 1e8b87a..4342684 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -14,18 +14,20 @@
 
 package com.google.gerrit.server.query.change;
 
-import static java.util.concurrent.TimeUnit.DAYS;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.TimeUnit.HOURS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.google.common.base.Charsets;
 import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.hash.Hashing;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.Account;
@@ -33,17 +35,15 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
-import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.ChangesCollection;
 import com.google.gerrit.server.change.PostReview;
 import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.CreateProject;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.schema.SchemaCreator;
@@ -60,6 +60,7 @@
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.joda.time.DateTime;
 import org.joda.time.DateTimeUtils;
 import org.joda.time.DateTimeUtils.MillisProvider;
 import org.junit.After;
@@ -75,8 +76,8 @@
   private static final TopLevelResource TLR = TopLevelResource.INSTANCE;
 
   @Inject protected AccountManager accountManager;
-  @Inject protected ChangeControl.GenericFactory changeControlFactory;
   @Inject protected ChangeInserter.Factory changeFactory;
+  @Inject protected ChangesCollection changes;
   @Inject protected CreateProject.Factory projectFactory;
   @Inject protected IdentifiedUser.RequestFactory userFactory;
   @Inject protected InMemoryDatabase schemaFactory;
@@ -93,6 +94,8 @@
   protected CurrentUser user;
   protected volatile long clockStepMs;
 
+  private String systemTimeZone;
+
   protected abstract Injector createInjector();
 
   @Before
@@ -135,11 +138,11 @@
   }
 
   @Before
-  public void setMillisProvider() {
+  public void setTimeForTesting() {
+    systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
     clockStepMs = 1;
     final AtomicLong clockMs = new AtomicLong(
-        MILLISECONDS.convert(ChangeUtil.SORT_KEY_EPOCH_MINS, MINUTES)
-        + MILLISECONDS.convert(60, DAYS));
+        new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
 
     DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
       @Override
@@ -150,8 +153,9 @@
   }
 
   @After
-  public void resetMillisProvider() {
+  public void resetTime() {
     DateTimeUtils.setCurrentMillisSystem();
+    System.setProperty("user.timezone", systemTimeZone);
   }
 
   @Test
@@ -346,24 +350,38 @@
     RevCommit commit2 = repo.parseBody(repo.commit().message("two").create());
     Change change2 = newChange(repo, commit2, null, null, null).insert();
 
-    assertTrue(query("topic:foo").isEmpty());
+    assertTrue(query("message:foo").isEmpty());
     assertResultEquals(change1, queryOne("message:one"));
     assertResultEquals(change2, queryOne("message:two"));
   }
 
   @Test
+  public void fullTextWithNumbers() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    RevCommit commit1 =
+        repo.parseBody(repo.commit().message("12345 67890").create());
+    Change change1 = newChange(repo, commit1, null, null, null).insert();
+    RevCommit commit2 =
+        repo.parseBody(repo.commit().message("12346 67891").create());
+    Change change2 = newChange(repo, commit2, null, null, null).insert();
+
+    assertTrue(query("message:1234").isEmpty());
+    assertResultEquals(change1, queryOne("message:12345"));
+    assertResultEquals(change2, queryOne("message:12346"));
+  }
+
+  @Test
   public void byLabel() throws Exception {
     accountManager.authenticate(AuthRequest.forUser("anotheruser"));
     TestRepository<InMemoryRepository> repo = createProject("repo");
     ChangeInserter ins = newChange(repo, null, null, null, null);
     Change change = ins.insert();
-    ChangeControl ctl = changeControlFactory.controlFor(change, user);
 
-    PostReview.Input input = new PostReview.Input();
+    ReviewInput input = new ReviewInput();
     input.message = "toplevel";
     input.labels = ImmutableMap.<String, Short> of("Code-Review", (short) 1);
     postReview.apply(new RevisionResource(
-        new ChangeResource(ctl), ins.getPatchSet()), input);
+        changes.parse(change.getId()), ins.getPatchSet()), input);
 
     assertTrue(query("label:Code-Review=-2").isEmpty());
     assertTrue(query("label:Code-Review-2").isEmpty());
@@ -377,19 +395,17 @@
     assertTrue(query("label:Code-Review=2").isEmpty());
     assertTrue(query("label:Code-Review+2").isEmpty());
 
-    // TODO(dborowitz): > and < are broken at head.
-
     assertResultEquals(change, queryOne("label:Code-Review>=0"));
-    //assertResultEquals(change, queryOne("label:Code-Review>0"));
+    assertResultEquals(change, queryOne("label:Code-Review>0"));
     assertResultEquals(change, queryOne("label:Code-Review>=1"));
-    //assertTrue(query("label:Code-Review>1").isEmpty());
+    assertTrue(query("label:Code-Review>1").isEmpty());
     assertTrue(query("label:Code-Review>=2").isEmpty());
 
     assertResultEquals(change, queryOne("label: Code-Review<=2"));
-    //assertResultEquals(change, queryOne("label: Code-Review<2"));
+    assertResultEquals(change, queryOne("label: Code-Review<2"));
     assertResultEquals(change, queryOne("label: Code-Review<=1"));
-    //assertTrue(query("label: Code-Review<1").isEmpty());
-    assertTrue(query("label: Code-Review<=0").isEmpty());
+    assertTrue(query("label:Code-Review<1").isEmpty());
+    assertTrue(query("label:Code-Review<=0").isEmpty());
 
     assertTrue(query("label:Code-Review=+1,anotheruser").isEmpty());
     assertResultEquals(change, queryOne("label:Code-Review=+1,user"));
@@ -416,56 +432,108 @@
   }
 
   @Test
-  public void pagination() throws Exception {
+  public void start() throws Exception {
     TestRepository<InMemoryRepository> repo = createProject("repo");
     List<Change> changes = Lists.newArrayList();
-    for (int i = 0; i < 5; i++) {
+    for (int i = 0; i < 2; i++) {
       changes.add(newChange(repo, null, null, null, null).insert());
     }
 
-    // Page forward and back through 3 pages of results.
+    QueryChanges q;
+    List<ChangeInfo> results;
+    results = query("status:new");
+    assertEquals(2, results.size());
+    assertResultEquals(changes.get(1), results.get(0));
+    assertResultEquals(changes.get(0), results.get(1));
+
+    q = newQuery("status:new");
+    q.setStart(1);
+    results = query(q);
+    assertEquals(1, results.size());
+    assertResultEquals(changes.get(0), results.get(0));
+
+    q = newQuery("status:new");
+    q.setStart(2);
+    results = query(q);
+    assertEquals(0, results.size());
+
+    q = newQuery("status:new");
+    q.setStart(3);
+    results = query(q);
+    assertEquals(0, results.size());
+  }
+
+  @Test
+  public void startWithLimit() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    List<Change> changes = Lists.newArrayList();
+    for (int i = 0; i < 3; i++) {
+      changes.add(newChange(repo, null, null, null, null).insert());
+    }
+
     QueryChanges q;
     List<ChangeInfo> results;
     results = query("status:new limit:2");
     assertEquals(2, results.size());
-    assertResultEquals(changes.get(4), results.get(0));
-    assertResultEquals(changes.get(3), results.get(1));
-
-    q = newQuery("status:new limit:2");
-    q.setSortKeyBefore(results.get(1)._sortkey);
-    results = query(q);
-    assertEquals(2, results.size());
     assertResultEquals(changes.get(2), results.get(0));
     assertResultEquals(changes.get(1), results.get(1));
 
     q = newQuery("status:new limit:2");
-    q.setSortKeyBefore(results.get(1)._sortkey);
+    q.setStart(1);
+    results = query(q);
+    assertEquals(2, results.size());
+    assertResultEquals(changes.get(1), results.get(0));
+    assertResultEquals(changes.get(0), results.get(1));
+
+    q = newQuery("status:new limit:2");
+    q.setStart(2);
     results = query(q);
     assertEquals(1, results.size());
     assertResultEquals(changes.get(0), results.get(0));
 
     q = newQuery("status:new limit:2");
-    q.setSortKeyAfter(results.get(0)._sortkey);
+    q.setStart(3);
     results = query(q);
-    assertEquals(2, results.size());
-    assertResultEquals(changes.get(2), results.get(0));
-    assertResultEquals(changes.get(1), results.get(1));
-
-    q = newQuery("status:new limit:2");
-    q.setSortKeyAfter(results.get(0)._sortkey);
-    results = query(q);
-    assertEquals(2, results.size());
-    assertResultEquals(changes.get(4), results.get(0));
-    assertResultEquals(changes.get(3), results.get(1));
+    assertEquals(0, results.size());
   }
 
   @Test
-  public void sortKeyWithMinuteResolution() throws Exception {
+  public void updateOrder() throws Exception {
+    clockStepMs = MILLISECONDS.convert(2, MINUTES);
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    List<ChangeInserter> inserters = Lists.newArrayList();
+    List<Change> changes = Lists.newArrayList();
+    for (int i = 0; i < 5; i++) {
+      inserters.add(newChange(repo, null, null, null, null));
+      changes.add(inserters.get(i).insert());
+    }
+
+    for (int i : ImmutableList.of(2, 0, 1, 4, 3)) {
+      ReviewInput input = new ReviewInput();
+      input.message = "modifying " + i;
+      postReview.apply(
+          new RevisionResource(
+            this.changes.parse(changes.get(i).getId()),
+            inserters.get(i).getPatchSet()),
+          input);
+      changes.set(i, db.changes().get(changes.get(i).getId()));
+    }
+
+    List<ChangeInfo> results = query("status:new");
+    assertEquals(5, results.size());
+    assertResultEquals(changes.get(3), results.get(0));
+    assertResultEquals(changes.get(4), results.get(1));
+    assertResultEquals(changes.get(1), results.get(2));
+    assertResultEquals(changes.get(0), results.get(3));
+    assertResultEquals(changes.get(2), results.get(4));
+  }
+
+  @Test
+  public void updatedOrderWithMinuteResolution() throws Exception {
     clockStepMs = MILLISECONDS.convert(2, MINUTES);
     TestRepository<InMemoryRepository> repo = createProject("repo");
     ChangeInserter ins1 = newChange(repo, null, null, null, null);
     Change change1 = ins1.insert();
-    ChangeControl ctl1 = changeControlFactory.controlFor(change1, user);
     Change change2 = newChange(repo, null, null, null, null).insert();
 
     assertTrue(lastUpdatedMs(change1) < lastUpdatedMs(change2));
@@ -476,10 +544,10 @@
     assertResultEquals(change2, results.get(0));
     assertResultEquals(change1, results.get(1));
 
-    PostReview.Input input = new PostReview.Input();
+    ReviewInput input = new ReviewInput();
     input.message = "toplevel";
     postReview.apply(new RevisionResource(
-        new ChangeResource(ctl1), ins1.getPatchSet()), input);
+        changes.parse(change1.getId()), ins1.getPatchSet()), input);
     change1 = db.changes().get(change1.getId());
 
     assertTrue(lastUpdatedMs(change1) > lastUpdatedMs(change2));
@@ -494,11 +562,10 @@
   }
 
   @Test
-  public void sortKeyWithSubMinuteResolution() throws Exception {
+  public void updatedOrderWithSubMinuteResolution() throws Exception {
     TestRepository<InMemoryRepository> repo = createProject("repo");
     ChangeInserter ins1 = newChange(repo, null, null, null, null);
     Change change1 = ins1.insert();
-    ChangeControl ctl1 = changeControlFactory.controlFor(change1, user);
     Change change2 = newChange(repo, null, null, null, null).insert();
 
     assertTrue(lastUpdatedMs(change1) < lastUpdatedMs(change2));
@@ -509,10 +576,10 @@
     assertResultEquals(change2, results.get(0));
     assertResultEquals(change1, results.get(1));
 
-    PostReview.Input input = new PostReview.Input();
+    ReviewInput input = new ReviewInput();
     input.message = "toplevel";
     postReview.apply(new RevisionResource(
-        new ChangeResource(ctl1), ins1.getPatchSet()), input);
+        changes.parse(change1.getId()), ins1.getPatchSet()), input);
     change1 = db.changes().get(change1.getId());
 
     assertTrue(lastUpdatedMs(change1) > lastUpdatedMs(change2));
@@ -521,23 +588,9 @@
 
     results = query("status:new");
     assertEquals(2, results.size());
-    // Same order as before change1 was modified.
-    assertResultEquals(change2, results.get(0));
-    assertResultEquals(change1, results.get(1));
-  }
-
-  @Test
-  public void sortKeyBreaksTiesOnChangeId() throws Exception {
-    clockStepMs = 0;
-    TestRepository<InMemoryRepository> repo = createProject("repo");
-    Change change1 = newChange(repo, null, null, null, null).insert();
-    Change change2 = newChange(repo, null, null, null, null).insert();
-    assertEquals(change1.getLastUpdatedOn(), change2.getLastUpdatedOn());
-
-    List<ChangeInfo> results = query("status:new");
-    assertEquals(2, results.size());
-    assertResultEquals(change2, results.get(0));
-    assertResultEquals(change1, results.get(1));
+    // change1 moved to the top.
+    assertResultEquals(change1, results.get(0));
+    assertResultEquals(change2, results.get(1));
   }
 
   @Test
@@ -550,7 +603,7 @@
       newChange(repo, null, null, user2, null).insert();
     }
 
-    assertResultEquals(change, queryOne("status:new ownerin:Administrators"));
+    //assertResultEquals(change, queryOne("status:new ownerin:Administrators"));
     assertResultEquals(change,
         queryOne("status:new ownerin:Administrators limit:2"));
   }
@@ -568,6 +621,170 @@
     assertTrue(query("status:new ownerin:Administrators limit:2").isEmpty());
   }
 
+  @Test
+  public void byFileExact() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    RevCommit commit = repo.parseBody(
+        repo.commit().message("one")
+        .add("dir/file1", "contents1").add("dir/file2", "contents2")
+        .create());
+    Change change = newChange(repo, commit, null, null, null).insert();
+
+    assertTrue(query("file:file").isEmpty());
+    assertResultEquals(change, queryOne("file:dir"));
+    assertResultEquals(change, queryOne("file:file1"));
+    assertResultEquals(change, queryOne("file:file2"));
+    assertResultEquals(change, queryOne("file:dir/file1"));
+    assertResultEquals(change, queryOne("file:dir/file2"));
+  }
+
+  @Test
+  public void byFileRegex() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    RevCommit commit = repo.parseBody(
+        repo.commit().message("one")
+        .add("dir/file1", "contents1").add("dir/file2", "contents2")
+        .create());
+    Change change = newChange(repo, commit, null, null, null).insert();
+
+    assertTrue(query("file:.*file.*").isEmpty());
+    assertTrue(query("file:^file.*").isEmpty()); // Whole path only.
+    assertResultEquals(change, queryOne("file:^dir.file.*"));
+  }
+
+  @Test
+  public void byPathExact() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    RevCommit commit = repo.parseBody(
+        repo.commit().message("one")
+        .add("dir/file1", "contents1").add("dir/file2", "contents2")
+        .create());
+    Change change = newChange(repo, commit, null, null, null).insert();
+
+    assertTrue(query("path:file").isEmpty());
+    assertTrue(query("path:dir").isEmpty());
+    assertTrue(query("path:file1").isEmpty());
+    assertTrue(query("path:file2").isEmpty());
+    assertResultEquals(change, queryOne("path:dir/file1"));
+    assertResultEquals(change, queryOne("path:dir/file2"));
+  }
+
+  @Test
+  public void byPathRegex() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    RevCommit commit = repo.parseBody(
+        repo.commit().message("one")
+        .add("dir/file1", "contents1").add("dir/file2", "contents2")
+        .create());
+    Change change = newChange(repo, commit, null, null, null).insert();
+
+    assertTrue(query("path:.*file.*").isEmpty());
+    assertResultEquals(change, queryOne("path:^dir.file.*"));
+  }
+
+  @Test
+  public void byComment() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    ChangeInserter ins = newChange(repo, null, null, null, null);
+    Change change = ins.insert();
+
+    ReviewInput input = new ReviewInput();
+    input.message = "toplevel";
+    ReviewInput.Comment comment = new ReviewInput.Comment();
+    comment.line = 1;
+    comment.message = "inline";
+    input.comments = ImmutableMap.<String, List<ReviewInput.Comment>> of(
+        "Foo.java", ImmutableList.<ReviewInput.Comment> of(comment));
+    postReview.apply(new RevisionResource(
+        changes.parse(change.getId()), ins.getPatchSet()), input);
+
+    assertTrue(query("comment:foo").isEmpty());
+    assertResultEquals(change, queryOne("comment:toplevel"));
+    assertResultEquals(change, queryOne("comment:inline"));
+  }
+
+  @Test
+  public void byAge() throws Exception {
+    long thirtyHours = MILLISECONDS.convert(30, HOURS);
+    clockStepMs = thirtyHours;
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    Change change1 = newChange(repo, null, null, null, null).insert();
+    Change change2 = newChange(repo, null, null, null, null).insert();
+    clockStepMs = 0; // Queried by AgePredicate constructor.
+    long now = TimeUtil.nowMs();
+    assertEquals(thirtyHours, lastUpdatedMs(change2) - lastUpdatedMs(change1));
+    assertEquals(thirtyHours, now - lastUpdatedMs(change2));
+    assertEquals(now, TimeUtil.nowMs());
+
+    assertTrue(query("-age:1d").isEmpty());
+    assertTrue(query("-age:" + (30*60-1) + "m").isEmpty());
+    assertResultEquals(change2, queryOne("-age:2d"));
+
+    List<ChangeInfo> results;
+    results = query("-age:3d");
+    assertEquals(2, results.size());
+    assertResultEquals(change2, results.get(0));
+    assertResultEquals(change1, results.get(1));
+
+    assertTrue(query("age:3d").isEmpty());
+    assertResultEquals(change1, queryOne("age:2d"));
+
+    results = query("age:1d");
+    assertEquals(2, results.size());
+    assertResultEquals(change2, results.get(0));
+    assertResultEquals(change1, results.get(1));
+  }
+
+  @Test
+  public void byBefore() throws Exception {
+    clockStepMs = MILLISECONDS.convert(30, HOURS);
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    Change change1 = newChange(repo, null, null, null, null).insert();
+    Change change2 = newChange(repo, null, null, null, null).insert();
+    clockStepMs = 0;
+
+    assertTrue(query("before:2009-09-29").isEmpty());
+    assertTrue(query("before:2009-09-30").isEmpty());
+    assertTrue(query("before:\"2009-09-30 16:59:00 -0400\"").isEmpty());
+    assertTrue(query("before:\"2009-09-30 20:59:00 -0000\"").isEmpty());
+    assertTrue(query("before:\"2009-09-30 20:59:00\"").isEmpty());
+    assertResultEquals(change1,
+        queryOne("before:\"2009-09-30 17:02:00 -0400\""));
+    assertResultEquals(change1,
+        queryOne("before:\"2009-10-01 21:02:00 -0000\""));
+    assertResultEquals(change1,
+        queryOne("before:\"2009-10-01 21:02:00\""));
+    assertResultEquals(change1, queryOne("before:2009-10-01"));
+
+    List<ChangeInfo> results;
+    results = query("before:2009-10-03");
+    assertEquals(2, results.size());
+    assertResultEquals(change2, results.get(0));
+    assertResultEquals(change1, results.get(1));
+  }
+
+  @Test
+  public void byAfter() throws Exception {
+    clockStepMs = MILLISECONDS.convert(30, HOURS);
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    Change change1 = newChange(repo, null, null, null, null).insert();
+    Change change2 = newChange(repo, null, null, null, null).insert();
+    clockStepMs = 0;
+
+    assertTrue(query("after:2009-10-03").isEmpty());
+    assertResultEquals(change2,
+        queryOne("after:\"2009-10-01 20:59:59 -0400\""));
+    assertResultEquals(change2,
+        queryOne("after:\"2009-10-01 20:59:59 -0000\""));
+    assertResultEquals(change2, queryOne("after:2009-10-01"));
+
+    List<ChangeInfo> results;
+    results = query("after:2009-09-30");
+    assertEquals(2, results.size());
+    assertResultEquals(change2, results.get(0));
+    assertResultEquals(change1, results.get(1));
+  }
+
   protected ChangeInserter newChange(
       TestRepository<InMemoryRepository> repo,
       @Nullable RevCommit commit, @Nullable String key, @Nullable Integer owner,
@@ -587,10 +804,10 @@
     if (key == null) {
       key = "I" + Hashing.sha1().newHasher()
           .putInt(id.get())
-          .putString(project.get(), Charsets.UTF_8)
-          .putString(commit.name(), Charsets.UTF_8)
+          .putString(project.get(), UTF_8)
+          .putString(commit.name(), UTF_8)
           .putInt(ownerId.get())
-          .putString(branch, Charsets.UTF_8)
+          .putString(branch, UTF_8)
           .hash()
           .toString();
     }
@@ -657,7 +874,7 @@
     return results.get(0);
   }
 
-  private static long lastUpdatedMs(Change c) {
+  protected static long lastUpdatedMs(Change c) {
     return c.getLastUpdatedOn().getTime();
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
index ab12986..370dd9d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
@@ -18,14 +18,8 @@
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 
-import org.eclipse.jgit.lib.Config;
-
-public class LuceneQueryChangesTest extends AbstractIndexQueryChangesTest {
+public class LuceneQueryChangesTest extends AbstractQueryChangesTest {
   protected Injector createInjector() {
-    Config cfg = InMemoryModule.newDefaultConfig();
-    cfg.setString("index", null, "type", "lucene");
-    cfg.setBoolean("index", "lucene", "testInmemory", true);
-    cfg.setInt("index", "lucene", "testVersion", 4);
-    return Guice.createInjector(new InMemoryModule(cfg));
+    return Guice.createInjector(new InMemoryModule());
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
new file mode 100644
index 0000000..948626e
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
@@ -0,0 +1,145 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.change.ChangeInserter;
+import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
+import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.testutil.InMemoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+import java.util.List;
+
+public class LuceneQueryChangesV7Test extends AbstractQueryChangesTest {
+  protected Injector createInjector() {
+    Config cfg = InMemoryModule.newDefaultConfig();
+    cfg.setInt("index", "lucene", "testVersion", 7);
+    return Guice.createInjector(new InMemoryModule(cfg));
+  }
+
+  @Test
+  public void pagination() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    List<Change> changes = Lists.newArrayList();
+    for (int i = 0; i < 5; i++) {
+      changes.add(newChange(repo, null, null, null, null).insert());
+    }
+
+    // Page forward and back through 3 pages of results.
+    QueryChanges q;
+    List<ChangeInfo> results;
+    results = query("status:new limit:2");
+    assertEquals(2, results.size());
+    assertResultEquals(changes.get(4), results.get(0));
+    assertResultEquals(changes.get(3), results.get(1));
+
+    q = newQuery("status:new limit:2");
+    q.setSortKeyBefore(results.get(1)._sortkey);
+    results = query(q);
+    assertEquals(2, results.size());
+    assertResultEquals(changes.get(2), results.get(0));
+    assertResultEquals(changes.get(1), results.get(1));
+
+    q = newQuery("status:new limit:2");
+    q.setSortKeyBefore(results.get(1)._sortkey);
+    results = query(q);
+    assertEquals(1, results.size());
+    assertResultEquals(changes.get(0), results.get(0));
+
+    q = newQuery("status:new limit:2");
+    q.setSortKeyAfter(results.get(0)._sortkey);
+    results = query(q);
+    assertEquals(2, results.size());
+    assertResultEquals(changes.get(2), results.get(0));
+    assertResultEquals(changes.get(1), results.get(1));
+
+    q = newQuery("status:new limit:2");
+    q.setSortKeyAfter(results.get(0)._sortkey);
+    results = query(q);
+    assertEquals(2, results.size());
+    assertResultEquals(changes.get(4), results.get(0));
+    assertResultEquals(changes.get(3), results.get(1));
+  }
+
+  @Override
+  @Test
+  public void updatedOrderWithSubMinuteResolution() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    ChangeInserter ins1 = newChange(repo, null, null, null, null);
+    Change change1 = ins1.insert();
+    Change change2 = newChange(repo, null, null, null, null).insert();
+
+    assertTrue(lastUpdatedMs(change1) < lastUpdatedMs(change2));
+
+    List<ChangeInfo> results;
+    results = query("status:new");
+    assertEquals(2, results.size());
+    assertResultEquals(change2, results.get(0));
+    assertResultEquals(change1, results.get(1));
+
+    ReviewInput input = new ReviewInput();
+    input.message = "toplevel";
+    postReview.apply(new RevisionResource(
+        changes.parse(change1.getId()), ins1.getPatchSet()), input);
+    change1 = db.changes().get(change1.getId());
+
+    assertTrue(lastUpdatedMs(change1) > lastUpdatedMs(change2));
+    assertTrue(lastUpdatedMs(change1) - lastUpdatedMs(change2)
+        < MILLISECONDS.convert(1, MINUTES));
+
+    results = query("status:new");
+    assertEquals(2, results.size());
+    // Same order as before change1 was modified.
+    assertResultEquals(change2, results.get(0));
+    assertResultEquals(change1, results.get(1));
+  }
+
+  @Test
+  public void sortKeyBreaksTiesOnChangeId() throws Exception {
+    clockStepMs = 0;
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+    ChangeInserter ins1 = newChange(repo, null, null, null, null);
+    Change change1 = ins1.insert();
+    Change change2 = newChange(repo, null, null, null, null).insert();
+
+    ReviewInput input = new ReviewInput();
+    input.message = "toplevel";
+    postReview.apply(new RevisionResource(
+        changes.parse(change1.getId()), ins1.getPatchSet()), input);
+    change1 = db.changes().get(change1.getId());
+
+    assertEquals(change1.getLastUpdatedOn(), change2.getLastUpdatedOn());
+
+    List<ChangeInfo> results = query("status:new");
+    assertEquals(2, results.size());
+    // Updated at the same time, 2 > 1.
+    assertResultEquals(change2, results.get(0));
+    assertResultEquals(change1, results.get(1));
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexFilePredicateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexPathPredicateTest.java
similarity index 78%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexFilePredicateTest.java
rename to gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexPathPredicateTest.java
index 1500272..dd7ac56 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexFilePredicateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/RegexPathPredicateTest.java
@@ -14,16 +14,20 @@
 
 package com.google.gerrit.server.query.change;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwtorm.server.OrmException;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
 import java.util.Arrays;
 
-public class RegexFilePredicateTest extends TestCase {
+public class RegexPathPredicateTest {
+  @Test
   public void testPrefixOnlyOptimization() throws OrmException {
-    RegexFilePredicate p = predicate("^a/b/.*");
+    RegexPathPredicate p = predicate("^a/b/.*");
     assertTrue(p.match(change("a/b/source.c")));
     assertFalse(p.match(change("source.c")));
 
@@ -31,8 +35,9 @@
     assertFalse(p.match(change("a/bb/source.c")));
   }
 
+  @Test
   public void testPrefixReducesSearchSpace() throws OrmException {
-    RegexFilePredicate p = predicate("^a/b/.*\\.[ch]");
+    RegexPathPredicate p = predicate("^a/b/.*\\.[ch]");
     assertTrue(p.match(change("a/b/source.c")));
     assertFalse(p.match(change("a/b/source.res")));
     assertFalse(p.match(change("source.res")));
@@ -40,20 +45,23 @@
     assertTrue(p.match(change("a/b/a.a", "a/b/a.d", "a/b/a.h")));
   }
 
+  @Test
   public void testFileExtension_Constant() throws OrmException {
-    RegexFilePredicate p = predicate("^.*\\.res");
+    RegexPathPredicate p = predicate("^.*\\.res");
     assertTrue(p.match(change("test.res")));
     assertTrue(p.match(change("foo/bar/test.res")));
     assertFalse(p.match(change("test.res.bar")));
   }
 
+  @Test
   public void testFileExtension_CharacterGroup() throws OrmException {
-    RegexFilePredicate p = predicate("^.*\\.[ch]");
+    RegexPathPredicate p = predicate("^.*\\.[ch]");
     assertTrue(p.match(change("test.c")));
     assertTrue(p.match(change("test.h")));
     assertFalse(p.match(change("test.C")));
   }
 
+  @Test
   public void testEndOfString() throws OrmException {
     assertTrue(predicate("^a$").match(change("a")));
     assertFalse(predicate("^a$").match(change("a$")));
@@ -62,20 +70,21 @@
     assertTrue(predicate("^a\\$").match(change("a$")));
   }
 
+  @Test
   public void testExactMatch() throws OrmException {
-    RegexFilePredicate p = predicate("^foo.c");
+    RegexPathPredicate p = predicate("^foo.c");
     assertTrue(p.match(change("foo.c")));
     assertFalse(p.match(change("foo.cc")));
     assertFalse(p.match(change("bar.c")));
   }
 
-  private static RegexFilePredicate predicate(String pattern) {
-    return new RegexFilePredicate(null, null, pattern);
+  private static RegexPathPredicate predicate(String pattern) {
+    return new RegexPathPredicate(ChangeQueryBuilder.FIELD_PATH, pattern);
   }
 
   private static ChangeData change(String... files) {
     Arrays.sort(files);
-    ChangeData cd = new ChangeData(new Change.Id(1));
+    ChangeData cd = ChangeData.createForTest(new Change.Id(1));
     cd.setCurrentFilePaths(Arrays.asList(files));
     return cd;
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/SqlQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/SqlQueryChangesTest.java
deleted file mode 100644
index f235efc..0000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/SqlQueryChangesTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.query.change;
-
-import com.google.gerrit.testutil.InMemoryModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-
-import org.eclipse.jgit.lib.Config;
-
-public class SqlQueryChangesTest extends AbstractQueryChangesTest {
-  protected Injector createInjector() {
-    Config cfg = InMemoryModule.newDefaultConfig();
-    cfg.setString("index", null, "type", "sql");
-    return Guice.createInjector(new InMemoryModule(cfg));
-  }
-}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
index e58266f..4756390 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
@@ -14,6 +14,11 @@
 
 package com.google.gerrit.server.schema;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -29,9 +34,10 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
-import junit.framework.TestCase;
-
 import org.eclipse.jgit.lib.Repository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.io.File;
 import java.io.IOException;
@@ -40,7 +46,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class SchemaCreatorTest extends TestCase {
+public class SchemaCreatorTest {
   @Inject
   private AllProjectsName allProjects;
 
@@ -50,18 +56,17 @@
   @Inject
   private InMemoryDatabase db;
 
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
+  @Before
+  public void setUp() throws Exception {
     new InMemoryModule().inject(this);
   }
 
-  @Override
-  protected void tearDown() throws Exception {
+  @After
+  public void tearDown() throws Exception {
     InMemoryDatabase.drop(db);
-    super.tearDown();
   }
 
+  @Test
   public void testGetCauses_CreateSchema() throws OrmException, SQLException,
       IOException {
     // Initially the schema should be empty.
@@ -109,6 +114,7 @@
     }
   }
 
+  @Test
   public void testCreateSchema_LabelTypes() throws Exception {
     List<String> labels = Lists.newArrayList();
     for (LabelType label : getLabelTypes().getLabelTypes()) {
@@ -117,6 +123,7 @@
     assertEquals(ImmutableList.of("Code-Review"), labels);
   }
 
+  @Test
   public void testCreateSchema_Label_CodeReview() throws Exception {
     LabelType codeReview = getLabelTypes().byLabel("Code-Review");
     assertNotNull(codeReview);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
index 5039cc2..e6b7a3f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
@@ -34,10 +34,11 @@
 import com.google.inject.Guice;
 import com.google.inject.TypeLiteral;
 
-import junit.framework.TestCase;
-
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -45,21 +46,22 @@
 import java.util.List;
 import java.util.UUID;
 
-public class SchemaUpdaterTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+
+public class SchemaUpdaterTest {
   private InMemoryDatabase db;
 
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
+  @Before
+  public void setUp() throws Exception {
     db = InMemoryDatabase.newDatabase();
   }
 
-  @Override
-  protected void tearDown() throws Exception {
+  @After
+  public void tearDown() throws Exception {
     InMemoryDatabase.drop(db);
-    super.tearDown();
   }
 
+  @Test
   public void testUpdate() throws OrmException, FileNotFoundException,
       IOException {
     db.create();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java
index 1f50fa2..5546410 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/IdGeneratorTest.java
@@ -14,11 +14,15 @@
 
 package com.google.gerrit.server.util;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
 import java.util.HashSet;
 
-public class IdGeneratorTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class IdGeneratorTest {
+  @Test
   public void test1234() {
     final HashSet<Integer> seen = new HashSet<Integer>();
     for (int i = 0; i < 1 << 16; i++) {
@@ -32,6 +36,7 @@
     assertEquals(0x0b966b11, IdGenerator.unmix(IdGenerator.mix(0x0b966b11)));
   }
 
+  @Test
   public void testFormat() {
     assertEquals("0000000f", IdGenerator.format(0xf));
     assertEquals("801234ab", IdGenerator.format(0x801234ab));
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
new file mode 100644
index 0000000..0ed0ba8
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/LabelVoteTest.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.util;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class LabelVoteTest {
+  @Test
+  public void parse() {
+    LabelVote l;
+    l = LabelVote.parse("Code-Review-2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) -2, l.getValue());
+    l = LabelVote.parse("Code-Review-1");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) -1, l.getValue());
+    l = LabelVote.parse("-Code-Review");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 0, l.getValue());
+    l = LabelVote.parse("Code-Review");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 1, l.getValue());
+    l = LabelVote.parse("Code-Review+1");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 1, l.getValue());
+    l = LabelVote.parse("Code-Review+2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 2, l.getValue());
+  }
+
+  @Test
+  public void format() {
+    assertEquals("Code-Review-2", LabelVote.parse("Code-Review-2").format());
+    assertEquals("Code-Review-1", LabelVote.parse("Code-Review-1").format());
+    assertEquals("-Code-Review", LabelVote.parse("-Code-Review").format());
+    assertEquals("Code-Review+1", LabelVote.parse("Code-Review+1").format());
+    assertEquals("Code-Review+2", LabelVote.parse("Code-Review+2").format());
+  }
+
+  @Test
+  public void parseWithEquals() {
+    LabelVote l;
+    l = LabelVote.parseWithEquals("Code-Review=-2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) -2, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=-1");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) -1, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=0");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 0, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=1");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 1, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=+1");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 1, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 2, l.getValue());
+    l = LabelVote.parseWithEquals("Code-Review=+2");
+    assertEquals("Code-Review", l.getLabel());
+    assertEquals((short) 2, l.getValue());
+  }
+
+  @Test
+  public void formatWithEquals() {
+    assertEquals("Code-Review=-2",
+        LabelVote.parseWithEquals("Code-Review=-2").formatWithEquals());
+    assertEquals("Code-Review=-1",
+        LabelVote.parseWithEquals("Code-Review=-1").formatWithEquals());
+    assertEquals("Code-Review=0",
+        LabelVote.parseWithEquals("Code-Review=0").formatWithEquals());
+    assertEquals("Code-Review=+1",
+        LabelVote.parseWithEquals("Code-Review=+1").formatWithEquals());
+    assertEquals("Code-Review=+2",
+        LabelVote.parseWithEquals("Code-Review=+2").formatWithEquals());
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java
index 9e66046..5ce8882 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SocketUtilTest.java
@@ -20,8 +20,12 @@
 import static com.google.gerrit.server.util.SocketUtil.resolve;
 import static java.net.InetAddress.getByName;
 import static java.net.InetSocketAddress.createUnresolved;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import junit.framework.TestCase;
+import org.junit.Test;
 
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -29,7 +33,8 @@
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 
-public class SocketUtilTest extends TestCase {
+public class SocketUtilTest {
+  @Test
   public void testIsIPv6() throws UnknownHostException {
     final InetAddress ipv6 = getByName("1:2:3:4:5:6:7:8");
     assertTrue(ipv6 instanceof Inet6Address);
@@ -40,12 +45,14 @@
     assertFalse(isIPv6(ipv4));
   }
 
+  @Test
   public void testHostname() {
     assertEquals("*", hostname(new InetSocketAddress(80)));
     assertEquals("localhost", hostname(new InetSocketAddress("localhost", 80)));
     assertEquals("foo", hostname(createUnresolved("foo", 80)));
   }
 
+  @Test
   public void testFormat() throws UnknownHostException {
     assertEquals("*:1234", SocketUtil.format(new InetSocketAddress(1234), 80));
     assertEquals("*", SocketUtil.format(new InetSocketAddress(80), 80));
@@ -64,6 +71,7 @@
         SocketUtil. format(new InetSocketAddress("localhost", 80), 80));
   }
 
+  @Test
   public void testParse() {
     assertEquals(new InetSocketAddress(1234), parse("*:1234", 80));
     assertEquals(new InetSocketAddress(80), parse("*", 80));
@@ -100,6 +108,7 @@
     }
   }
 
+  @Test
   public void testResolve() throws UnknownHostException {
     assertEquals(new InetSocketAddress(1234), resolve("*:1234", 80));
     assertEquals(new InetSocketAddress(80), resolve("*", 80));
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
index 299a245..edd79241 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
@@ -200,8 +200,9 @@
     expect(bbc.getSubsections("submodule"))
         .andReturn(sectionsToReturn.keySet());
 
-    for (final String id : sectionsToReturn.keySet()) {
-      final SubmoduleSection section = sectionsToReturn.get(id);
+    for (Map.Entry<String, SubmoduleSection> entry : sectionsToReturn.entrySet()) {
+      String id = entry.getKey();
+      final SubmoduleSection section = entry.getValue();
       expect(bbc.getString("submodule", id, "url")).andReturn(section.getUrl());
       expect(bbc.getString("submodule", id, "path")).andReturn(
           section.getPath());
@@ -209,7 +210,7 @@
           section.getBranch());
 
       if (THIS_SERVER.equals(new URI(section.getUrl()).getHost())) {
-        String projectNameCandidate = null;
+        String projectNameCandidate;
         final String urlExtractedPath = new URI(section.getUrl()).getPath();
         int fromIndex = urlExtractedPath.length() - 1;
         while (fromIndex > 0) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java
new file mode 100644
index 0000000..1da3c30
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java
@@ -0,0 +1,186 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.testutil;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+
+import org.junit.runner.Runner;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Suite;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+/**
+ * Suite to run tests with different {@code gerrit.config} values.
+ * <p>
+ * For each {@link Config} method in the class and base classes, a new group of
+ * tests is created with the {@link Parameter} field set to the config.
+ *
+ * <pre>
+ * @RunWith(ConfigSuite.class)
+ * public abstract class MyAbstractTest {
+ *   @ConfigSuite.Parameter
+ *   protected Config cfg;
+ *
+ *   @ConfigSuite.Config
+ *   public static Config firstConfig() {
+ *     Config cfg = new Config();
+ *     cfg.setString("gerrit", null, "testValue", "a");
+ *   }
+ * }
+ *
+ * public class MyTest {
+ *   @ConfigSuite.Config
+ *   public static Config secondConfig() {
+ *     Config cfg = new Config();
+ *     cfg.setString("gerrit", null, "testValue", "b");
+ *   }
+ *
+ *   @Test
+ *   public void myTest() {
+ *     // Test using cfg.
+ *   }
+ * }
+ * </pre>
+ *
+ * This creates a suite of tests with three groups:
+ * <ul>
+ *   <li><strong>default</strong>: {@code MyTest.myTest}</li>
+ *   <li><strong>firstConfig</strong>: {@code MyTest.myTest[firstConfig]}</li>
+ *   <li><strong>secondConfig</strong>: {@code MyTest.myTest[secondConfig]}</li>
+ * </ul>
+ */
+public class ConfigSuite extends Suite {
+  private static final String DEFAULT = "default";
+
+  @Target({METHOD})
+  @Retention(RUNTIME)
+  public static @interface Config {
+  }
+
+  @Target({FIELD})
+  @Retention(RUNTIME)
+  public static @interface Parameter {
+  }
+
+  private static class ConfigRunner extends BlockJUnit4ClassRunner {
+    private final Method configMethod;
+    private final Field parameterField;
+    private final String name;
+
+    private ConfigRunner(Class<?> clazz, Field parameterField, String name,
+        Method configMethod) throws InitializationError {
+      super(clazz);
+      this.parameterField = parameterField;
+      this.name = name;
+      this.configMethod = configMethod;
+    }
+
+    @Override
+    public Object createTest() throws Exception {
+      Object test = getTestClass().getJavaClass().newInstance();
+      parameterField.set(test, callConfigMethod(configMethod));
+      return test;
+    }
+
+    @Override
+    protected String getName() {
+      return Objects.firstNonNull(name, DEFAULT);
+    }
+
+    @Override
+    protected String testName(FrameworkMethod method) {
+      String n = method.getName();
+      return name == null ? n : n + "[" + name + "]";
+    }
+  }
+
+  private static List<Runner> runnersFor(Class<?> clazz) {
+    List<Method> configs = getConfigs(clazz);
+    Field field = getParameterField(clazz);
+    List<Runner> result = Lists.newArrayListWithCapacity(configs.size() + 1);
+    try {
+      result.add(new ConfigRunner(clazz, field, null, null));
+      for (Method m : configs) {
+        result.add(new ConfigRunner(clazz, field, m.getName(), m));
+      }
+      return result;
+    } catch (InitializationError e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static List<Method> getConfigs(Class<?> clazz) {
+    List<Method> result = Lists.newArrayListWithExpectedSize(3);
+    for (Method m : clazz.getMethods()) {
+      Config ann = m.getAnnotation(Config.class);
+      if (ann != null) {
+        checkArgument(!m.getName().equals(DEFAULT),
+            "@ConfigSuite.Config cannot be named %s", DEFAULT);
+        result.add(m);
+      }
+    }
+    return result;
+  }
+
+  private static org.eclipse.jgit.lib.Config callConfigMethod(Method m) {
+    if (m == null) {
+      return new org.eclipse.jgit.lib.Config();
+    }
+    checkArgument(
+        org.eclipse.jgit.lib.Config.class.isAssignableFrom(m.getReturnType()),
+        "%s must return Config", m);
+    checkArgument((m.getModifiers() & Modifier.STATIC) != 0,
+        "%s must be static", m);
+    checkArgument(m.getParameterTypes().length == 0,
+        "%s must take no parameters", m);
+    try {
+      return (org.eclipse.jgit.lib.Config) m.invoke(null);
+    } catch (IllegalAccessException | IllegalArgumentException
+        | InvocationTargetException e) {
+      throw new IllegalArgumentException(e);
+    }
+  }
+
+  private static Field getParameterField(Class<?> clazz) {
+    List<Field> fields = Lists.newArrayListWithExpectedSize(1);
+    for (Field f : clazz.getFields()) {
+      if (f.getAnnotation(Parameter.class) != null) {
+        fields.add(f);
+      }
+    }
+    checkArgument(fields.size() == 1,
+        "expected 1 @ConfigSuite.Parameter field, found: %s", fields);
+    return fields.get(0);
+  }
+
+  public ConfigSuite(Class<?> clazz) throws InitializationError {
+    super(clazz, runnersFor(clazz));
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
new file mode 100644
index 0000000..db1ed05
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.testutil;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+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.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.util.TimeUtil;
+
+import java.util.Map;
+
+/** Fake implementation of {@link AccountCache} for testing. */
+public class FakeAccountCache implements AccountCache {
+  private final Map<Account.Id, AccountState> byId;
+  private final Map<String, AccountState> byUsername;
+
+  public FakeAccountCache() {
+    byId = Maps.newHashMap();
+    byUsername = Maps.newHashMap();
+  }
+
+  @Override
+  public synchronized AccountState get(Account.Id accountId) {
+    AccountState state = getIfPresent(accountId);
+    if (state != null) {
+      return state;
+    }
+    return newState(new Account(accountId, TimeUtil.nowTs()));
+  }
+
+  @Override
+  public synchronized AccountState getIfPresent(Account.Id accountId) {
+    return byId.get(accountId);
+  }
+
+  @Override
+  public synchronized AccountState getByUsername(String username) {
+    return byUsername.get(username);
+  }
+
+  @Override
+  public synchronized void evict(Account.Id accountId) {
+    byId.remove(accountId);
+  }
+
+  @Override
+  public synchronized void evictByUsername(String username) {
+    byUsername.remove(username);
+  }
+
+  public synchronized void put(Account account) {
+    AccountState state = newState(account);
+    byId.put(account.getId(), state);
+    if (account.getUserName() != null) {
+      byUsername.put(account.getUserName(), state);
+    }
+  }
+
+  private static AccountState newState(Account account) {
+    return new AccountState(
+        account, ImmutableSet.<AccountGroup.UUID> of(),
+        ImmutableSet.<AccountExternalId> of());
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeRealm.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeRealm.java
new file mode 100644
index 0000000..7214a5c
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeRealm.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.testutil;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Account.FieldName;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.Realm;
+
+/** Fake implementation of {@link Realm} for testing. */
+public class FakeRealm implements Realm {
+  @Override
+  public boolean allowsEdit(FieldName field) {
+    return false;
+  }
+
+  @Override
+  public AuthRequest authenticate(AuthRequest who) {
+    return who;
+  }
+
+  @Override
+  public AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who) {
+    return who;
+  }
+
+  @Override
+  public AuthRequest unlink(ReviewDb db, Account.Id to, AuthRequest who) {
+    return who;
+  }
+
+  @Override
+  public void onCreateAccount(AuthRequest who, Account account) {
+    // Do nothing.
+  }
+
+  @Override
+  public Account.Id lookup(String accountName) {
+    return null;
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
index a1e68d0..bc344fa 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryDatabase.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.testutil;
 
+import static org.junit.Assert.assertEquals;
+
 import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
 import com.google.gerrit.reviewdb.client.SystemConfig;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -26,8 +28,6 @@
 import com.google.inject.Guice;
 import com.google.inject.Inject;
 
-import junit.framework.TestCase;
-
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
 import java.io.IOException;
@@ -57,8 +57,7 @@
     final Properties p = new Properties();
     p.setProperty("driver", org.h2.Driver.class.getName());
     p.setProperty("url", "jdbc:h2:mem:" + "Test_" + (++dbCnt));
-    final DataSource dataSource = new SimpleDataSource(p);
-    return dataSource;
+    return new SimpleDataSource(p);
   }
 
   /** Drop the database from memory; does nothing if the instance was null. */
@@ -162,6 +161,6 @@
 
   public void assertSchemaVersion() throws OrmException {
     final CurrentSchemaVersion act = getSchemaVersion();
-    TestCase.assertEquals(schemaVersion.getVersionNbr(), act.versionNbr);
+    assertEquals(schemaVersion.getVersionNbr(), act.versionNbr);
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 5e3cbac..9e698f7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -17,6 +17,7 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.inject.Scopes.SINGLETON;
 
+import com.google.common.net.InetAddresses;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.DisabledChangeHooks;
 import com.google.gerrit.reviewdb.client.AuthType;
@@ -25,6 +26,7 @@
 import com.google.gerrit.server.GerritPersonIdentProvider;
 import com.google.gerrit.server.RemotePeer;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.change.MergeabilityChecksExecutorModule;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.config.AnonymousCowardName;
@@ -43,7 +45,6 @@
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.index.ChangeSchemas;
 import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.index.NoIndexModule;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.schema.Current;
@@ -70,10 +71,8 @@
 import java.io.File;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
-import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
-import java.net.UnknownHostException;
 
 public class InMemoryModule extends FactoryModule {
   public static Config newDefaultConfig() {
@@ -85,6 +84,10 @@
     cfg.setString("user", null, "email", "gerrit@localhost");
     cfg.setBoolean("sendemail", null, "enable", false);
     cfg.setString("cache", null, "directory", null);
+    cfg.setString("index", null, "type", "lucene");
+    cfg.setBoolean("index", "lucene", "testInmemory", true);
+    cfg.setInt("index", "lucene", "testVersion",
+        ChangeSchemas.getLatest().getVersion());
     return cfg;
   }
 
@@ -121,12 +124,8 @@
 
     bind(File.class).annotatedWith(SitePath.class).toInstance(new File("."));
     bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
-    try {
-      bind(SocketAddress.class).annotatedWith(RemotePeer.class)
-          .toInstance(new InetSocketAddress(InetAddress.getLocalHost(), 1234));
-    } catch (UnknownHostException e) {
-      throw newProvisionException(e);
-    }
+    bind(SocketAddress.class).annotatedWith(RemotePeer.class).toInstance(
+        new InetSocketAddress(InetAddresses.forString("127.0.0.1"), 1234));
     bind(PersonIdent.class)
         .annotatedWith(GerritPersonIdent.class)
         .toProvider(GerritPersonIdentProvider.class);
@@ -157,10 +156,11 @@
     install(new DefaultCacheFactory.Module());
     install(new SmtpEmailSender.Module());
     install(new SignedTokenEmailTokenVerifier.Module());
+    install(new MergeabilityChecksExecutorModule());
 
     IndexType indexType = null;
     try {
-      indexType = cfg.getEnum("index", null, "type", IndexType.SQL);
+      indexType = cfg.getEnum("index", null, "type", IndexType.LUCENE);
     } catch (IllegalArgumentException e) {
       // Custom index type, caller must provide their own module.
     }
@@ -169,9 +169,6 @@
         case LUCENE:
           install(luceneIndexModule());
           break;
-        case SQL:
-          install(new NoIndexModule());
-          break;
         default:
           throw new ProvisionException(
               "index type unsupported in tests: " + indexType);
@@ -204,27 +201,13 @@
           Class.forName("com.google.gerrit.lucene.LuceneIndexModule");
       Constructor<?> c =
           clazz.getConstructor(Integer.class, int.class, String.class);
-      return (Module) c.newInstance(Integer.valueOf(version), 0, (String) null);
-    } catch (ClassNotFoundException e) {
-      throw newProvisionException(e);
-    } catch (SecurityException e) {
-      throw newProvisionException(e);
-    } catch (NoSuchMethodException e) {
-      throw newProvisionException(e);
-    } catch (IllegalArgumentException e) {
-      throw newProvisionException(e);
-    } catch (InstantiationException e) {
-      throw newProvisionException(e);
-    } catch (IllegalAccessException e) {
-      throw newProvisionException(e);
-    } catch (InvocationTargetException e) {
-      throw newProvisionException(e);
+      return (Module) c.newInstance(version, 0, null);
+    } catch (ClassNotFoundException | SecurityException | NoSuchMethodException
+        | IllegalArgumentException | InstantiationException
+        | IllegalAccessException | InvocationTargetException e) {
+      ProvisionException pe = new ProvisionException(e.getMessage());
+      pe.initCause(e);
+      throw pe;
     }
   }
-
-  private static ProvisionException newProvisionException(Throwable cause) {
-    ProvisionException pe = new ProvisionException(cause.getMessage());
-    pe.initCause(cause);
-    return pe;
-  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
index c6626f0..328df75 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
@@ -31,6 +31,10 @@
 
 /** Repository manager that uses in-memory repositories. */
 public class InMemoryRepositoryManager implements GitRepositoryManager {
+  public static InMemoryRepository newRepository(Project.NameKey name) {
+    return new Repo(name);
+  }
+
   private static class Description extends DfsRepositoryDescription {
     private String desc;
 
diff --git a/gerrit-solr/BUCK b/gerrit-solr/BUCK
index c072830..ec3c728 100644
--- a/gerrit-solr/BUCK
+++ b/gerrit-solr/BUCK
@@ -12,6 +12,7 @@
     '//lib/guice:guice',
     '//lib/jgit:jgit',
     '//lib/log:api',
+    '//lib/lucene:analyzers-common',
     '//lib/lucene:core',
     '//lib/solr:solrj',
   ],
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
index 4f616b9..8c8b007 100644
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
+++ b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
@@ -20,11 +20,13 @@
 import static com.google.gerrit.solr.IndexVersionCheck.solrIndexConfig;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lucene.QueryBuilder;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.ChangeField;
@@ -40,11 +42,17 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.SortKeyPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Provider;
 
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.analysis.util.CharArraySet;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.util.Version;
 import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrQuery.SortClause;
 import org.apache.solr.client.solrj.SolrServer;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.CloudSolrServer;
@@ -69,30 +77,45 @@
   public static final String CHANGES_CLOSED = "changes_closed";
   private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
 
+  private final Provider<ReviewDb> db;
+  private final ChangeData.Factory changeDataFactory;
   private final FillArgs fillArgs;
   private final SitePaths sitePaths;
   private final IndexCollection indexes;
   private final CloudSolrServer openIndex;
   private final CloudSolrServer closedIndex;
   private final Schema<ChangeData> schema;
+  private final QueryBuilder queryBuilder;
 
   SolrChangeIndex(
       @GerritServerConfig Config cfg,
+      Provider<ReviewDb> db,
+      ChangeData.Factory changeDataFactory,
       FillArgs fillArgs,
       SitePaths sitePaths,
       IndexCollection indexes,
       Schema<ChangeData> schema,
       String base) throws IOException {
+    this.db = db;
+    this.changeDataFactory = changeDataFactory;
     this.fillArgs = fillArgs;
     this.sitePaths = sitePaths;
     this.indexes = indexes;
     this.schema = schema;
 
-    String url = cfg.getString("index", "solr", "url");
+    String url = cfg.getString("index", null, "url");
     if (Strings.isNullOrEmpty(url)) {
-      throw new IllegalStateException("index.solr.url must be supplied");
+      throw new IllegalStateException("index.url must be supplied");
     }
 
+    // Version is only used to determine the list of stop words used by the
+    // analyzer, so use the latest version rather than trying to match the Solr
+    // server version.
+    @SuppressWarnings("deprecation")
+    Version v = Version.LUCENE_CURRENT;
+    queryBuilder = new QueryBuilder(
+        schema, new StandardAnalyzer(v, CharArraySet.EMPTY_SET));
+
     base = Strings.nullToEmpty(base);
     openIndex = new CloudSolrServer(url);
     openIndex.setDefaultCollection(base + CHANGES_OPEN);
@@ -128,14 +151,14 @@
     String id = cd.getId().toString();
     SolrInputDocument doc = toDocument(cd);
     try {
-      if (cd.getChange().getStatus().isOpen()) {
+      if (cd.change().getStatus().isOpen()) {
         closedIndex.deleteById(id);
         openIndex.add(doc);
       } else {
         openIndex.deleteById(id);
         closedIndex.add(doc);
       }
-    } catch (SolrServerException e) {
+    } catch (OrmException | SolrServerException e) {
       throw new IOException(e);
     }
     commit(openIndex);
@@ -147,14 +170,14 @@
     String id = cd.getId().toString();
     SolrInputDocument doc = toDocument(cd);
     try {
-      if (cd.getChange().getStatus().isOpen()) {
+      if (cd.change().getStatus().isOpen()) {
         closedIndex.deleteById(id);
         openIndex.add(doc);
       } else {
         openIndex.deleteById(id);
         closedIndex.add(doc);
       }
-    } catch (SolrServerException e) {
+    } catch (OrmException | SolrServerException e) {
       throw new IOException(e);
     }
     commit(openIndex);
@@ -165,14 +188,14 @@
   public void delete(ChangeData cd) throws IOException {
     String id = cd.getId().toString();
     try {
-      if (cd.getChange().getStatus().isOpen()) {
+      if (cd.change().getStatus().isOpen()) {
         openIndex.deleteById(id);
         commit(openIndex);
       } else {
         closedIndex.deleteById(id);
         commit(closedIndex);
       }
-    } catch (SolrServerException e) {
+    } catch (OrmException | SolrServerException e) {
       throw new IOException(e);
     }
   }
@@ -190,7 +213,7 @@
   }
 
   @Override
-  public ChangeDataSource getSource(Predicate<ChangeData> p, int limit)
+  public ChangeDataSource getSource(Predicate<ChangeData> p, int start, int limit)
       throws QueryParseException {
     Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p);
     List<SolrServer> indexes = Lists.newArrayListWithCapacity(2);
@@ -200,8 +223,24 @@
     if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
       indexes.add(closedIndex);
     }
-    return new QuerySource(indexes, QueryBuilder.toQuery(schema, p), limit,
-        ChangeQueryBuilder.hasNonTrivialSortKeyAfter(schema, p));
+    return new QuerySource(indexes, queryBuilder.toQuery(p), start, limit,
+        getSorts(schema, p));
+  }
+
+  @SuppressWarnings("deprecation")
+  private static List<SortClause> getSorts(Schema<ChangeData> schema,
+      Predicate<ChangeData> p) {
+    if (SortKeyPredicate.hasSortKeyField(schema)) {
+      boolean reverse = ChangeQueryBuilder.hasNonTrivialSortKeyAfter(schema, p);
+      return ImmutableList.of(new SortClause(ChangeField.SORTKEY.getName(),
+          !reverse ? SolrQuery.ORDER.desc : SolrQuery.ORDER.asc));
+    } else {
+      return ImmutableList.of(
+          new SortClause(
+            ChangeField.UPDATED.getName(), SolrQuery.ORDER.desc),
+          new SortClause(
+            ChangeField.LEGACY_ID.getName(), SolrQuery.ORDER.desc));
+    }
   }
 
   private void commit(SolrServer server) throws IOException {
@@ -216,17 +255,18 @@
     private final List<SolrServer> indexes;
     private final SolrQuery query;
 
-    public QuerySource(List<SolrServer> indexes, Query q, int limit,
-        boolean reverse) {
+    public QuerySource(List<SolrServer> indexes, Query q, int start, int limit,
+        List<SortClause> sorts) {
       this.indexes = indexes;
 
       query = new SolrQuery(q.toString());
       query.setParam("shards.tolerant", true);
       query.setParam("rows", Integer.toString(limit));
+      if (start != 0) {
+        query.setParam("start", Integer.toString(start));
+      }
       query.setFields(ID_FIELD);
-      query.setSort(
-          ChangeField.SORTKEY.getName(),
-          !reverse ? SolrQuery.ORDER.desc : SolrQuery.ORDER.asc);
+      query.setSorts(sorts);
     }
 
     @Override
@@ -256,7 +296,8 @@
         List<ChangeData> result = Lists.newArrayListWithCapacity(docs.size());
         for (SolrDocument doc : docs) {
           Integer v = (Integer) doc.getFieldValue(ID_FIELD);
-          result.add(new ChangeData(new Change.Id(v.intValue())));
+          result.add(
+              changeDataFactory.create(db.get(), new Change.Id(v.intValue())));
         }
 
         final List<ChangeData> r = Collections.unmodifiableList(result);
@@ -308,8 +349,17 @@
         doc.addField(name, (Long) value);
       }
     } else if (type == FieldType.TIMESTAMP) {
-      for (Object v : values.getValues()) {
-        doc.addField(name, QueryBuilder.toIndexTime((Timestamp) v));
+      @SuppressWarnings("deprecation")
+      boolean legacy = values.getField() == ChangeField.LEGACY_UPDATED;
+      if (legacy) {
+        for (Object value : values.getValues()) {
+          int t = queryBuilder.toIndexTimeInMinutes((Timestamp) value);
+          doc.addField(name, t);
+        }
+      } else {
+        for (Object value : values.getValues()) {
+          doc.addField(name, ((Timestamp) value).getTime());
+        }
       }
     } else if (type == FieldType.EXACT
         || type == FieldType.PREFIX
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
index e73c6c1..7474d0f 100644
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
+++ b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.solr;
 
 import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.ChangeIndex;
@@ -22,6 +23,8 @@
 import com.google.gerrit.server.index.FieldDef.FillArgs;
 import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.index.IndexModule;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Provider;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
 
@@ -57,10 +60,12 @@
   @Provides
   @Singleton
   public SolrChangeIndex getChangeIndex(@GerritServerConfig Config cfg,
+      Provider<ReviewDb> db,
+      ChangeData.Factory changeDataFactory,
       SitePaths sitePaths,
       IndexCollection indexes,
       FillArgs fillArgs) throws IOException {
-    return new SolrChangeIndex(cfg, fillArgs, sitePaths, indexes,
-        ChangeSchemas.getLatest(), base);
+    return new SolrChangeIndex(cfg, db, changeDataFactory, fillArgs, sitePaths,
+        indexes, ChangeSchemas.getLatest(), base);
   }
 }
diff --git a/gerrit-sshd/.gitignore b/gerrit-sshd/.gitignore
deleted file mode 100644
index 8deb9bd..0000000
--- a/gerrit-sshd/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-sshd.iml
\ No newline at end of file
diff --git a/gerrit-sshd/.settings/org.eclipse.core.resources.prefs b/gerrit-sshd/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index 839d647..0000000
--- a/gerrit-sshd/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,5 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/main/resources=UTF-8
-encoding//src/test/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-sshd/.settings/org.eclipse.core.runtime.prefs b/gerrit-sshd/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-sshd/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-sshd/.settings/org.eclipse.jdt.core.prefs b/gerrit-sshd/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 941fb31..0000000
--- a/gerrit-sshd/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,270 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-sshd/.settings/org.eclipse.jdt.ui.prefs b/gerrit-sshd/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-sshd/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index 93a3ef7..4728c31 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -6,6 +6,7 @@
   deps = [
     '//gerrit-extension-api:api',
     '//gerrit-cache-h2:cache-h2',
+    '//gerrit-common:annotations',
     '//gerrit-common:server',
     '//gerrit-patch-jgit:server',
     '//gerrit-reviewdb:server',
@@ -37,3 +38,17 @@
   srcs = SRCS,
   visibility = ['PUBLIC'],
 )
+
+java_test(
+  name = 'sshd_tests',
+  srcs = glob(
+    ['src/test/java/**/*.java'],
+  ),
+  deps = [
+    ':sshd',
+    '//gerrit-server:server',
+    '//lib:guava',
+    '//lib:junit',
+  ],
+  source_under_test = [':sshd'],
+)
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index 4ac8f64..40f7c7d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -405,7 +405,7 @@
       m.append(context.getCommandLine());
       if (userProvider.get().isIdentifiedUser()) {
         IdentifiedUser u = (IdentifiedUser) userProvider.get();
-        m.append(" (" + u.getAccount().getUserName() + ")");
+        m.append(" (").append(u.getAccount().getUserName()).append(")");
       }
       this.taskName = m.toString();
     }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java
index 42c0fd7..7fb9226 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java
@@ -21,15 +21,19 @@
 import java.lang.annotation.Target;
 
 /**
- * Annotation tagged on a concrete Command to describe what it is doing
+ * Annotation tagged on a concrete Command to describe what it is doing and
+ * whether it can be run on slaves.
  */
-@Target( {ElementType.TYPE})
+@Target({ElementType.TYPE})
 @Retention(RUNTIME)
 public @interface CommandMetaData {
+  public enum Mode {
+    MASTER, MASTER_OR_SLAVE;
+    public boolean isSupported(boolean slaveMode) {
+      return this == MASTER_OR_SLAVE || !slaveMode;
+    }
+  }
   String name();
   String description() default "";
-
-  /** @deprecated use description intead. */
-  @Deprecated
-  String descr() default "";
+  Mode runsAt() default Mode.MASTER;
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
index c64f9d8..1e409d2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
@@ -14,15 +14,15 @@
 
 package com.google.gerrit.sshd;
 
-import com.google.common.base.Objects;
-import com.google.common.base.Strings;
-import com.google.inject.AbstractModule;
+import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.inject.binder.LinkedBindingBuilder;
 
 import org.apache.sshd.server.Command;
 
 /** Module to register commands in the SSH daemon. */
-public abstract class CommandModule extends AbstractModule {
+public abstract class CommandModule extends LifecycleModule {
+  protected boolean slaveMode;
+
   /**
    * Configure a command to be invoked by name.
    *
@@ -72,11 +72,13 @@
    */
   protected void command(final CommandName parent,
       final Class<? extends BaseCommand> clazz) {
-    CommandMetaData meta = (CommandMetaData)clazz.getAnnotation(CommandMetaData.class);
+    CommandMetaData meta = clazz.getAnnotation(CommandMetaData.class);
     if (meta == null) {
       throw new IllegalStateException("no CommandMetaData annotation found");
     }
-    bind(Commands.key(parent, meta.name(), description(meta))).to(clazz);
+    if (meta.runsAt().isSupported(slaveMode)) {
+      bind(Commands.key(parent, meta.name(), meta.description())).to(clazz);
+    }
   }
 
   /**
@@ -91,18 +93,11 @@
    */
   protected void alias(final CommandName parent, final String name,
       final Class<? extends BaseCommand> clazz) {
-    CommandMetaData meta = (CommandMetaData)clazz.getAnnotation(CommandMetaData.class);
+    CommandMetaData meta = clazz.getAnnotation(CommandMetaData.class);
     if (meta == null) {
       throw new IllegalStateException("no CommandMetaData annotation found");
     }
-    bind(Commands.key(parent, name, description(meta))).to(clazz);
-  }
-
-  @SuppressWarnings("deprecation")
-  private static String description(CommandMetaData meta) {
-    return Objects.firstNonNull(
-        Strings.emptyToNull(meta.description()),
-        meta.descr());
+    bind(Commands.key(parent, name, meta.description())).to(clazz);
   }
 
   /**
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 8dc3f2c..40e58f2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -140,14 +140,13 @@
       }
     }
 
-    if (!SshUtil.createUser(sd, userFactory, key.getAccount())
-        .getAccount().isActive()) {
+    IdentifiedUser cu = SshUtil.createUser(sd, userFactory, key.getAccount());
+    if (!cu.getAccount().isActive()) {
       sd.authenticationError(username, "inactive-account");
       return false;
     }
 
-    return SshUtil.success(username, session, sshScope, sshLog, sd,
-        SshUtil.createUser(sd, userFactory, key.getAccount()));
+    return SshUtil.success(username, session, sshScope, sshLog, sd, cu);
   }
 
   private Set<PublicKey> getPeerKeys() {
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 d548d34..fa5ab53 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
@@ -21,7 +21,6 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.CapabilityUtils;
 import com.google.gerrit.server.args4j.SubcommandHandler;
-import com.google.gerrit.sshd.commands.ErrorSlaveMode;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
@@ -155,13 +154,9 @@
     String format = "%-" + maxLength + "s   %s";
     for (String name : Sets.newTreeSet(commands.keySet())) {
       final CommandProvider p = commands.get(name);
-      Command c = p.getProvider().get();
-      String description = c instanceof ErrorSlaveMode
-          ? "Command disabled: server is running in slave mode"
-          : Strings.nullToEmpty(p.getDescription());
-
       usage.append("   ");
-      usage.append(String.format(format, name, description));
+      usage.append(String.format(format, name,
+          Strings.nullToEmpty(p.getDescription())));
       usage.append("\n");
     }
     usage.append("\n");
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
index cde7ae8..78f006b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/NoShell.java
@@ -106,7 +106,7 @@
       } finally {
         sshScope.set(old);
       }
-      err.write(Constants.encode(message.toString()));
+      err.write(Constants.encode(message));
       err.flush();
 
       in.close();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SingleCommandPluginModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SingleCommandPluginModule.java
new file mode 100644
index 0000000..0c5fca3
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SingleCommandPluginModule.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd;
+
+import com.google.common.base.Preconditions;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.inject.binder.LinkedBindingBuilder;
+
+import org.apache.sshd.server.Command;
+
+import javax.inject.Inject;
+
+/**
+ * Binds one SSH command to the plugin name itself.
+ * <p>
+ * Cannot be combined with {@link PluginCommandModule}.
+ */
+public abstract class SingleCommandPluginModule extends CommandModule {
+  private CommandName command;
+
+  @Inject
+  void setPluginName(@PluginName String name) {
+    this.command = Commands.named(name);
+  }
+
+  @Override
+  protected final void configure() {
+    Preconditions.checkState(command != null, "@PluginName must be provided");
+    configure(bind(Commands.key(command)));
+  }
+
+  protected abstract void configure(LinkedBindingBuilder<Command> bind);
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 4f6ccc0..6351280 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -111,12 +111,12 @@
 /**
  * SSH daemon to communicate with Gerrit.
  * <p>
- * Use a Git URL such as <code>ssh://${email}@${host}:${port}/${path}</code>,
- * e.g. <code>ssh://sop@google.com@gerrit.com:8010/tools/gerrit.git</code> to
+ * Use a Git URL such as {@code ssh://${email}@${host}:${port}/${path}},
+ * e.g. {@code ssh://sop@google.com@gerrit.com:8010/tools/gerrit.git} to
  * access the SSH daemon itself.
  * <p>
  * Versions of Git before 1.5.3 may require setting the username and port
- * properties in the user's <code>~/.ssh/config</code> file, and using a host
+ * properties in the user's {@code ~/.ssh/config} file, and using a host
  * alias through a URL such as <code>gerrit-alias:/tools/gerrit.git:
  * <pre>
  * Host gerrit-alias
@@ -346,7 +346,6 @@
     return r.toString();
   }
 
-  @SuppressWarnings("unchecked")
   private void initProviderBouncyCastle() {
     setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>> asList(
         new DHG14.Factory(), new DHG1.Factory()));
@@ -354,7 +353,6 @@
         new BouncyCastleRandom.Factory()));
   }
 
-  @SuppressWarnings("unchecked")
   private void initProviderJce() {
     setKeyExchangeFactories(Arrays
         .<NamedFactory<KeyExchange>> asList(new DHG1.Factory()));
@@ -393,13 +391,13 @@
         .size()])));
   }
 
-  @SuppressWarnings("unchecked")
   private void initMacs(final Config cfg) {
     setMacFactories(filter(cfg, "mac", new HMACMD5.Factory(),
         new HMACSHA1.Factory(), new HMACMD596.Factory(),
         new HMACSHA196.Factory()));
   }
 
+  @SafeVarargs
   private static <T> List<NamedFactory<T>> filter(final Config cfg,
       final String key, final NamedFactory<T>... avail) {
     final ArrayList<NamedFactory<T>> def = new ArrayList<NamedFactory<T>>();
@@ -432,7 +430,8 @@
       final NamedFactory<T> n = find(name, avail);
       if (n == null) {
         final StringBuilder msg = new StringBuilder();
-        msg.append("sshd." + key + " = " + name + " unsupported; only ");
+        msg.append("sshd.").append(key).append(" = ").append(name)
+           .append(" unsupported; only ");
         for (int i = 0; i < avail.length; i++) {
           if (avail[i] == null) {
             continue;
@@ -456,6 +455,7 @@
     return def;
   }
 
+  @SafeVarargs
   private static <T> NamedFactory<T> find(final String name,
       final NamedFactory<T>... avail) {
     for (final NamedFactory<T> n : avail) {
@@ -466,13 +466,11 @@
     return null;
   }
 
-  @SuppressWarnings("unchecked")
   private void initSignatures() {
     setSignatureFactories(Arrays.<NamedFactory<Signature>> asList(
         new SignatureDSA.Factory(), new SignatureRSA.Factory()));
   }
 
-  @SuppressWarnings("unchecked")
   private void initCompression() {
     // Always disable transparent compression. The majority of our data
     // transfer is highly compressed Git pack files. We cannot make them
@@ -482,7 +480,6 @@
         .<NamedFactory<Compression>> asList(new CompressionNone.Factory()));
   }
 
-  @SuppressWarnings("unchecked")
   private void initChannels() {
     setChannelFactories(Arrays.<NamedFactory<Channel>> asList(
         new ChannelSession.Factory(), //
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 a78f9f5..3e6dc97 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
@@ -25,6 +25,7 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.util.IdGenerator;
+import com.google.gerrit.server.util.LogUtil;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gerrit.sshd.SshScope.Context;
 import com.google.inject.Inject;
@@ -34,20 +35,14 @@
 import org.apache.log4j.Appender;
 import org.apache.log4j.AsyncAppender;
 import org.apache.log4j.DailyRollingFileAppender;
-import org.apache.log4j.Layout;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 import org.apache.log4j.spi.ErrorHandler;
 import org.apache.log4j.spi.LoggingEvent;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.util.QuotedString;
 
 import java.io.File;
 import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.TimeZone;
 
 @Singleton
 class SshLog implements LifecycleListener {
@@ -72,10 +67,20 @@
     this.context = context;
     this.auditService = auditService;
 
-    if (config.getBoolean("sshd", "requestLog", true)) {
+    if (!config.getBoolean("sshd", "requestLog", true)) {
+      async = null;
+      return;
+    }
+
+    async = new AsyncAppender();
+    async.setBlocking(true);
+    async.setBufferSize(config.getInt("core", "asyncLoggingBufferSize", 64));
+    async.setLocationInfo(false);
+
+    if (LogUtil.shouldConfigureLogSystem()) {
       final DailyRollingFileAppender dst = new DailyRollingFileAppender();
       dst.setName(LOG_NAME);
-      dst.setLayout(new MyLayout());
+      dst.setLayout(new SshLogLayout());
       dst.setEncoding("UTF-8");
       dst.setFile(new File(resolve(site.logs_dir), LOG_NAME).getPath());
       dst.setImmediateFlush(true);
@@ -84,16 +89,18 @@
       dst.setErrorHandler(new DieErrorHandler());
       dst.activateOptions();
       dst.setErrorHandler(new LogLogHandler());
-
-      async = new AsyncAppender();
-      async.setBlocking(true);
-      async.setBufferSize(config.getInt("core", "asyncLoggingBufferSize", 64));
-      async.setLocationInfo(false);
       async.addAppender(dst);
-      async.activateOptions();
     } else {
-      async = null;
+      Appender appender = log.getAppender(LOG_NAME);
+      if (appender != null) {
+        async.addAppender(appender);
+      } else {
+        log.warn("No appender with the name: "
+            + LOG_NAME
+            + " was found. SSHD logging is disabled");
+      }
     }
+    async.activateOptions();
   }
 
   @Override
@@ -277,126 +284,6 @@
     }
   }
 
-  private static final class MyLayout extends Layout {
-    private final Calendar calendar;
-    private long lastTimeMillis;
-    private final char[] lastTimeString = new char[20];
-    private final char[] timeZone;
-
-    MyLayout() {
-      final TimeZone tz = TimeZone.getDefault();
-      calendar = Calendar.getInstance(tz);
-
-      final SimpleDateFormat sdf = new SimpleDateFormat("Z");
-      sdf.setTimeZone(tz);
-      timeZone = sdf.format(new Date()).toCharArray();
-    }
-
-    @Override
-    public String format(LoggingEvent event) {
-      final StringBuffer buf = new StringBuffer(128);
-
-      buf.append('[');
-      formatDate(event.getTimeStamp(), buf);
-      buf.append(' ');
-      buf.append(timeZone);
-      buf.append(']');
-
-      req(P_SESSION, buf, event);
-      req(P_USER_NAME, buf, event);
-      req(P_ACCOUNT_ID, buf, event);
-
-      buf.append(' ');
-      buf.append(event.getMessage());
-
-      opt(P_WAIT, buf, event);
-      opt(P_EXEC, buf, event);
-      opt(P_STATUS, buf, event);
-
-      buf.append('\n');
-      return buf.toString();
-    }
-
-    private void formatDate(final long now, final StringBuffer sbuf) {
-      final int millis = (int) (now % 1000);
-      final long rounded = now - millis;
-      if (rounded != lastTimeMillis) {
-        synchronized (calendar) {
-          final int start = sbuf.length();
-
-          calendar.setTimeInMillis(rounded);
-          sbuf.append(calendar.get(Calendar.YEAR));
-          sbuf.append('-');
-          final int month = calendar.get(Calendar.MONTH) + 1;
-          if (month < 10) sbuf.append('0');
-          sbuf.append(month);
-          sbuf.append('-');
-          final int day = calendar.get(Calendar.DAY_OF_MONTH);
-          if (day < 10) sbuf.append('0');
-          sbuf.append(day);
-
-          sbuf.append(' ');
-          final int hour = calendar.get(Calendar.HOUR_OF_DAY);
-          if (hour < 10) sbuf.append('0');
-          sbuf.append(hour);
-          sbuf.append(':');
-          final int mins = calendar.get(Calendar.MINUTE);
-          if (mins < 10) sbuf.append('0');
-          sbuf.append(mins);
-          sbuf.append(':');
-          final int secs = calendar.get(Calendar.SECOND);
-          if (secs < 10) sbuf.append('0');
-          sbuf.append(secs);
-
-          sbuf.append(',');
-          sbuf.getChars(start, sbuf.length(), lastTimeString, 0);
-          lastTimeMillis = rounded;
-        }
-      } else {
-        sbuf.append(lastTimeString);
-      }
-      if (millis < 100) {
-        sbuf.append('0');
-      }
-      if (millis < 10) {
-        sbuf.append('0');
-      }
-      sbuf.append(millis);
-    }
-
-    private void req(String key, StringBuffer buf, LoggingEvent event) {
-      Object val = event.getMDC(key);
-      buf.append(' ');
-      if (val != null) {
-        String s = val.toString();
-        if (0 <= s.indexOf(' ')) {
-          buf.append(QuotedString.BOURNE.quote(s));
-        } else {
-          buf.append(val);
-        }
-      } else {
-        buf.append('-');
-      }
-    }
-
-    private void opt(String key, StringBuffer buf, LoggingEvent event) {
-      Object val = event.getMDC(key);
-      if (val != null) {
-        buf.append(' ');
-        buf.append(val);
-      }
-    }
-
-    @Override
-    public boolean ignoresThrowable() {
-      return true;
-    }
-
-    @Override
-    public void activateOptions() {
-    }
-  }
-
   private static final class DieErrorHandler implements ErrorHandler {
     @Override
     public void error(String message, Exception e, int errorCode,
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLogLayout.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLogLayout.java
new file mode 100644
index 0000000..0c04749
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLogLayout.java
@@ -0,0 +1,137 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.spi.LoggingEvent;
+import org.eclipse.jgit.util.QuotedString;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+public final class SshLogLayout extends Layout {
+
+  private static final String P_SESSION = "session";
+  private static final String P_USER_NAME = "userName";
+  private static final String P_ACCOUNT_ID = "accountId";
+  private static final String P_WAIT = "queueWaitTime";
+  private static final String P_EXEC = "executionTime";
+  private static final String P_STATUS = "status";
+
+  private final Calendar calendar;
+  private long lastTimeMillis;
+  private final char[] lastTimeString = new char[20];
+  private final char[] timeZone;
+
+ public SshLogLayout() {
+    final TimeZone tz = TimeZone.getDefault();
+    calendar = Calendar.getInstance(tz);
+
+    final SimpleDateFormat sdf = new SimpleDateFormat("Z");
+    sdf.setTimeZone(tz);
+    timeZone = sdf.format(new Date()).toCharArray();
+  }
+
+  @Override
+  public String format(LoggingEvent event) {
+    final StringBuffer buf = new StringBuffer(128);
+
+    buf.append('[');
+    formatDate(event.getTimeStamp(), buf);
+    buf.append(' ');
+    buf.append(timeZone);
+    buf.append(']');
+
+    req(P_SESSION, buf, event);
+    req(P_USER_NAME, buf, event);
+    req(P_ACCOUNT_ID, buf, event);
+
+    buf.append(' ');
+    buf.append(event.getMessage());
+
+    opt(P_WAIT, buf, event);
+    opt(P_EXEC, buf, event);
+    opt(P_STATUS, buf, event);
+
+    buf.append('\n');
+    return buf.toString();
+  }
+
+  private void formatDate(final long now, final StringBuffer sbuf) {
+    final int millis = (int) (now % 1000);
+    final long rounded = now - millis;
+    if (rounded != lastTimeMillis) {
+      synchronized (calendar) {
+        final int start = sbuf.length();
+        calendar.setTimeInMillis(rounded);
+        sbuf.append(calendar.get(Calendar.YEAR));
+        sbuf.append('-');
+        sbuf.append(toTwoDigits(calendar.get(Calendar.MONTH) + 1));
+        sbuf.append('-');
+        sbuf.append(toTwoDigits(calendar.get(Calendar.DAY_OF_MONTH)));
+        sbuf.append(' ');
+        sbuf.append(toTwoDigits(calendar.get(Calendar.HOUR_OF_DAY)));
+        sbuf.append(':');
+        sbuf.append(toTwoDigits(calendar.get(Calendar.MINUTE)));
+        sbuf.append(':');
+        sbuf.append(toTwoDigits(calendar.get(Calendar.SECOND)));
+        sbuf.append(',');
+        sbuf.getChars(start, sbuf.length(), lastTimeString, 0);
+        lastTimeMillis = rounded;
+      }
+    } else {
+      sbuf.append(lastTimeString);
+    }
+    sbuf.append(String.format("%03d", millis));
+  }
+
+  private String toTwoDigits(int input) {
+    return String.format("%02d", input);
+  }
+
+  private void req(String key, StringBuffer buf, LoggingEvent event) {
+    Object val = event.getMDC(key);
+    buf.append(' ');
+    if (val != null) {
+      String s = val.toString();
+      if (0 <= s.indexOf(' ')) {
+        buf.append(QuotedString.BOURNE.quote(s));
+      } else {
+        buf.append(val);
+      }
+    } else {
+      buf.append('-');
+    }
+  }
+
+  private void opt(String key, StringBuffer buf, LoggingEvent event) {
+    Object val = event.getMDC(key);
+    if (val != null) {
+      buf.append(' ');
+      buf.append(val);
+    }
+  }
+
+  @Override
+  public boolean ignoresThrowable() {
+    return true;
+  }
+
+  @Override
+  public void activateOptions() {
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index c1c0ff5..50ab639 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.PeerDaemonUser;
 import com.google.gerrit.server.RemotePeer;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritRequestModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.AsyncReceiveCommits;
@@ -32,7 +31,6 @@
 import com.google.gerrit.server.plugins.StartPluginListener;
 import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.gerrit.sshd.commands.DefaultCommandModule;
 import com.google.gerrit.sshd.commands.QueryShell;
 import com.google.inject.Inject;
 import com.google.inject.internal.UniqueAnnotations;
@@ -47,7 +45,7 @@
 import java.util.Map;
 
 /** Configures standard dependencies for {@link SshDaemon}. */
-public class SshModule extends FactoryModule {
+public class SshModule extends LifecycleModule {
   private final Map<String, String> aliases;
 
   @Inject
@@ -85,27 +83,20 @@
     bind(GSSAuthenticator.class).to(GerritGSSAuthenticator.class);
     bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
 
-    install(new DefaultCommandModule());
+    bind(ModuleGenerator.class).to(SshAutoRegisterModuleGenerator.class);
+    bind(SshPluginStarterCallback.class);
+    bind(StartPluginListener.class)
+      .annotatedWith(UniqueAnnotations.create())
+      .to(SshPluginStarterCallback.class);
 
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        bind(ModuleGenerator.class).to(SshAutoRegisterModuleGenerator.class);
-        bind(SshPluginStarterCallback.class);
-        bind(StartPluginListener.class)
-          .annotatedWith(UniqueAnnotations.create())
-          .to(SshPluginStarterCallback.class);
+    bind(ReloadPluginListener.class)
+      .annotatedWith(UniqueAnnotations.create())
+      .to(SshPluginStarterCallback.class);
 
-        bind(ReloadPluginListener.class)
-          .annotatedWith(UniqueAnnotations.create())
-          .to(SshPluginStarterCallback.class);
-
-        listener().toInstance(registerInParentInjectors());
-        listener().to(SshLog.class);
-        listener().to(SshDaemon.class);
-        listener().to(CommandFactoryProvider.class);
-      }
-    });
+    listener().toInstance(registerInParentInjectors());
+    listener().to(SshLog.class);
+    listener().to(SshDaemon.class);
+    listener().to(CommandFactoryProvider.class);
   }
 
   private void configureAliases() {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshRemotePeerProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshRemotePeerProvider.java
index 29ede85..2c77360 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshRemotePeerProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshRemotePeerProvider.java
@@ -21,7 +21,7 @@
 import java.net.SocketAddress;
 
 @Singleton
-class SshRemotePeerProvider implements Provider<SocketAddress> {
+public class SshRemotePeerProvider implements Provider<SocketAddress> {
   private final Provider<SshSession> session;
 
   @Inject
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
index 440f236..9067b9b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
@@ -35,7 +35,7 @@
 import java.util.Map;
 
 /** Guice scopes for state during an SSH connection. */
-class SshScope {
+public class SshScope {
   private static final Key<RequestCleanup> RC_KEY =
       Key.get(RequestCleanup.class);
 
@@ -119,7 +119,7 @@
     }
   }
 
-  static class SshSessionProvider implements Provider<SshSession> {
+  public static class SshSessionProvider implements Provider<SshSession> {
     @Override
     public SshSession get() {
       return requireContext().getSession();
@@ -181,7 +181,7 @@
   }
 
   /** Returns exactly one instance per command executed. */
-  static final Scope REQUEST = new Scope() {
+  public static final Scope REQUEST = new Scope() {
     public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
       return new Provider<T>() {
         public T get() {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
index a2f2c1d..62efaa0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshUtil.java
@@ -71,7 +71,7 @@
    * Convert an RFC 4716 style key to an OpenSSH style key.
    *
    * @param keyStr the key string to convert.
-   * @return <code>keyStr</code> if conversion failed; otherwise the converted
+   * @return {@code keyStr} if conversion failed; otherwise the converted
    *         key, in OpenSSH key format.
    */
   public static String toOpenSshPublicKey(final String keyStr) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index c68dc26..fa48084 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -130,16 +130,18 @@
       if (allProjectsName.equals(nameKey)) {
         // Don't allow the wild card project to have a parent.
         //
-        err.append("error: Cannot set parent of '" + name + "'\n");
+        err.append("error: Cannot set parent of '").append(name).append("'\n");
         continue;
       }
 
       if (grandParents.contains(nameKey) || nameKey.equals(newParentKey)) {
         // Try to avoid creating a cycle in the parent pointers.
         //
-        err.append("error: Cycle exists between '" + name + "' and '"
-            + (newParentKey != null ? newParentKey.get() : allProjectsName.get())
-            + "'\n");
+        err.append("error: Cycle exists between '")
+           .append(name)
+           .append("' and '")
+           .append(newParentKey != null ? newParentKey.get() : allProjectsName.get())
+           .append("'\n");
         continue;
       }
 
@@ -155,15 +157,15 @@
           md.close();
         }
       } catch (RepositoryNotFoundException notFound) {
-        err.append("error: Project " + name + " not found\n");
+        err.append("error: Project ").append(name).append(" not found\n");
       } catch (IOException e) {
         final String msg = "Cannot update project " + name;
         log.error(msg, e);
-        err.append("error: " + msg + "\n");
+        err.append("error: ").append(msg).append("\n");
       } catch (ConfigInvalidException e) {
         final String msg = "Cannot update project " + name;
         log.error(msg, e);
-        err.append("error: " + msg + "\n");
+        err.append("error: ").append(msg).append("\n");
       }
 
       projectCache.evict(nameKey);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
index 29250d3..fea16cd 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ApproveOption.java
@@ -24,8 +24,10 @@
 import org.kohsuke.args4j.spi.OneArgumentOptionHandler;
 import org.kohsuke.args4j.spi.OptionHandler;
 import org.kohsuke.args4j.spi.Setter;
+import org.kohsuke.args4j.spi.FieldSetter;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
 
 final class ApproveOption implements Option, Setter<Short> {
   private final String name;
@@ -46,6 +48,16 @@
   }
 
   @Override
+  public String[] depends() {
+    return new String[] {};
+  }
+
+  @Override
+  public boolean hidden() {
+    return false;
+  }
+
+  @Override
   public Class<? extends OptionHandler<Short>> handler() {
     return Handler.class;
   }
@@ -56,11 +68,6 @@
   }
 
   @Override
-  public boolean multiValued() {
-    return false;
-  }
-
-  @Override
   public String name() {
     return name;
   }
@@ -85,6 +92,16 @@
   }
 
   @Override
+  public FieldSetter asFieldSetter() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public AnnotatedElement asAnnotatedElement() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
   public void addValue(final Short val) {
     this.value = val;
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
new file mode 100644
index 0000000..0aa12c4
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.documentation.QueryDocumentationExecutor;
+import com.google.gerrit.server.documentation.QueryDocumentationExecutor.DocQueryException;
+import com.google.gerrit.server.documentation.QueryDocumentationExecutor.DocResult;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+import org.kohsuke.args4j.Argument;
+
+import java.util.List;
+
+@CommandMetaData(name = "apropos", description = "Search in Gerrit documentation",
+  runsAt = MASTER_OR_SLAVE)
+final class AproposCommand extends SshCommand {
+  @Inject
+  private QueryDocumentationExecutor searcher;
+  @Inject
+  @CanonicalWebUrl String url;
+
+  @Argument(index=0, required = true, metaVar = "QUERY")
+  private String q;
+
+  @Override
+  public void run() throws Exception {
+    try {
+      List<QueryDocumentationExecutor.DocResult> res = searcher.doQuery(q);
+      for (DocResult docResult : res) {
+        stdout.println(String.format("%s:\n%s%s\n", docResult.title, url,
+            docResult.url));
+      }
+    } catch (DocQueryException dqe) {
+      throw new UnloggedFailure(1, "fatal: " + dqe.getMessage());
+    }
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
index dc22a29..1d4b900 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.server.git.BanCommit;
 import com.google.gerrit.server.git.BanCommitResult;
@@ -33,7 +35,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@CommandMetaData(name = "ban-commit", description = "Ban a commit from a project's repository")
+@CommandMetaData(name = "ban-commit", description = "Ban a commit from a project's repository",
+  runsAt = MASTER_OR_SLAVE)
 public class BanCommitCommand extends SshCommand {
   @Option(name = "--reason", aliases = {"-r"}, metaVar = "REASON", usage = "reason for banning the commit")
   private String reason;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
new file mode 100644
index 0000000..55f6158
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+import org.kohsuke.args4j.Argument;
+
+/** Create a new branch. **/
+@CommandMetaData(name = "create-branch", description = "Create a new branch")
+final public class CreateBranchCommand extends SshCommand {
+
+  @Argument(index = 0, required = true, metaVar = "PROJECT", usage = "name of the project")
+  private ProjectControl project;
+
+  @Argument(index = 1, required = true, metaVar = "NAME", usage = "name of branch to be created")
+  private String name;
+
+  @Argument(index = 2, required = true, metaVar = "REVISION", usage = "base revision of the new branch")
+  private String revision;
+
+  @Inject
+  GerritApi gApi;
+
+  @Override
+  protected void run() throws UnloggedFailure {
+    try {
+      BranchInput in = new BranchInput();
+      in.revision = revision;
+      gApi.projects().name(project.getProject().getNameKey().get())
+          .branch(name).create(in);
+    } catch (RestApiException e) {
+      throw new UnloggedFailure(1, "fatal: " + e.getMessage(), e);
+    }
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
index c652641..91996b1 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
@@ -18,9 +18,13 @@
 import com.google.gerrit.common.errors.NameAlreadyUsedException;
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.CreateGroupArgs;
 import com.google.gerrit.server.account.PerformCreateGroup;
+import com.google.gerrit.server.validators.GroupCreationValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.gwtorm.server.OrmException;
@@ -69,15 +73,29 @@
   @Inject
   private PerformCreateGroup.Factory performCreateGroupFactory;
 
+  @Inject
+  private DynamicSet<GroupCreationValidationListener> groupCreationValidationListeners;
+
   @Override
   protected void run() throws Failure, OrmException {
     try {
-      performCreateGroupFactory.create().createGroup(groupName,
-          groupDescription,
-          visibleToAll,
-          ownerGroupId,
-          initialMembers,
-          initialGroups);
+      CreateGroupArgs args = new CreateGroupArgs();
+      args.setGroupName(groupName);
+      args.groupDescription = groupDescription;
+      args.visibleToAll = visibleToAll;
+      args.ownerGroupId = ownerGroupId;
+      args.initialMembers = initialMembers;
+      args.initialGroups = initialGroups;
+
+      for (GroupCreationValidationListener l : groupCreationValidationListeners) {
+        try {
+          l.validateNewGroup(args);
+        } catch (ValidationException e) {
+          die(e);
+        }
+      }
+
+      performCreateGroupFactory.create(args).createGroup();
     } catch (PermissionDeniedException e) {
       throw die(e);
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index 24d7689..67d2738 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -14,25 +14,37 @@
 
 package com.google.gerrit.sshd.commands;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.errors.ProjectCreationFailedException;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
-import com.google.gerrit.server.project.PerformCreateProject;
-import com.google.gerrit.server.project.CreateProjectArgs;
+import com.google.gerrit.server.project.CreateProject;
+import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.PutConfig.ConfigValue;
 import com.google.gerrit.server.project.SuggestParentCandidates;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
 
+import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /** Create a new project. **/
 @RequiresCapability(GlobalCapability.CREATE_PROJECT)
@@ -63,9 +75,8 @@
   @Option(name = "--description", aliases = {"-d"}, metaVar = "DESCRIPTION", usage = "description of project")
   private String projectDescription = "";
 
-  @Option(name = "--submit-type", aliases = {"-t"}, usage = "project submit type\n"
-      + "(default: MERGE_IF_NECESSARY)")
-  private SubmitType submitType = SubmitType.MERGE_IF_NECESSARY;
+  @Option(name = "--submit-type", aliases = {"-t"}, usage = "project submit type")
+  private SubmitType submitType;
 
   @Option(name = "--contributor-agreements", usage = "if contributor agreement is required")
   private InheritableBoolean contributorAgreements = InheritableBoolean.INHERIT;
@@ -109,6 +120,9 @@
   @Option(name = "--max-object-size-limit", usage = "max Git object size for this project")
   private String maxObjectSizeLimit;
 
+  @Option(name = "--plugin-config", usage = "plugin configuration parameter with format '<plugin-name>.<parameter-name>=<value>'")
+  private List<String> pluginConfigValues;
+
   private String projectName;
 
   @Argument(index = 0, metaVar = "NAME", usage = "name of project to be created")
@@ -121,35 +135,49 @@
   }
 
   @Inject
-  private PerformCreateProject.Factory factory;
+  private Provider<CreateProject.Factory> createProjectFactory;
 
   @Inject
   private SuggestParentCandidates.Factory suggestParentCandidatesFactory;
 
   @Override
-  protected void run() throws Exception {
+  protected void run() throws UnloggedFailure {
     try {
       if (!suggestParent) {
         if (projectName == null) {
           throw new UnloggedFailure(1, "fatal: Project name is required.");
         }
-        final CreateProjectArgs args = new CreateProjectArgs();
-        args.setProjectName(projectName);
-        args.ownerIds = ownerIds;
-        args.newParent = newParent;
-        args.permissionsOnly = permissionsOnly;
-        args.projectDescription = projectDescription;
-        args.submitType = submitType;
-        args.contributorAgreements = contributorAgreements;
-        args.signedOffBy = signedOffBy;
-        args.contentMerge = contentMerge;
-        args.changeIdRequired = requireChangeID;
-        args.branch = branch;
-        args.createEmptyCommit = createEmptyCommit;
-        args.maxObjectSizeLimit = maxObjectSizeLimit;
 
-        final PerformCreateProject createProject = factory.create(args);
-        createProject.createProject();
+        CreateProject.Input input = new CreateProject.Input();
+        input.name = projectName;
+        if (ownerIds != null) {
+          input.owners = Lists.transform(ownerIds,
+            new Function<AccountGroup.UUID, String>() {
+              @Override
+              public String apply(AccountGroup.UUID uuid) {
+                return uuid.get();
+              }
+            });
+        }
+        if (newParent != null) {
+          input.parent = newParent.getProject().getName();
+        }
+        input.permissionsOnly = permissionsOnly;
+        input.description = projectDescription;
+        input.submitType = submitType;
+        input.useContributorAgreements = contributorAgreements;
+        input.useSignedOffBy = signedOffBy;
+        input.useContentMerge = contentMerge;
+        input.requireChangeId = requireChangeID;
+        input.branches = branch;
+        input.createEmptyCommit = createEmptyCommit;
+        input.maxObjectSizeLimit = maxObjectSizeLimit;
+        if (pluginConfigValues != null) {
+          input.pluginConfigValues = parsePluginConfigValues(pluginConfigValues);
+        }
+
+        createProjectFactory.get().create(projectName)
+            .apply(TopLevelResource.INSTANCE, input);
       } else {
         List<Project.NameKey> parentCandidates =
             suggestParentCandidatesFactory.create().getNameKeys();
@@ -158,8 +186,41 @@
           stdout.print(parent + "\n");
         }
       }
-    } catch (ProjectCreationFailedException err) {
+    } catch (RestApiException | ProjectCreationFailedException | IOException
+        | NoSuchProjectException | OrmException err) {
       throw new UnloggedFailure(1, "fatal: " + err.getMessage(), err);
     }
   }
+
+  @VisibleForTesting
+  Map<String, Map<String, ConfigValue>> parsePluginConfigValues(
+      List<String> pluginConfigValues) throws UnloggedFailure {
+    Map<String, Map<String, ConfigValue>> m = new HashMap<>();
+    for (String pluginConfigValue : pluginConfigValues) {
+      String[] s = pluginConfigValue.split("=");
+      String[] s2 = s[0].split("\\.");
+      if (s.length != 2 || s2.length != 2) {
+        throw new UnloggedFailure(1, "Invalid plugin config value '"
+            + pluginConfigValue
+            + "', expected format '<plugin-name>.<parameter-name>=<value>'"
+            + " or '<plugin-name>.<parameter-name>=<value1,value2,...>'");
+      }
+      ConfigValue value = new ConfigValue();
+      String v = s[1];
+      if (v.contains(",")) {
+        value.values = Lists.newArrayList(Splitter.on(",").split(v));
+      } else {
+        value.value = v;
+      }
+      String pluginName = s2[0];
+      String paramName = s2[1];
+      Map<String, ConfigValue> l = m.get(pluginName);
+      if (l == null) {
+        l = new HashMap<>();
+        m.put(pluginName, l);
+      }
+      l.put(paramName, value);
+    }
+    return m;
+  }
 }
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 521103b..f905c5b 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
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.sshd.commands;
 
-import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.sshd.CommandModule;
 import com.google.gerrit.sshd.CommandName;
 import com.google.gerrit.sshd.Commands;
@@ -22,20 +21,21 @@
 import com.google.gerrit.sshd.SuExec;
 
 
-/** Register the basic commands any Gerrit server should support. */
+/** Register the commands a Gerrit server supports. */
 public class DefaultCommandModule extends CommandModule {
+  public DefaultCommandModule(boolean slave) {
+    slaveMode = slave;
+  }
+
   @Override
   protected void configure() {
     final CommandName git = Commands.named("git");
     final CommandName gerrit = Commands.named("gerrit");
     final CommandName plugin = Commands.named(gerrit, "plugin");
-
-    // The following commands can be ran on a server in either Master or Slave
-    // mode. If a command should only be used on a server in one mode, but not
-    // both, it should be bound in both MasterCommandModule and
-    // SlaveCommandModule.
+    final CommandName testSubmit = Commands.named(gerrit, "test-submit");
 
     command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
+    command(gerrit, AproposCommand.class);
     command(gerrit, BanCommitCommand.class);
     command(gerrit, FlushCaches.class);
     command(gerrit, ListProjectsCommand.class);
@@ -49,7 +49,6 @@
     command(gerrit, StreamEvents.class);
     command(gerrit, VersionCommand.class);
     command(gerrit, GarbageCollectionCommand.class);
-
     command(gerrit, "plugin").toProvider(new DispatchCommandProvider(plugin));
 
     command(plugin, PluginLsCommand.class);
@@ -61,26 +60,43 @@
     alias(plugin, "rm", PluginRemoveCommand.class);
 
     command(git).toProvider(new DispatchCommandProvider(git));
-    command(git, "receive-pack").to(Commands.key(gerrit, "receive-pack"));
-    command(git, "upload-pack").to(Upload.class);
 
     command("ps").to(ShowQueue.class);
     command("kill").to(KillCommand.class);
     command("scp").to(ScpCommand.class);
 
     // Honor the legacy hyphenated forms as aliases for the non-hyphenated forms
-    //
     command("git-upload-pack").to(Commands.key(git, "upload-pack"));
-    command("git-receive-pack").to(Commands.key(git, "receive-pack"));
-    command("gerrit-receive-pack").to(Commands.key(git, "receive-pack"));
-
+    command(git, "upload-pack").to(Upload.class);
     command("suexec").to(SuExec.class);
+    listener().to(ShowCaches.StartupListener.class);
 
-    install(new LifecycleModule() {
-      @Override
-      protected void configure() {
-        listener().to(ShowCaches.StartupListener.class);
-      }
-    });
+    // The following commands can only be ran on a server in Master mode
+    command(gerrit, CreateAccountCommand.class);
+    command(gerrit, CreateGroupCommand.class);
+    command(gerrit, CreateProjectCommand.class);
+    command(gerrit, AdminQueryShell.class);
+    if (!slaveMode) {
+      command("git-receive-pack").to(Commands.key(git, "receive-pack"));
+      command("gerrit-receive-pack").to(Commands.key(git, "receive-pack"));
+      command(git, "receive-pack").to(Commands.key(gerrit, "receive-pack"));
+      command(gerrit, "test-submit").toProvider(
+          new DispatchCommandProvider(testSubmit));
+    }
+    command(gerrit, Receive.class);
+
+    command(gerrit, RenameGroupCommand.class);
+    command(gerrit, ReviewCommand.class);
+    command(gerrit, SetProjectCommand.class);
+    command(gerrit, SetReviewersCommand.class);
+
+    command(gerrit, SetMembersCommand.class);
+    command(gerrit, CreateBranchCommand.class);
+    command(gerrit, SetAccountCommand.class);
+    command(gerrit, AdminSetParent.class);
+
+    command(gerrit, CreateAccountCommand.class);
+    command(testSubmit, TestSubmitRuleCommand.class);
+    command(testSubmit, TestSubmitTypeCommand.class);
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ErrorSlaveMode.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ErrorSlaveMode.java
deleted file mode 100644
index 32c72038..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ErrorSlaveMode.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.sshd.commands;
-
-import com.google.gerrit.sshd.BaseCommand;
-
-import org.apache.sshd.server.Environment;
-
-import java.io.IOException;
-
-/**
- * A command which just throws an error because it shouldn't be ran on this
- * server. This is used when a user tries to run a command on a server in Slave
- * Mode, but the command only applies to the Master server.
- */
-public final class ErrorSlaveMode extends BaseCommand {
-  @Override
-  public void start(final Environment env) {
-    String msg =
-        "error: That command is disabled on this server.\n\n"
-            + "Please use the master server URL.\n";
-    try {
-      err.write(msg.getBytes(ENC));
-      err.flush();
-    } catch (IOException e) {
-      // Ignore errors writing to the client
-    }
-    onExit(1);
-  }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
index 6c07dddb..40152b0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.cache.Cache;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -31,7 +33,8 @@
 
 /** Causes the caches to purge all entries and reload. */
 @RequiresCapability(GlobalCapability.FLUSH_CACHES)
-@CommandMetaData(name = "flush-caches", description = "Flush some/all server caches from memory")
+@CommandMetaData(name = "flush-caches", description = "Flush some/all server caches from memory",
+  runsAt = MASTER_OR_SLAVE)
 final class FlushCaches extends CacheCommand {
   private static final String WEB_SESSIONS = "web_sessions";
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
index 346bea7..821701d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GarbageCollectionResult;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -37,12 +39,16 @@
 
 /** Runs the Git garbage collection. */
 @RequiresCapability(GlobalCapability.RUN_GC)
-@CommandMetaData(name = "gc", description = "Run Git garbage collection")
+@CommandMetaData(name = "gc", description = "Run Git garbage collection",
+  runsAt = MASTER_OR_SLAVE)
 public class GarbageCollectionCommand extends BaseCommand {
 
   @Option(name = "--all", usage = "runs the Git garbage collection for all projects")
   private boolean all;
 
+  @Option(name = "--show-progress", usage = "progress information is shown")
+  private boolean showProgress;
+
   @Argument(index = 0, required = false, multiValued = true, metaVar = "NAME",
       usage = "projects for which the Git garbage collection should be run")
   private List<ProjectControl> projects = new ArrayList<ProjectControl>();
@@ -95,7 +101,7 @@
     }
 
     GarbageCollectionResult result =
-        garbageCollectionFactory.create().run(projectNames, stdout);
+        garbageCollectionFactory.create().run(projectNames, showProgress ? stdout : null);
     if (result.hasErrors()) {
       for (GarbageCollectionResult.Error e : result.getErrors()) {
         String msg;
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 aadb1d9..f0169a8 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,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.Url;
@@ -37,7 +39,8 @@
 
 import java.io.PrintWriter;
 
-@CommandMetaData(name = "ls-groups", description = "List groups visible to the caller")
+@CommandMetaData(name = "ls-groups", description = "List groups visible to the caller",
+  runsAt = MASTER_OR_SLAVE)
 public class ListGroupsCommand extends BaseCommand {
   @Inject
   private MyListGroups impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
index 7d34b1a..f8cf8dd 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -39,7 +41,8 @@
 /**
  * Implements a command that allows the user to see the members of a group.
  */
-@CommandMetaData(name = "ls-members", description = "List the members of a given group")
+@CommandMetaData(name = "ls-members", description = "List the members of a given group",
+  runsAt = MASTER_OR_SLAVE)
 public class ListMembersCommand extends BaseCommand {
   @Inject
   ListMembersCommandImpl impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
index 8bcae4b..78034fc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.server.project.ListProjects;
 import com.google.gerrit.sshd.BaseCommand;
 import com.google.gerrit.sshd.CommandMetaData;
@@ -23,7 +25,8 @@
 
 import java.util.List;
 
-@CommandMetaData(name = "ls-projects", description = "List projects visible to the caller")
+@CommandMetaData(name = "ls-projects", description = "List projects visible to the caller",
+  runsAt = MASTER_OR_SLAVE)
 final class ListProjectsCommand extends BaseCommand {
   @Inject
   private ListProjects impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
index b071e3d..c41fcdc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
 import static org.eclipse.jgit.lib.RefDatabase.ALL;
 
 import com.google.gerrit.common.data.GlobalCapability;
@@ -42,7 +43,8 @@
 import java.util.Map;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "ls-user-refs", description = "List refs visible to a specific user")
+@CommandMetaData(name = "ls-user-refs", description = "List refs visible to a specific user",
+  runsAt = MASTER_OR_SLAVE)
 public class LsUserRefs extends SshCommand {
   @Inject
   private AccountResolver accountResolver;
@@ -75,7 +77,7 @@
 
   @Override
   protected void run() throws Failure {
-    Account userAccount = null;
+    Account userAccount;
     try {
       userAccount = accountResolver.find(userName);
     } catch (OrmException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
deleted file mode 100644
index a8d5c4d..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.sshd.commands;
-
-import com.google.gerrit.sshd.CommandModule;
-import com.google.gerrit.sshd.CommandName;
-import com.google.gerrit.sshd.Commands;
-import com.google.gerrit.sshd.DispatchCommandProvider;
-
-
-/** Register the commands a Gerrit server in master mode supports. */
-public class MasterCommandModule extends CommandModule {
-  @Override
-  protected void configure() {
-    final CommandName gerrit = Commands.named("gerrit");
-    final CommandName testSubmit = Commands.named(gerrit, "test-submit");
-
-    command(gerrit, CreateAccountCommand.class);
-    command(gerrit, CreateGroupCommand.class);
-    command(gerrit, RenameGroupCommand.class);
-    command(gerrit, CreateProjectCommand.class);
-    command(gerrit, AdminQueryShell.class);
-    command(gerrit, SetReviewersCommand.class);
-    command(gerrit, Receive.class);
-    command(gerrit, AdminSetParent.class);
-    command(gerrit, ReviewCommand.class);
-    command(gerrit, SetAccountCommand.class);
-    command(gerrit, SetMembersCommand.class);
-    command(gerrit, SetProjectCommand.class);
-
-    command(gerrit, "test-submit").toProvider(new DispatchCommandProvider(testSubmit));
-    command(testSubmit, TestSubmitRuleCommand.class);
-    command(testSubmit, TestSubmitTypeCommand.class);
-  }
-}
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
index 47c2d68..1d3ee9f 100644
--- 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
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -28,7 +30,8 @@
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "enable", description = "Enable plugins")
+@CommandMetaData(name = "enable", description = "Enable plugins",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginEnableCommand extends SshCommand {
   @Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin(s) to enable")
   List<String> names;
@@ -38,6 +41,9 @@
 
   @Override
   protected void run() throws UnloggedFailure {
+    if (!loader.isRemoteAdminEnabled()) {
+      throw die("remote plugin administration is disabled");
+    }
     if (names != null && !names.isEmpty()) {
       try {
         loader.enablePlugins(Sets.newHashSet(names));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
index 70d09ee..49120f7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.base.Strings;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -35,7 +37,8 @@
 import java.net.URL;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "install", description = "Install/Add a plugin")
+@CommandMetaData(name = "install", description = "Install/Add a plugin",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginInstallCommand extends SshCommand {
   @Option(name = "--name", aliases = {"-n"}, usage = "install under name")
   private String name;
@@ -53,6 +56,9 @@
 
   @Override
   protected void run() throws UnloggedFailure {
+    if (!loader.isRemoteAdminEnabled()) {
+      throw die("remote installation is disabled");
+    }
     if (Strings.isNullOrEmpty(source)) {
       throw die("Argument \"-|URL\" is required");
     }
@@ -94,7 +100,9 @@
       throw die("cannot install plugin");
     } catch (PluginInstallException e) {
       e.printStackTrace(stderr);
-      throw die("plugin failed to install");
+      String msg =
+          String.format("Plugin failed to install. Cause: %s", e.getMessage());
+      throw die(msg);
     } finally {
       try {
         data.close();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
index 7e44641..9f6bb50 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.plugins.ListPlugins;
@@ -26,7 +28,8 @@
 import java.io.IOException;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "ls", description = "List the installed plugins")
+@CommandMetaData(name = "ls", description = "List the installed plugins",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginLsCommand extends BaseCommand {
   @Inject
   private ListPlugins impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
index 3ed1011..4157515 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
@@ -14,10 +14,12 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.plugins.InvalidPluginException;
 import com.google.gerrit.server.plugins.PluginInstallException;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.plugins.PluginLoader;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
@@ -28,7 +30,8 @@
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "reload", description = "Reload/Restart plugins")
+@CommandMetaData(name = "reload", description = "Reload/Restart plugins",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginReloadCommand extends SshCommand {
   @Argument(index = 0, metaVar = "NAME", usage = "plugins to reload/restart")
   private List<String> names;
@@ -38,6 +41,9 @@
 
   @Override
   protected void run() throws UnloggedFailure {
+    if (!loader.isRemoteAdminEnabled()) {
+      throw die("remote plugin administration is disabled");
+    }
     if (names == null || names.isEmpty()) {
       loader.rescan();
     } else {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
index 0ae11af..8e87b0f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -27,7 +29,8 @@
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "remove", description = "Disable plugins")
+@CommandMetaData(name = "remove", description = "Disable plugins",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginRemoveCommand extends SshCommand {
   @Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin to remove")
   List<String> names;
@@ -36,7 +39,10 @@
   private PluginLoader loader;
 
   @Override
-  protected void run() {
+  protected void run() throws UnloggedFailure {
+    if (!loader.isRemoteAdminEnabled()) {
+      throw die("remote plugin administration is disabled");
+    }
     if (names != null && !names.isEmpty()) {
       loader.disablePlugins(Sets.newHashSet(names));
     }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
index 185bb67..2bda15d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.server.query.change.QueryProcessor;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
@@ -24,7 +26,8 @@
 
 import java.util.List;
 
-@CommandMetaData(name = "query", description = "Query the change database")
+@CommandMetaData(name = "query", description = "Query the change database",
+  runsAt = MASTER_OR_SLAVE)
 class Query extends SshCommand {
   @Inject
   private QueryProcessor processor;
@@ -72,6 +75,11 @@
     processor.setIncludeDependencies(on);
   }
 
+  @Option(name = "--all-reviewers", usage = "Include all reviewers")
+  void setAllReviewers(boolean on) {
+    processor.setIncludeAllReviewers(on);
+  }
+
   @Option(name = "--submit-records", usage = "Include submit and label status")
   void setSubmitRecords(boolean on) {
     processor.setIncludeSubmitRecords(on);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
index 5649843..77c5df4 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
@@ -51,7 +51,7 @@
   }
 
   public static enum OutputFormat {
-    PRETTY, JSON, JSON_SINGLE;
+    PRETTY, JSON, JSON_SINGLE
   }
 
   private final BufferedReader in;
@@ -455,16 +455,15 @@
     }
 
     int rowCnt = 0;
-    final int colCnt = columnMap.length;
     while (alreadyOnRow || rs.next()) {
       final JsonObject row = new JsonObject();
       final JsonObject cols = new JsonObject();
-      for (int c = 0; c < colCnt; c++) {
-        String v = columnMap[c].apply(rs);
+      for (Function function : columnMap) {
+        String v = function.apply(rs);
         if (v == null) {
           continue;
         }
-        cols.addProperty(columnMap[c].name.toLowerCase(), v);
+        cols.addProperty(function.name.toLowerCase(), v);
       }
       row.addProperty("type", "row");
       row.add("columns", cols);
@@ -512,7 +511,6 @@
         obj.addProperty("type", "error");
         obj.addProperty("message", "Unsupported Json variant");
         println(obj.toString());
-        return;
     }
   }
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index 31f9301..a3c2cf5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -14,11 +14,14 @@
 
 package com.google.gerrit.sshd.commands;
 
+import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.Capable;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.AsyncReceiveCommits;
 import com.google.gerrit.server.git.ReceiveCommits;
+import com.google.gerrit.server.git.ReceivePackInitializer;
 import com.google.gerrit.server.git.TransferConfig;
 import com.google.gerrit.server.git.VisibleRefFilter;
 import com.google.gerrit.sshd.AbstractGitCommand;
@@ -30,6 +33,8 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.transport.AdvertiseRefsHook;
+import org.eclipse.jgit.transport.PostReceiveHook;
+import org.eclipse.jgit.transport.PostReceiveHookChain;
 import org.eclipse.jgit.transport.ReceivePack;
 import org.kohsuke.args4j.Option;
 import org.slf4j.Logger;
@@ -59,15 +64,21 @@
   @Inject
   private TransferConfig config;
 
+  @Inject
+  private DynamicSet<ReceivePackInitializer> receivePackInitializers;
+
+  @Inject
+  private DynamicSet<PostReceiveHook> postReceiveHooks;
+
   private final Set<Account.Id> reviewerId = new HashSet<Account.Id>();
   private final Set<Account.Id> ccId = new HashSet<Account.Id>();
 
-  @Option(name = "--reviewer", aliases = {"--re"}, multiValued = true, metaVar = "EMAIL", usage = "request reviewer for change(s)")
+  @Option(name = "--reviewer", aliases = {"--re"}, metaVar = "EMAIL", usage = "request reviewer for change(s)")
   void addReviewer(final Account.Id id) {
     reviewerId.add(id);
   }
 
-  @Option(name = "--cc", aliases = {}, multiValued = true, metaVar = "EMAIL", usage = "CC user on change(s)")
+  @Option(name = "--cc", aliases = {}, metaVar = "EMAIL", usage = "CC user on change(s)")
   void addCC(final Account.Id id) {
     ccId.add(id);
   }
@@ -97,6 +108,9 @@
     rp.setTimeout(config.getTimeout());
     rp.setMaxObjectSizeLimit(config.getEffectiveMaxObjectSizeLimit(
         projectControl.getProjectState()));
+    init(rp);
+    rp.setPostReceiveHook(PostReceiveHookChain.newChain(
+        Lists.newArrayList(postReceiveHooks)));
     try {
       rp.receive(in, out, err);
     } catch (UnpackException badStream) {
@@ -105,8 +119,8 @@
       // we want to present this error to the user
       if (badStream.getCause() instanceof TooLargeObjectInPackException) {
         StringBuilder msg = new StringBuilder();
-        msg.append("Receive error on project \""
-            + projectControl.getProject().getName() + "\"");
+        msg.append("Receive error on project \"")
+           .append(projectControl.getProject().getName()).append("\"");
         msg.append(" (user ");
         msg.append(currentUser.getAccount().getUserName());
         msg.append(" account ");
@@ -121,10 +135,10 @@
       // Log what the heck is going on, as detailed as we can.
       //
       StringBuilder msg = new StringBuilder();
-      msg.append("Unpack error on project \""
-          + projectControl.getProject().getName() + "\":\n");
+      msg.append("Unpack error on project \"")
+         .append(projectControl.getProject().getName()).append("\":\n");
 
-      msg.append("  AdvertiseRefsHook: " + rp.getAdvertiseRefsHook());
+      msg.append("  AdvertiseRefsHook: ").append(rp.getAdvertiseRefsHook());
       if (rp.getAdvertiseRefsHook() == AdvertiseRefsHook.DEFAULT) {
         msg.append("DEFAULT");
       } else if (rp.getAdvertiseRefsHook() instanceof VisibleRefFilter) {
@@ -136,10 +150,10 @@
 
       if (rp.getAdvertiseRefsHook() instanceof VisibleRefFilter) {
         Map<String, Ref> adv = rp.getAdvertisedRefs();
-        msg.append("  Visible references (" + adv.size() + "):\n");
+        msg.append("  Visible references (").append(adv.size()).append("):\n");
         for (Ref ref : adv.values()) {
-          msg.append("  - " + ref.getObjectId().abbreviate(8).name() + " "
-              + ref.getName() + "\n");
+          msg.append("  - ").append(ref.getObjectId().abbreviate(8).name())
+             .append(" ").append(ref.getName()).append("\n");
         }
 
         Map<String, Ref> allRefs =
@@ -151,10 +165,10 @@
           }
         }
 
-        msg.append("  Hidden references (" + hidden.size() + "):\n");
+        msg.append("  Hidden references (").append(hidden.size()).append("):\n");
         for (Ref ref : hidden) {
-          msg.append("  - " + ref.getObjectId().abbreviate(8).name() + " "
-              + ref.getName() + "\n");
+          msg.append("  - ").append(ref.getObjectId().abbreviate(8).name())
+              .append(" ").append(ref.getName()).append("\n");
         }
       }
 
@@ -163,6 +177,12 @@
     }
   }
 
+  private void init(ReceivePack rp) {
+    for (ReceivePackInitializer initializer : receivePackInitializers) {
+      initializer.init(projectControl.getProject().getNameKey(), rp);
+    }
+  }
+
   private void verifyProjectVisible(final String type, final Set<Account.Id> who)
       throws UnloggedFailure {
     for (final Account.Id id : who) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index e5eb567..e30d41b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -14,35 +14,31 @@
 
 package com.google.gerrit.sshd.commands;
 
-import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelValue;
-import com.google.gerrit.common.data.ReviewResult;
-import com.google.gerrit.common.data.ReviewResult.Error.Type;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.changes.AbandonInput;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.api.changes.RestoreInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.Abandon;
-import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.change.DeleteDraftPatchSet;
-import com.google.gerrit.server.change.PostReview;
-import com.google.gerrit.server.change.Restore;
-import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.change.Submit;
-import com.google.gerrit.server.changedetail.PublishDraft;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.util.LabelVote;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.gerrit.util.cli.CmdLineParser;
@@ -93,9 +89,15 @@
   @Option(name = "--project", aliases = "-p", usage = "project containing the specified patch set(s)")
   private ProjectControl projectControl;
 
+  @Option(name = "--branch", aliases = "-b", usage = "branch containing the specified patch set(s)")
+  private String branch;
+
   @Option(name = "--message", aliases = "-m", usage = "cover message to publish on change(s)", metaVar = "MESSAGE")
   private String changeComment;
 
+  @Option(name = "--notify", aliases = "-n", usage = "Who to send email notifications to after the review is stored.", metaVar = "NOTIFYHANDLING")
+  private NotifyHandling notify;
+
   @Option(name = "--abandon", usage = "abandon the specified change(s)")
   private boolean abandonChange;
 
@@ -113,49 +115,22 @@
 
   @Option(name = "--label", aliases = "-l", usage = "custom label(s) to assign", metaVar = "LABEL=VALUE")
   void addLabel(final String token) {
-    List<String> parts = ImmutableList.copyOf(Splitter.on('=').split(token));
-    if (parts.size() != 2) {
-      throw new IllegalArgumentException("invalid custom label " + token);
-    }
-    short value;
-    try {
-      value = Short.parseShort(parts.get(1));
-    } catch (IllegalArgumentException e) {
-      throw new IllegalArgumentException("invalid custom label value "
-          + parts.get(1));
-    }
-    customLabels.put(parts.get(0), value);
+    LabelVote v = LabelVote.parseWithEquals(token);
+    LabelType.checkName(v.getLabel()); // Disallow SUBM.
+    customLabels.put(v.getLabel(), v.getValue());
   }
 
   @Inject
   private ReviewDb db;
 
   @Inject
-  private DeleteDraftPatchSet deleteDraftPatchSetImpl;
-
-  @Inject
   private ProjectControl.Factory projectControlFactory;
 
   @Inject
   private AllProjectsName allProjects;
 
   @Inject
-  private ChangeControl.Factory changeControlFactory;
-
-  @Inject
-  private Provider<Abandon> abandonProvider;
-
-  @Inject
-  private Provider<PostReview> reviewProvider;
-
-  @Inject
-  private PublishDraft.Factory publishDraftFactory;
-
-  @Inject
-  private Provider<Restore> restoreProvider;
-
-  @Inject
-  private Provider<Submit> submitProvider;
+  private Provider<GerritApi> gApi;
 
   private List<ApproveOption> optionList;
   private Map<String, Short> customLabels;
@@ -212,10 +187,12 @@
     }
   }
 
-  private void applyReview(final ChangeControl ctl, final PatchSet patchSet,
-      final PostReview.Input review) throws Exception {
-    reviewProvider.get().apply(new RevisionResource(
-        new ChangeResource(ctl), patchSet), review);
+  private void applyReview(PatchSet patchSet,
+      final ReviewInput review) throws Exception {
+    gApi.get().changes()
+        .id(patchSet.getId().getParentKey().get())
+        .revision(patchSet.getRevision().get())
+        .review(review);
   }
 
   private void approveOne(final PatchSet patchSet) throws Exception {
@@ -224,10 +201,11 @@
       changeComment = "";
     }
 
-    PostReview.Input review = new PostReview.Input();
+    ReviewInput review = new ReviewInput();
     review.message = Strings.emptyToNull(changeComment);
+    review.notify = notify;
     review.labels = Maps.newTreeMap();
-    review.drafts = PostReview.DraftHandling.PUBLISH;
+    review.drafts = ReviewInput.DraftHandling.PUBLISH;
     review.strictLabels = false;
     for (ApproveOption ao : optionList) {
       Short v = ao.value();
@@ -245,54 +223,28 @@
     }
 
     try {
-      ChangeControl ctl =
-          changeControlFactory.controlFor(patchSet.getId().getParentKey());
-
       if (abandonChange) {
-        final Abandon abandon = abandonProvider.get();
-        final Abandon.Input input = new Abandon.Input();
+        AbandonInput input = new AbandonInput();
         input.message = changeComment;
-        applyReview(ctl, patchSet, review);
-        try {
-          abandon.apply(new ChangeResource(ctl), input);
-        } catch (AuthException e) {
-          writeError("error: " + parseError(Type.ABANDON_NOT_PERMITTED) + "\n");
-        } catch (ResourceConflictException e) {
-          writeError("error: " + parseError(Type.CHANGE_IS_CLOSED) + "\n");
-        }
+        applyReview(patchSet, review);
+        changeApi(patchSet).abandon(input);
       } else if (restoreChange) {
-        final Restore restore = restoreProvider.get();
-        final Restore.Input input = new Restore.Input();
+        RestoreInput input = new RestoreInput();
         input.message = changeComment;
-        try {
-          restore.apply(new ChangeResource(ctl), input);
-          applyReview(ctl, patchSet, review);
-        } catch (AuthException e) {
-          writeError("error: " + parseError(Type.RESTORE_NOT_PERMITTED) + "\n");
-        } catch (ResourceConflictException e) {
-          writeError("error: " + parseError(Type.CHANGE_NOT_ABANDONED) + "\n");
-        }
+        changeApi(patchSet).restore(input);
+        applyReview(patchSet, review);
       } else {
-        applyReview(ctl, patchSet, review);
+        applyReview(patchSet, review);
       }
 
       if (submitChange) {
-        Submit submit = submitProvider.get();
-        Submit.Input input = new Submit.Input();
-        input.waitForMerge = true;
-        submit.apply(new RevisionResource(
-            new ChangeResource(ctl), patchSet),
-          input);
+        revisionApi(patchSet).submit();
       }
 
       if (publishPatchSet) {
-        final ReviewResult result =
-            publishDraftFactory.create(patchSet.getId()).call();
-        handleReviewResultErrors(result);
+        revisionApi(patchSet).publish();
       } else if (deleteDraftPatchSet) {
-        deleteDraftPatchSetImpl.apply(new RevisionResource(
-            new ChangeResource(ctl), patchSet),
-            new DeleteDraftPatchSet.Input());
+        revisionApi(patchSet).delete();
       }
     } catch (InvalidChangeOperationException e) {
       throw error(e.getMessage());
@@ -304,49 +256,17 @@
       throw error(e.getMessage());
     } catch (ResourceConflictException e) {
       throw error(e.getMessage());
+    } catch (RestApiException e) {
+      throw error(e.getMessage());
     }
   }
 
-  private void handleReviewResultErrors(final ReviewResult result) {
-    for (ReviewResult.Error resultError : result.getErrors()) {
-      String errMsg = "error: (change " + result.getChangeId() + ") ";
-      errMsg += parseError(resultError.getType());
-      if (resultError.getMessage() != null) {
-        errMsg += ": " + resultError.getMessage();
-      }
-      writeError(errMsg);
-    }
+  private ChangeApi changeApi(PatchSet patchSet) throws RestApiException {
+    return gApi.get().changes().id(patchSet.getId().getParentKey().get());
   }
 
-  private String parseError(Type type) {
-    switch (type) {
-      case ABANDON_NOT_PERMITTED:
-        return "not permitted to abandon change";
-      case RESTORE_NOT_PERMITTED:
-        return "not permitted to restore change";
-      case SUBMIT_NOT_PERMITTED:
-        return "not permitted to submit change";
-      case SUBMIT_NOT_READY:
-        return "approvals or dependencies lacking";
-      case CHANGE_IS_CLOSED:
-        return "change is closed";
-      case CHANGE_NOT_ABANDONED:
-        return "change is not abandoned";
-      case PUBLISH_NOT_PERMITTED:
-        return "not permitted to publish change";
-      case DELETE_NOT_PERMITTED:
-        return "not permitted to delete change/patch set";
-      case RULE_ERROR:
-        return "rule error";
-      case NOT_A_DRAFT:
-        return "change/patch set is not a draft";
-      case GIT_ERROR:
-        return "error writing change to git repository";
-      case DEST_BRANCH_NOT_FOUND:
-        return "destination branch not found";
-      default:
-        return "failure in review";
-    }
+  private RevisionApi revisionApi(PatchSet patchSet) throws RestApiException {
+    return changeApi(patchSet).revision(patchSet.getRevision().get());
   }
 
   private PatchSet parsePatchSet(final String patchIdentity)
@@ -365,7 +285,7 @@
       final Set<PatchSet> matches = new HashSet<PatchSet>();
       for (final PatchSet ps : patches) {
         final Change change = db.changes().get(ps.getId().getParentKey());
-        if (inProject(change)) {
+        if (inProject(change) && inBranch(change)) {
           matches.add(ps);
         }
       }
@@ -393,12 +313,16 @@
       if (patchSet == null) {
         throw error("\"" + patchIdentity + "\" no such patch set");
       }
-      if (projectControl != null) {
+      if (projectControl != null || branch != null) {
         final Change change = db.changes().get(patchSetId.getParentKey());
         if (!inProject(change)) {
           throw error("change " + change.getId() + " not in project "
               + projectControl.getProject().getName());
         }
+        if (!inBranch(change)) {
+          throw error("change " + change.getId() + " not in branch "
+              + change.getDest().get());
+        }
       }
       return patchSet;
     }
@@ -414,6 +338,14 @@
     return projectControl.getProject().getNameKey().equals(change.getProject());
   }
 
+  private boolean inBranch(final Change change) {
+    if (branch == null) {
+      // No --branch option, so they want every branch.
+      return true;
+    }
+    return change.getDest().get().equals(branch);
+  }
+
   @Override
   protected void parseCommandLine() throws UnloggedFailure {
     optionList = new ArrayList<ApproveOption>();
@@ -427,7 +359,7 @@
     }
 
     for (LabelType type : allProjectsControl.getLabelTypes().getLabelTypes()) {
-      String usage = "";
+      String usage;
       usage = "score for " + type.getName() + "\n";
 
       for (LabelValue v : type.getValues()) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java
index 987380f..65f876f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java
@@ -203,7 +203,7 @@
         buf.append(TYPE_FILE);
         break;
     }
-    buf.append("0" + Integer.toOctalString(dir.getMode())); // perms
+    buf.append("0").append(Integer.toOctalString(dir.getMode())); // perms
     buf.append(" ");
     buf.append(len); // length
     buf.append(" ");
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 5736bb6..f3f35db 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -70,16 +70,16 @@
   @Option(name = "--inactive", usage = "set account's state to inactive")
   private boolean inactive;
 
-  @Option(name = "--add-email", multiValued = true, metaVar = "EMAIL", usage = "email addresses to add to the account")
+  @Option(name = "--add-email", metaVar = "EMAIL", usage = "email addresses to add to the account")
   private List<String> addEmails = new ArrayList<String>();
 
-  @Option(name = "--delete-email", multiValued = true, metaVar = "EMAIL", usage = "email addresses to delete from the account")
+  @Option(name = "--delete-email", metaVar = "EMAIL", usage = "email addresses to delete from the account")
   private List<String> deleteEmails = new ArrayList<String>();
 
-  @Option(name = "--add-ssh-key", multiValued = true, metaVar = "-|KEY", usage = "public keys to add to the account")
+  @Option(name = "--add-ssh-key", metaVar = "-|KEY", usage = "public keys to add to the account")
   private List<String> addSshKeys = new ArrayList<String>();
 
-  @Option(name = "--delete-ssh-key", multiValued = true, metaVar = "-|KEY", usage = "public keys to delete from the account")
+  @Option(name = "--delete-ssh-key", metaVar = "-|KEY", usage = "public keys to delete from the account")
   private List<String> deleteSshKeys = new ArrayList<String>();
 
   @Option(name = "--http-password", metaVar = "PASSWORD", usage = "password for HTTP authentication for the account")
@@ -287,7 +287,7 @@
   private List<String> readSshKey(final List<String> sshKeys)
       throws UnsupportedEncodingException, IOException {
     if (!sshKeys.isEmpty()) {
-      String sshKey = "";
+      String sshKey;
       int idx = sshKeys.indexOf("-");
       if (idx >= 0) {
         sshKey = "";
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
index b45fb3a..e0c2a97 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -160,15 +160,15 @@
         md.close();
       }
     } catch (RepositoryNotFoundException notFound) {
-      err.append("error: Project " + name + " not found\n");
+      err.append("error: Project ").append(name).append(" not found\n");
     } catch (IOException e) {
       final String msg = "Cannot update project " + name;
       log.error(msg, e);
-      err.append("error: " + msg + "\n");
+      err.append("error: ").append(msg).append("\n");
     } catch (ConfigInvalidException e) {
       final String msg = "Cannot update project " + name;
       log.error(msg, e);
-      err.append("error: " + msg + "\n");
+      err.append("error: ").append(msg).append("\n");
     }
     projectCache.evict(ctlProject);
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
index c1facef..41d3e97 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.sshd.commands;
 
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
@@ -21,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.ChangesCollection;
 import com.google.gerrit.server.change.DeleteReviewer;
 import com.google.gerrit.server.change.PostReviewers;
 import com.google.gerrit.server.change.ReviewerResource;
@@ -87,6 +89,9 @@
   @Inject
   private ChangeControl.Factory changeControlFactory;
 
+  @Inject
+  private ChangesCollection changesCollection;
+
   private Set<Account.Id> toRemove = new HashSet<Account.Id>();
   private Set<Change.Id> changes = new HashSet<Change.Id>();
 
@@ -109,8 +114,7 @@
   }
 
   private boolean modifyOne(Change.Id changeId) throws Exception {
-    ChangeResource changeRsrc =
-        new ChangeResource(changeControlFactory.validateFor(changeId));
+    ChangeResource changeRsrc = changesCollection.parse(changeId);
     boolean ok = true;
 
     // Remove reviewers
@@ -118,7 +122,7 @@
     DeleteReviewer delete = deleteReviewerProvider.get();
     for (Account.Id reviewer : toRemove) {
       ReviewerResource rsrc = reviewerFactory.create(changeRsrc, reviewer);
-      String error = null;;
+      String error = null;
       try {
         delete.apply(rsrc, new DeleteReviewer.Input());
       } catch (ResourceNotFoundException e) {
@@ -137,14 +141,12 @@
     //
     PostReviewers post = postReviewersProvider.get();
     for (String reviewer : toAdd) {
-      PostReviewers.Input input = new PostReviewers.Input();
+      AddReviewerInput input = new AddReviewerInput();
       input.reviewer = reviewer;
       input.confirmed = true;
       String error;
       try {
         error = post.apply(changeRsrc, input).error;
-      } catch (ResourceNotFoundException e) {
-        error = String.format("could not add %s: not found", reviewer);
       } catch (Exception e) {
         error = String.format("could not add %s: %s", reviewer, e.getMessage());
       }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
index ff1de80..397120f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheStats;
 import com.google.common.collect.Maps;
@@ -54,7 +56,8 @@
 
 /** Show the current cache states. */
 @RequiresCapability(GlobalCapability.VIEW_CACHES)
-@CommandMetaData(name = "show-caches", description = "Display current cache statistics")
+@CommandMetaData(name = "show-caches", description = "Display current cache statistics",
+  runsAt = MASTER_OR_SLAVE)
 final class ShowCaches extends CacheCommand {
   private static volatile long serverStarted;
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index d97d750..17bea45 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.CurrentUser;
@@ -46,7 +48,8 @@
 
 /** Show the current SSH connections. */
 @RequiresCapability(GlobalCapability.VIEW_CONNECTIONS)
-@CommandMetaData(name = "show-connections", description = "Display active client SSH connections")
+@CommandMetaData(name = "show-connections", description = "Display active client SSH connections",
+  runsAt = MASTER_OR_SLAVE)
 final class ShowConnections extends SshCommand {
   @Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names")
   private boolean numeric;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index afb5787..77d79b3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
+import com.google.common.base.Objects;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.TaskInfoFactory;
@@ -42,7 +45,8 @@
 
 /** Display the current work queue. */
 @AdminHighPriorityCommand
-@CommandMetaData(name = "show-queue", description = "Display the background work queues")
+@CommandMetaData(name = "show-queue", description = "Display the background work queues",
+  runsAt = MASTER_OR_SLAVE)
 final class ShowQueue extends SshCommand {
   @Option(name = "--wide", aliases = {"-w"}, usage = "display without line width truncation")
   private boolean wide;
@@ -136,14 +140,17 @@
             id(taskInfo.getTaskId()), start, startTime, "",
             taskInfo.getTaskString(taskNameWidth)));
       } else if (regularUserCanSee) {
-        if (remoteName == null) {
-          remoteName = projectName.get();
-        } else {
-          remoteName = remoteName + "/" + projectName;
+        if (projectName != null) {
+          if (remoteName == null) {
+            remoteName = projectName.get();
+          } else {
+            remoteName = remoteName + "/" + projectName.get();
+          }
         }
 
-        stdout.print(String.format("%8s %-12s %-4s %s\n", //
-            id(taskInfo.getTaskId()), start, startTime, remoteName));
+        stdout.print(String.format("%8s %-12s %-4s %s\n",
+            id(taskInfo.getTaskId()), start, startTime,
+            Objects.firstNonNull(remoteName, "n/a")));
       }
     }
     stdout.print("----------------------------------------------"
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java
deleted file mode 100644
index ac0eb0d..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.sshd.commands;
-
-import com.google.gerrit.sshd.CommandModule;
-import com.google.gerrit.sshd.CommandName;
-import com.google.gerrit.sshd.Commands;
-
-
-/** Register the commands a Gerrit server in slave mode supports. */
-public class SlaveCommandModule extends CommandModule {
-  @Override
-  protected void configure() {
-    final CommandName gerrit = Commands.named("gerrit");
-
-    command(gerrit, "create-account").to(ErrorSlaveMode.class);
-    command(gerrit, "create-group").to(ErrorSlaveMode.class);
-    command(gerrit, "create-project").to(ErrorSlaveMode.class);
-    command(gerrit, "gsql").to(ErrorSlaveMode.class);
-    command(gerrit, "receive-pack").to(ErrorSlaveMode.class);
-    command(gerrit, "rename-group").to(ErrorSlaveMode.class);
-    command(gerrit, "review").to(ErrorSlaveMode.class);
-    command(gerrit, "set-project-parent").to(ErrorSlaveMode.class);
-    command(gerrit, "set-reviewers").to(ErrorSlaveMode.class);
-  }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
index fd4a9ec..c55c7ed 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.ChangeListener;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -36,7 +38,8 @@
 import java.util.concurrent.LinkedBlockingQueue;
 
 @RequiresCapability(GlobalCapability.STREAM_EVENTS)
-@CommandMetaData(name = "stream-events", description = "Monitor events occurring in real time")
+@CommandMetaData(name = "stream-events", description = "Monitor events occurring in real time",
+  runsAt = MASTER_OR_SLAVE)
 final class StreamEvents extends BaseCommand {
   /** Maximum number of events that may be queued up for each connection. */
   private static final int MAX_EVENTS = 128;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java
index 19888c8..50f880d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java
@@ -14,11 +14,14 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.Version;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 
-@CommandMetaData(name = "version", description = "Display gerrit version")
+@CommandMetaData(name = "version", description = "Display gerrit version",
+  runsAt = MASTER_OR_SLAVE)
 final class VersionCommand extends SshCommand {
 
   @Override
diff --git a/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java b/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
new file mode 100644
index 0000000..a22bae2
--- /dev/null
+++ b/gerrit-sshd/src/test/java/com/google/gerrit/sshd/commands/ProjectConfigParamParserTest.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNull;
+
+import com.google.gerrit.server.project.PutConfig.ConfigValue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class ProjectConfigParamParserTest {
+
+  private CreateProjectCommand cmd;
+  @Before
+  public void setUp() {
+    cmd = new CreateProjectCommand();
+  }
+
+  @Test
+  public void parseSingleValue() throws Exception {
+    String in = "a.b=c";
+    Map<String, Map<String, ConfigValue>> r =
+        cmd.parsePluginConfigValues(Collections.singletonList(in));
+    ConfigValue configValue = r.get("a").get("b");
+    assertEquals("c", configValue.value);
+    assertNull(configValue.values);
+  }
+
+  @Test
+  public void parseMultipleValue() throws Exception {
+    String in = "a.b=c,d,e";
+    Map<String, Map<String, ConfigValue>> r =
+        cmd.parsePluginConfigValues(Collections.singletonList(in));
+    ConfigValue configValue = r.get("a").get("b");
+    assertArrayEquals(new String[] {"c", "d", "e"}, configValue.values.toArray());
+    assertNull(configValue.value);
+  }
+}
diff --git a/gerrit-util-cli/.gitignore b/gerrit-util-cli/.gitignore
deleted file mode 100644
index 35069e7..0000000
--- a/gerrit-util-cli/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-util-cli.iml
\ No newline at end of file
diff --git a/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs b/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index e9441bb..0000000
--- a/gerrit-util-cli/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-util-cli/.settings/org.eclipse.core.runtime.prefs b/gerrit-util-cli/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-util-cli/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-util-cli/.settings/org.eclipse.jdt.core.prefs b/gerrit-util-cli/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 941fb31..0000000
--- a/gerrit-util-cli/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,270 +0,0 @@
-#Thu Jul 28 11:02:36 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=ignore
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-util-cli/.settings/org.eclipse.jdt.ui.prefs b/gerrit-util-cli/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-util-cli/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-util-cli/BUCK b/gerrit-util-cli/BUCK
index 7e43cc4..8cdc2dc 100644
--- a/gerrit-util-cli/BUCK
+++ b/gerrit-util-cli/BUCK
@@ -2,6 +2,7 @@
   name = 'cli',
   srcs = glob(['src/main/java/**/*.java']),
   deps = [
+    '//gerrit-common:annotations',
     '//gerrit-common:server',
     '//lib:args4j',
     '//lib:guava',
diff --git a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
index b75635f..7fc6d6f 100644
--- a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -52,10 +52,13 @@
 import org.kohsuke.args4j.spi.EnumOptionHandler;
 import org.kohsuke.args4j.spi.OptionHandler;
 import org.kohsuke.args4j.spi.Setter;
+import org.kohsuke.args4j.spi.FieldSetter;
+
 
 import java.io.StringWriter;
 import java.io.Writer;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -381,6 +384,16 @@
     }
 
     @Override
+    public String[] depends() {
+      return new String[] {};
+    }
+
+    @Override
+    public boolean hidden() {
+      return false;
+    }
+
+    @Override
     public String usage() {
       return "display this help text";
     }
@@ -401,11 +414,6 @@
     }
 
     @Override
-    public boolean multiValued() {
-      return false;
-    }
-
-    @Override
     public boolean required() {
       return false;
     }
@@ -416,13 +424,23 @@
     }
 
     @Override
+    public FieldSetter asFieldSetter() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AnnotatedElement asAnnotatedElement() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     public Class<Boolean> getType() {
       return Boolean.class;
     }
 
     @Override
     public boolean isMultiValued() {
-      return multiValued();
+      return false;
     }
   }
 }
diff --git a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/OptionHandlers.java b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/OptionHandlers.java
index 756a885..50cad12 100644
--- a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/OptionHandlers.java
+++ b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/OptionHandlers.java
@@ -73,9 +73,8 @@
     return (Class<?>) p.getActualTypeArguments()[0];
   }
 
+  @SuppressWarnings("unchecked")
   private static Binding<OptionHandlerFactory<?>> cast(Binding<?> e) {
-    @SuppressWarnings("unchecked")
-    Binding<OptionHandlerFactory<?>> b = (Binding<OptionHandlerFactory<?>>) e;
-    return b;
+    return (Binding<OptionHandlerFactory<?>>) e;
   }
 }
diff --git a/gerrit-util-ssl/.gitignore b/gerrit-util-ssl/.gitignore
deleted file mode 100644
index e552ad5..0000000
--- a/gerrit-util-ssl/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-util-ssl.iml
\ No newline at end of file
diff --git a/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs b/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index e9441bb..0000000
--- a/gerrit-util-ssl/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-util-ssl/.settings/org.eclipse.core.runtime.prefs b/gerrit-util-ssl/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-util-ssl/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-util-ssl/.settings/org.eclipse.jdt.core.prefs b/gerrit-util-ssl/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 5f73a7f..0000000
--- a/gerrit-util-ssl/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:35 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-util-ssl/.settings/org.eclipse.jdt.ui.prefs b/gerrit-util-ssl/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-util-ssl/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-war/.gitignore b/gerrit-war/.gitignore
deleted file mode 100644
index dc8c7ad..0000000
--- a/gerrit-war/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/target
-/.classpath
-/.project
-/.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
-/gerrit-war.iml
\ No newline at end of file
diff --git a/gerrit-war/.settings/org.eclipse.core.resources.prefs b/gerrit-war/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index abdea9ac..0000000
--- a/gerrit-war/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding//src/main/resources=UTF-8
-encoding/<project>=UTF-8
diff --git a/gerrit-war/.settings/org.eclipse.core.runtime.prefs b/gerrit-war/.settings/org.eclipse.core.runtime.prefs
deleted file mode 100644
index 8667cfd..0000000
--- a/gerrit-war/.settings/org.eclipse.core.runtime.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-#Tue Sep 02 16:59:24 PDT 2008
-eclipse.preferences.version=1
-line.separator=\n
diff --git a/gerrit-war/.settings/org.eclipse.jdt.core.prefs b/gerrit-war/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index a2d8fcb..0000000
--- a/gerrit-war/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,269 +0,0 @@
-#Thu Jul 28 11:02:37 PDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.source=1.6
-org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_assignment=16
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
-org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
-org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
-org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
-org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
-org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
-org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
-org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
-org.eclipse.jdt.core.formatter.blank_lines_after_package=1
-org.eclipse.jdt.core.formatter.blank_lines_before_field=0
-org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
-org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
-org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
-org.eclipse.jdt.core.formatter.blank_lines_before_method=1
-org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
-org.eclipse.jdt.core.formatter.blank_lines_before_package=0
-org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
-org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
-org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
-org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
-org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
-org.eclipse.jdt.core.formatter.comment.format_block_comments=true
-org.eclipse.jdt.core.formatter.comment.format_header=true
-org.eclipse.jdt.core.formatter.comment.format_html=true
-org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
-org.eclipse.jdt.core.formatter.comment.format_line_comments=true
-org.eclipse.jdt.core.formatter.comment.format_source_code=true
-org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
-org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
-org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
-org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
-org.eclipse.jdt.core.formatter.comment.line_length=80
-org.eclipse.jdt.core.formatter.compact_else_if=true
-org.eclipse.jdt.core.formatter.continuation_indentation=2
-org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
-org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
-org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
-org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_empty_lines=false
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
-org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
-org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
-org.eclipse.jdt.core.formatter.indentation.size=4
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
-org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
-org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
-org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
-org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
-org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
-org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
-org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
-org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
-org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
-org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
-org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
-org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
-org.eclipse.jdt.core.formatter.lineSplit=80
-org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
-org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
-org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
-org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
-org.eclipse.jdt.core.formatter.tabulation.char=space
-org.eclipse.jdt.core.formatter.tabulation.size=2
-org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-war/.settings/org.eclipse.jdt.ui.prefs b/gerrit-war/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index d4218a5..0000000
--- a/gerrit-war/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-#Wed Jul 29 11:31:38 PDT 2009
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-formatter_profile=_Google Format
-formatter_settings_version=11
-org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
-org.eclipse.jdt.ui.ondemandthreshold=99
-org.eclipse.jdt.ui.staticondemandthreshold=99
-org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=false
-sp_cleanup.format_source_code=false
-sp_cleanup.format_source_code_changes_only=false
-sp_cleanup.make_local_variable_final=true
-sp_cleanup.make_parameters_final=true
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=false
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=false
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_blocks=false
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index fa9a88f..cde128e 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -9,6 +9,7 @@
     '//gerrit-httpd:httpd',
     '//gerrit-lucene:lucene',
     '//gerrit-openid:openid',
+    '//gerrit-pgm:init-api',
     '//gerrit-pgm:init-base',
     '//gerrit-reviewdb:server',
     '//gerrit-server:server',
@@ -22,7 +23,7 @@
     '//lib/log:api',
     '//lib/jgit:jgit',
   ],
-  compile_deps = ['//lib:servlet-api-3_0'],
+  compile_deps = ['//lib:servlet-api-3_1'],
   visibility = [
     '//:',
     '//gerrit-gwtdebug:gwtdebug',
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/ReviewDbDataSourceProvider.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/ReviewDbDataSourceProvider.java
index 52467a0..86e7596 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/ReviewDbDataSourceProvider.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/ReviewDbDataSourceProvider.java
@@ -72,7 +72,6 @@
       Class<?> type = Class.forName("com.mchange.v2.c3p0.DataSources");
       if (type.isInstance(ds)) {
         type.getMethod("destroy", DataSource.class).invoke(null, ds);
-        return;
       }
     } catch (Throwable bad) {
       // Oh well, its not a c3p0 pooled connection.
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
index 0a9386d..9665cdd 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.httpd;
 
 import com.google.gerrit.pgm.BaseInit;
+import com.google.gerrit.pgm.init.PluginsDistribution;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -24,6 +25,7 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.List;
 
 public final class SiteInitializer {
   private static final Logger LOG = LoggerFactory
@@ -31,10 +33,15 @@
 
   private final String sitePath;
   private final String initPath;
+  private final PluginsDistribution pluginsDistribution;
+  private final List<String> pluginsToInstall;
 
-  SiteInitializer(String sitePath, String initPath) {
+  SiteInitializer(String sitePath, String initPath,
+      PluginsDistribution pluginsDistribution, List<String> pluginsToInstall) {
     this.sitePath = sitePath;
     this.initPath = initPath;
+    this.pluginsDistribution = pluginsDistribution;
+    this.pluginsToInstall = pluginsToInstall;
   }
 
   public void init() {
@@ -43,7 +50,7 @@
         File site = new File(sitePath);
         LOG.info(String.format("Initializing site at %s",
             site.getAbsolutePath()));
-        new BaseInit(site, false).run();
+        new BaseInit(site, false, true, pluginsDistribution, pluginsToInstall).run();
         return;
       }
 
@@ -56,7 +63,8 @@
         if (site != null) {
           LOG.info(String.format("Initializing site at %s",
               site.getAbsolutePath()));
-          new BaseInit(site, new ReviewDbDataSourceProvider(), false).run();
+          new BaseInit(site, new ReviewDbDataSourceProvider(), false, false,
+              pluginsDistribution, pluginsToInstall).run();
         }
       } finally {
         conn.close();
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java
new file mode 100644
index 0000000..665d420
--- /dev/null
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd;
+
+import static com.google.gerrit.pgm.init.InitPlugins.JAR;
+import static com.google.gerrit.pgm.init.InitPlugins.PLUGIN_DIR;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.pgm.init.PluginsDistribution;
+import com.google.inject.Singleton;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+
+@Singleton
+class UnzippedDistribution implements PluginsDistribution {
+
+  private ServletContext servletContext;
+  private File pluginsDir;
+
+  public UnzippedDistribution(ServletContext servletContext) {
+    this.servletContext = servletContext;
+  }
+
+  @Override
+  public void foreach(Processor processor) throws FileNotFoundException, IOException {
+    File[] list = getPluginsDir().listFiles();
+    if (list != null) {
+      for (File p : list) {
+        String pluginJarName = p.getName();
+        String pluginName = pluginJarName.substring(0,
+                pluginJarName.length() - JAR.length());
+        try (InputStream in = new FileInputStream(p)) {
+          processor.process(pluginName, in);
+        }
+      }
+    }
+  }
+
+  @Override
+  public List<String> listPluginNames() throws FileNotFoundException {
+    List<String> names = Lists.newArrayList();
+    String[] list = getPluginsDir().list();
+    if (list != null) {
+      for (String pluginJarName : list) {
+        String pluginName = pluginJarName.substring(0,
+            pluginJarName.length() - JAR.length());
+        names.add(pluginName);
+      }
+    }
+    return names;
+  }
+
+  private File getPluginsDir() {
+    if (pluginsDir == null) {
+      File root = new File(servletContext.getRealPath(""));
+      pluginsDir = new File(root, PLUGIN_DIR);
+    }
+    return pluginsDir;
+  }
+}
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 5936911..78d5fff 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -17,6 +17,7 @@
 import static com.google.inject.Scopes.SINGLETON;
 import static com.google.inject.Stage.PRODUCTION;
 
+import com.google.common.base.Splitter;
 import com.google.gerrit.common.ChangeHookRunner;
 import com.google.gerrit.httpd.auth.openid.OpenIdModule;
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
@@ -26,6 +27,7 @@
 import com.google.gerrit.reviewdb.client.AuthType;
 import com.google.gerrit.server.account.InternalAccountDirectory;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.change.MergeabilityChecksExecutorModule;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.AuthConfigModule;
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
@@ -39,7 +41,6 @@
 import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.index.NoIndexModule;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.patch.IntraLineWorkerPool;
@@ -51,11 +52,13 @@
 import com.google.gerrit.server.schema.DatabaseModule;
 import com.google.gerrit.server.schema.SchemaModule;
 import com.google.gerrit.server.schema.SchemaVersionCheck;
+import com.google.gerrit.server.ssh.NoSshModule;
+import com.google.gerrit.server.ssh.SshAddressesModule;
 import com.google.gerrit.solr.SolrIndexModule;
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
-import com.google.gerrit.sshd.commands.MasterCommandModule;
+import com.google.gerrit.sshd.commands.DefaultCommandModule;
 import com.google.inject.AbstractModule;
 import com.google.inject.CreationException;
 import com.google.inject.Guice;
@@ -81,6 +84,7 @@
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
 import javax.servlet.ServletContextEvent;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
@@ -103,6 +107,8 @@
   private LifecycleManager manager;
   private GuiceFilter filter;
 
+  private ServletContext servletContext;
+
   @Override
   public void doFilter(ServletRequest req, ServletResponse res,
       FilterChain chain) throws IOException, ServletException {
@@ -117,7 +123,16 @@
       }
 
       if (System.getProperty("gerrit.init") != null) {
-        new SiteInitializer(path, System.getProperty("gerrit.init_path")).init();
+        List<String> pluginsToInstall;
+        String installPlugins = System.getProperty("gerrit.install_plugins");
+        if (installPlugins == null) {
+          pluginsToInstall = null;
+        } else {
+          pluginsToInstall = Splitter.on(",").trimResults().omitEmptyStrings()
+              .splitToList(installPlugins);
+        }
+        new SiteInitializer(path, System.getProperty("gerrit.init_path"),
+            new UnzippedDistribution(servletContext), pluginsToInstall).init();
       }
 
       try {
@@ -143,12 +158,16 @@
 
       cfgInjector = createCfgInjector();
       sysInjector = createSysInjector();
-      sshInjector = createSshInjector();
+      if (!sshdOff()) {
+        sshInjector = createSshInjector();
+      }
       webInjector = createWebInjector();
 
       PluginGuiceEnvironment env = sysInjector.getInstance(PluginGuiceEnvironment.class);
       env.setCfgInjector(cfgInjector);
-      env.setSshInjector(sshInjector);
+      if (sshInjector != null) {
+        env.setSshInjector(sshInjector);
+      }
       env.setHttpInjector(webInjector);
 
       // Push the Provider<HttpServletRequest> down into the canonical
@@ -169,11 +188,18 @@
       manager.add(dbInjector);
       manager.add(cfgInjector);
       manager.add(sysInjector);
-      manager.add(sshInjector);
+      if (sshInjector != null) {
+        manager.add(sshInjector);
+      }
       manager.add(webInjector);
     }
   }
 
+  private boolean sshdOff() {
+    Config cfg = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
+    return new SshAddressesModule().getListenAddresses(cfg).isEmpty();
+  }
+
   private Injector createDbInjector() {
     final List<Module> modules = new ArrayList<Module>();
     if (sitePath != null) {
@@ -196,16 +222,10 @@
       final DataSourceType dst = Guice.createInjector(new DataSourceModule(),
           configModule, sitePathModule).getInstance(
             Key.get(DataSourceType.class, Names.named(dbType.toLowerCase())));
-      modules.add(new AbstractModule() {
-        @Override
-        protected void configure() {
-          bind(DataSourceType.class).toInstance(dst);
-        }
-      });
-
       modules.add(new LifecycleModule() {
         @Override
         protected void configure() {
+          bind(DataSourceType.class).toInstance(dst);
           bind(DataSourceProvider.Context.class).toInstance(
               DataSourceProvider.Context.MULTI_USER);
           bind(Key.get(DataSource.class, Names.named("ReviewDb"))).toProvider(
@@ -256,6 +276,7 @@
     modules.add(new WorkQueue.Module());
     modules.add(new ChangeHookRunner.Module());
     modules.add(new ReceiveCommitsExecutorModule());
+    modules.add(new MergeabilityChecksExecutorModule());
     modules.add(new IntraLineWorkerPool.Module());
     modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
     modules.add(new InternalAccountDirectory.Module());
@@ -272,7 +293,7 @@
         changeIndexModule = new SolrIndexModule();
         break;
       default:
-        changeIndexModule = new NoIndexModule();
+        throw new IllegalStateException("unsupported index.type");
     }
     modules.add(changeIndexModule);
     modules.add(new CanonicalWebUrlModule() {
@@ -296,7 +317,7 @@
     final List<Module> modules = new ArrayList<Module>();
     modules.add(sysInjector.getInstance(SshModule.class));
     modules.add(new SshHostKeyModule());
-    modules.add(new MasterCommandModule());
+    modules.add(new DefaultCommandModule(false));
     return sysInjector.createChildInjector(modules);
   }
 
@@ -305,8 +326,12 @@
     modules.add(RequestContextFilter.module());
     modules.add(AllRequestFilter.module());
     modules.add(sysInjector.getInstance(GitOverHttpModule.class));
-    modules.add(sshInjector.getInstance(WebModule.class));
-    modules.add(sshInjector.getInstance(WebSshGlueModule.class));
+    modules.add(sysInjector.getInstance(WebModule.class));
+    if (sshInjector != null) {
+      modules.add(sshInjector.getInstance(WebSshGlueModule.class));
+    } else {
+      modules.add(new NoSshModule());
+    }
     modules.add(CacheBasedWebSession.module());
     modules.add(HttpContactStoreConnection.module());
     modules.add(new HttpPluginModule());
@@ -327,7 +352,8 @@
 
   @Override
   public void init(FilterConfig cfg) throws ServletException {
-    contextInitialized(new ServletContextEvent(cfg.getServletContext()));
+    servletContext = cfg.getServletContext();
+    contextInitialized(new ServletContextEvent(servletContext));
     init();
     manager.start();
   }
diff --git a/lib/BUCK b/lib/BUCK
index c9549b3..290ed92 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -20,13 +20,14 @@
 define_license(name = 'prologcafe')
 define_license(name = 'protobuf')
 define_license(name = 'slf4j')
+define_license(name = 'xz')
 define_license(name = 'DO_NOT_DISTRIBUTE')
 
 maven_jar(
   name = 'gwtorm',
-  id = 'gwtorm:gwtorm:1.7',
-  bin_sha1 = 'ee3b316a023f1422dd4b6654a3d51d0e5690809c',
-  src_sha1 = 'a145bde4cc87a4ff4cec283880e2a03be32cc868',
+  id = 'gwtorm:gwtorm:1.8',
+  bin_sha1 = '0ec006f69f7b8aa48e22d4bdecea820fd53c0b4b',
+  src_sha1 = 'b0e347a3053328f029c93ac347a4761a98293073',
   license = 'Apache2.0',
   deps = [':protobuf'],
   repository = GERRIT,
@@ -34,9 +35,9 @@
 
 maven_jar(
   name = 'gwtjsonrpc',
-  id = 'gwtjsonrpc:gwtjsonrpc:1.3',
-  bin_sha1 = '1717ba11ab0c5160798c80085220a63f864691d3',
-  src_sha1 = '9e01c5d7bd54f8e70066450b372a43c16404789e',
+  id = 'gwtjsonrpc:gwtjsonrpc:1.5',
+  bin_sha1 = '8995287e2c3c866e826d06993904e2c8d7961e4b',
+  src_sha1 = 'c9461f6c0490f26720e3ff15b5607320eab89d96',
   license = 'Apache2.0',
   repository = GERRIT,
 )
@@ -50,8 +51,8 @@
 
 maven_jar(
   name = 'guava',
-  id = 'com.google.guava:guava:15.0',
-  sha1 = 'ed727a8d9f247e2050281cb083f1c77b09dcb5cd',
+  id = 'com.google.guava:guava:16.0',
+  sha1 = 'aca09d2e5e8416bf91550e72281958e35460be52',
   license = 'Apache2.0',
 )
 
@@ -65,29 +66,29 @@
 
 maven_jar(
   name = 'ow2-asm',
-  id = 'org.ow2.asm:asm:4.0',
-  sha1 = '659add6efc75a4715d738e73f07505246edf4d66',
+  id = 'org.ow2.asm:asm:4.1',
+  sha1 = 'ad568238ee36a820bd6c6806807e8a14ea34684d',
   license = 'ow2',
 )
 
 maven_jar(
   name = 'ow2-asm-analysis',
-  id = 'org.ow2.asm:asm-analysis:4.0',
-  sha1 = '1c45d52b6f6c638db13cf3ac12adeb56b254cdd7',
+  id = 'org.ow2.asm:asm-analysis:4.1',
+  sha1 = '73401033069e4714f57b60aeae02f97210aaa64e',
   license = 'ow2',
 )
 
 maven_jar(
   name = 'ow2-asm-tree',
-  id = 'org.ow2.asm:asm-tree:4.0',
-  sha1 = '67bd266cd17adcee486b76952ece4cc85fe248b8',
+  id = 'org.ow2.asm:asm-tree:4.1',
+  sha1 = '51085abcc4cb6c6e1cb5551e6f999eb8e31c5b2d',
   license = 'ow2',
 )
 
 maven_jar(
   name = 'ow2-asm-util',
-  id = 'org.ow2.asm:asm-util:4.0',
-  sha1 = 'd7a65f54cda284f9706a750c23d64830bb740c39',
+  id = 'org.ow2.asm:asm-util:4.1',
+  sha1 = '6344065cb0f94e2b930a95e6656e040ebc11df08',
   license = 'ow2',
 )
 
@@ -112,17 +113,17 @@
 )
 
 maven_jar(
-  name = 'servlet-api-3_0',
-  id = 'org.apache.tomcat:tomcat-servlet-api:7.0.32',
-  sha1 = 'e2f21e9868414122e6dd23ac66cf304d4290642c',
+  name = 'servlet-api-3_1',
+  id = 'org.apache.tomcat:tomcat-servlet-api:8.0.0-RC10',
+  sha1 = '975935b6203073938dfeeb28e4effc3b094c4fc4',
   license = 'Apache2.0',
   exclude = ['META-INF/NOTICE', 'META-INF/LICENSE'],
 )
 
 maven_jar(
   name = 'args4j',
-  id = 'args4j:args4j:2.0.16',
-  sha1 = '9f00fb12820743b9e05c686eba543d64dd43f2b1',
+  id = 'args4j:args4j:2.0.26',
+  sha1 = '01ebb18ebb3b379a74207d5af4ea7c8338ebd78b',
   license = 'args4j',
 )
 
@@ -151,25 +152,24 @@
 
 maven_jar(
   name = 'pegdown',
-  id = 'org.pegdown:pegdown:1.1.0',
-  sha1 = '00bcc0c5b025b09ab85bb80a8311ce5c015d005b',
+  id = 'org.pegdown:pegdown:1.2.1',
+  sha1 = '47689e060d90f90431b5ab2df911452b93930d8c',
   license = 'Apache2.0',
   deps = [':parboiled-java'],
-  exclude = ['META-INF/LICENSE', 'META-INF/NOTICE'],
 )
 
 maven_jar(
   name = 'parboiled-core',
-  id = 'org.parboiled:parboiled-core:1.1.3',
-  sha1 = '3fc3013adf98701efcc594a1ea99a3f841dc81bb',
+  id = 'org.parboiled:parboiled-core:1.1.6',
+  sha1 = '11bd0c34fc6ac3c3cbf440ab8180cc6422c044e9',
   license = 'Apache2.0',
   attach_source = False,
 )
 
 maven_jar(
   name = 'parboiled-java',
-  id = 'org.parboiled:parboiled-java:1.1.3',
-  sha1 = 'c2bf2935a8b3eca5f998557190cd6eb34f5536d0',
+  id = 'org.parboiled:parboiled-java:1.1.6',
+  sha1 = 'cb2ffa720f75b2fce8cfd1875599319e75ea9557',
   license = 'Apache2.0',
   deps = [
     ':parboiled-core',
@@ -183,8 +183,8 @@
 
 maven_jar(
   name = 'h2',
-  id = 'com.h2database:h2:1.3.173',
-  sha1 = '3d9cc700d2c6b0b7a9bb59bd2b850e6518b6c209',
+  id = 'com.h2database:h2:1.3.174',
+  sha1 = '2fb55391f525bc3ef9f320a379d19350af96a554',
   license = 'h2',
 )
 
@@ -199,9 +199,9 @@
 maven_jar(
   name = 'protobuf',
   # Must match version in gwtorm/pom.xml.
-  id = 'com.google.protobuf:protobuf-java:2.4.1',
-  bin_sha1 = '0c589509ec6fd86d5d2fda37e07c08538235d3b9',
-  src_sha1 = 'e406f69360f2a89cb4aa724ed996a1c5599af383',
+  id = 'com.google.protobuf:protobuf-java:2.5.0',
+  bin_sha1 = 'a10732c76bfacdbd633a7eb0f7968b1059a65dfa',
+  src_sha1 = '7a27a7fc815e481b367ead5df19b4a71ace4a419',
   license = 'protobuf',
 )
 
@@ -223,8 +223,8 @@
 
 maven_jar(
   name = 'easymock',
-  id = 'org.easymock:easymock:3.1',
-  sha1 = '3e127311a86fc2e8f550ef8ee4abe094bbcf7e7e',
+  id = 'org.easymock:easymock:3.2',
+  sha1 = '00c82f7fa3ef377d8954b1db25123944b5af2ba4',
   license = 'DO_NOT_DISTRIBUTE',
   deps = [
     ':cglib-2_2',
@@ -249,3 +249,12 @@
   visibility = ['//lib:easymock'],
   attach_source = False,
 )
+
+maven_jar(
+  name = 'tukaani-xz',
+  id = 'org.tukaani:xz:1.4',
+  sha1 = '18a9a2ce6abf32ea1b5fd31dae5210ad93f4e5e3',
+  license = 'xz',
+  attach_source = False,
+  visibility = ['//lib/jgit:jgit-archive'],
+)
diff --git a/lib/LICENSE-xz b/lib/LICENSE-xz
new file mode 100644
index 0000000..420556e
--- /dev/null
+++ b/lib/LICENSE-xz
@@ -0,0 +1,4 @@
+All the files in this package have been written by Lasse Collin
+and/or Igor Pavlov. All these files have been put into the
+public domain. You can do whatever you want with these files.
+This software is provided "as is", without any warranty.
diff --git a/lib/asciidoctor/BUCK b/lib/asciidoctor/BUCK
index 0b07b69..b1d5933 100644
--- a/lib/asciidoctor/BUCK
+++ b/lib/asciidoctor/BUCK
@@ -31,6 +31,7 @@
   srcs = ['java/DocIndexer.java'],
   deps = [
     ':asciidoc_lib',
+    '//gerrit-server:constants',
     '//lib:args4j',
     '//lib:guava',
     '//lib/lucene:analyzers-common',
diff --git a/lib/asciidoctor/java/AsciiDoctor.java b/lib/asciidoctor/java/AsciiDoctor.java
index 0613ff4..933b929 100644
--- a/lib/asciidoctor/java/AsciiDoctor.java
+++ b/lib/asciidoctor/java/AsciiDoctor.java
@@ -12,6 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import com.google.common.io.ByteStreams;
+
+import org.asciidoctor.Asciidoctor;
+import org.asciidoctor.AttributesBuilder;
+import org.asciidoctor.Options;
+import org.asciidoctor.OptionsBuilder;
+import org.asciidoctor.SafeMode;
+import org.asciidoctor.internal.JRubyAsciidoctor;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -23,20 +36,6 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
-import com.google.common.io.ByteStreams;
-
-import org.asciidoctor.Asciidoctor;
-import org.asciidoctor.AttributesBuilder;
-import org.asciidoctor.Options;
-import org.asciidoctor.OptionsBuilder;
-import org.asciidoctor.SafeMode;
-import org.asciidoctor.internal.JRubyAsciidoctor;
-
-import org.kohsuke.args4j.Argument;
-import org.kohsuke.args4j.CmdLineException;
-import org.kohsuke.args4j.CmdLineParser;
-import org.kohsuke.args4j.Option;
-
 public class AsciiDoctor {
 
   private static final String DOCTYPE = "article";
@@ -54,6 +53,9 @@
   @Option(name = "--out-ext", usage = "extension for output files")
   private String outExt = ".html";
 
+  @Option(name = "--tmp", usage = "temporary output path")
+  private File tmpdir;
+
   @Option(name = "-a", usage =
       "a list of attributes, in the form key or key=value pair")
   private List<String> attributes = new ArrayList<String>();
@@ -135,31 +137,17 @@
         // have to add css files into the SRCS.
         continue;
       }
-      String outName = mapInFileToOutFile(inputFile, inExt, outExt);
-      File out = new File(outName);
-      Options options = createOptions(out);
-      renderInput(options, inputFile);
 
+      String outName = mapInFileToOutFile(inputFile, inExt, outExt);
+      File out = new File(tmpdir, outName);
+      out.getParentFile().mkdirs();
+      Options options = createOptions(out);
+      renderInput(options, new File(inputFile));
       zipFile(out, outName, zip);
     }
     zip.close();
   }
 
-  public static void zipDir(File dir, String prefix, ZipOutputStream zip)
-      throws IOException {
-    for (File file : dir.listFiles()) {
-      String name = file.getName();
-      if (!prefix.isEmpty()) {
-        name = prefix + "/" + name;
-      }
-      if (file.isDirectory()) {
-        zipDir(file, name, zip);
-      } else {
-        zipFile(file, name, zip);
-      }
-    }
-  }
-
   public static void zipFile(File file, String name, ZipOutputStream zip)
       throws IOException {
     zip.putNextEntry(new ZipEntry(name));
@@ -169,9 +157,9 @@
     zip.closeEntry();
   }
 
-  private void renderInput(Options options, String inputFile) {
+  private void renderInput(Options options, File inputFile) {
     Asciidoctor asciidoctor = JRubyAsciidoctor.create();
-    asciidoctor.renderFile(new File(inputFile), options);
+    asciidoctor.renderFile(inputFile, options);
   }
 
   public static void main(String[] args) {
diff --git a/lib/asciidoctor/java/DocIndexer.java b/lib/asciidoctor/java/DocIndexer.java
index 497cba5..0cb785c 100644
--- a/lib/asciidoctor/java/DocIndexer.java
+++ b/lib/asciidoctor/java/DocIndexer.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import com.google.common.io.Files;
+import com.google.gerrit.server.documentation.Constants;
 
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.analysis.util.CharArraySet;
@@ -23,7 +23,8 @@
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
-import org.apache.lucene.store.NIOFSDirectory;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.Version;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.CmdLineException;
@@ -31,24 +32,30 @@
 import org.kohsuke.args4j.Option;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
 public class DocIndexer {
-  private static final Version LUCENE_VERSION = Version.LUCENE_44;
-  private static final String DOC_FIELD = "doc";
-  private static final String URL_FIELD = "url";
-  private static final String TITLE_FIELD = "title";
+  private static final Version LUCENE_VERSION = Version.LUCENE_46;
+  private static final Pattern SECTION_HEADER = Pattern.compile("^=+ (.*)");
 
-  @Option(name = "-z", usage = "output zip file")
-  private String zipFile;
+  @Option(name = "-o", usage = "output JAR file")
+  private String outFile;
 
   @Option(name = "--prefix", usage = "prefix for the html filepath")
   private String prefix = "";
@@ -76,8 +83,20 @@
       return;
     }
 
-    File tmp = Files.createTempDir();
-    NIOFSDirectory directory = new NIOFSDirectory(tmp);
+    byte[] compressedIndex = zip(index());
+    JarOutputStream jar = new JarOutputStream(new FileOutputStream(outFile));
+    JarEntry entry = new JarEntry(
+        String.format("%s/%s", Constants.PACKAGE, Constants.INDEX_ZIP));
+    entry.setSize(compressedIndex.length);
+    jar.putNextEntry(entry);
+    jar.write(compressedIndex);
+    jar.closeEntry();
+    jar.close();
+  }
+
+  private RAMDirectory index() throws IOException,
+      UnsupportedEncodingException, FileNotFoundException {
+    RAMDirectory directory = new RAMDirectory();
     IndexWriterConfig config = new IndexWriterConfig(
         LUCENE_VERSION,
         new StandardAnalyzer(LUCENE_VERSION, CharArraySet.EMPTY_SET));
@@ -95,23 +114,48 @@
         title = titleReader.readLine();
       }
       titleReader.close();
+      Matcher matcher = SECTION_HEADER.matcher(title);
+      if (matcher.matches()) {
+        title = matcher.group(1);
+      }
 
       String outputFile = AsciiDoctor.mapInFileToOutFile(
           inputFile, inExt, outExt);
       FileReader reader = new FileReader(file);
       Document doc = new Document();
-      doc.add(new TextField(DOC_FIELD, reader));
+      doc.add(new TextField(Constants.DOC_FIELD, reader));
       doc.add(new StringField(
-            URL_FIELD, prefix + outputFile, Field.Store.YES));
-      doc.add(new TextField(TITLE_FIELD, title, Field.Store.YES));
+            Constants.URL_FIELD, prefix + outputFile, Field.Store.YES));
+      doc.add(new TextField(Constants.TITLE_FIELD, title, Field.Store.YES));
       iwriter.addDocument(doc);
       reader.close();
     }
     iwriter.close();
+    return directory;
+  }
 
-    ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(zipFile));
-    AsciiDoctor.zipDir(tmp, "", zip);
+  private byte[] zip(RAMDirectory dir) throws IOException {
+    ByteArrayOutputStream buf = new ByteArrayOutputStream();
+    ZipOutputStream zip = new ZipOutputStream(buf);
+
+    for (String name : dir.listAll()) {
+      IndexInput in = dir.openInput(name, null);
+      try {
+        int len = (int) in.length();
+        byte[] tmp = new byte[len];
+        ZipEntry entry = new ZipEntry(name);
+        entry.setSize(len);
+        in.readBytes(tmp, 0, len);
+        zip.putNextEntry(entry);
+        zip.write(tmp, 0, len);
+        zip.closeEntry();
+      } finally {
+        in.close();
+      }
+    }
+
     zip.close();
+    return buf.toByteArray();
   }
 
   public static void main(String[] args) {
diff --git a/lib/bouncycastle/BUCK b/lib/bouncycastle/BUCK
index 3d76ea6..99f960e 100644
--- a/lib/bouncycastle/BUCK
+++ b/lib/bouncycastle/BUCK
@@ -2,19 +2,27 @@
 
 # This version must match the version that also appears in
 # gerrit-pgm/src/main/resources/com/google/gerrit/pgm/libraries.config
-VERSION = '1.44'
+VERSION = '1.49'
 
 maven_jar(
   name = 'bcprov',
-  id = 'org.bouncycastle:bcprov-jdk16:' + VERSION,
-  sha1 = '6327a5f7a3dc45e0fd735adb5d08c5a74c05c20c',
+  id = 'org.bouncycastle:bcprov-jdk15on:' + VERSION,
+  sha1 = 'f5155f04330459104b79923274db5060c1057b99',
   license = 'DO_NOT_DISTRIBUTE', #'bouncycastle'
 )
 
 maven_jar(
   name = 'bcpg',
-  id = 'org.bouncycastle:bcpg-jdk16:' + VERSION,
-  sha1 = 'ee14f5a29cb3cf9c1edec034ab16e1bbd26e9647',
+  id = 'org.bouncycastle:bcpg-jdk15on:' + VERSION,
+  sha1 = '081d84be5b125e1997ab0e2244d1a2276b5de76c',
+  license = 'DO_NOT_DISTRIBUTE', #'bouncycastle'
+  deps = [':bcprov'],
+)
+
+maven_jar(
+  name = 'bcpkix',
+  id = 'org.bouncycastle:bcpkix-jdk15on:' + VERSION,
+  sha1 = '924cc7ad2f589630c97b918f044296ebf1bb6855',
   license = 'DO_NOT_DISTRIBUTE', #'bouncycastle'
   deps = [':bcprov'],
 )
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
index b7b0066..1ba2d92 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -17,7 +17,7 @@
       "echo '*/' >>$OUT",
     ] +
     ['unzip -p $SRCDIR/%s %s/%s >>$OUT' % (ZIP, TOP, n)
-     for n in CM3_CSS]
+     for n in CM3_CSS + CM3_THEMES]
   ),
   srcs = [genfile(ZIP)],
   deps = [':download'],
diff --git a/lib/codemirror/cm3.defs b/lib/codemirror/cm3.defs
index 8de280a..9679b1b 100644
--- a/lib/codemirror/cm3.defs
+++ b/lib/codemirror/cm3.defs
@@ -3,6 +3,15 @@
   'addon/dialog/dialog.css',
 ]
 
+CM3_THEMES = [
+  'theme/eclipse.css',
+  'theme/elegant.css',
+  'theme/midnight.css',
+  'theme/neat.css',
+  'theme/night.css',
+  'theme/twilight.css',
+]
+
 CM3_JS = [
   'lib/codemirror.js',
   'keymap/vim.js',
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
index 6f412e4..ab8a036 100644
--- a/lib/commons/BUCK
+++ b/lib/commons/BUCK
@@ -22,6 +22,15 @@
 )
 
 maven_jar(
+  name = 'compress',
+  id = 'org.apache.commons:commons-compress:1.7',
+  sha1 = 'ab365c96ee9bc88adcc6fa40d185c8e15a31410d',
+  license = 'Apache2.0',
+  exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
+  visibility = ['//lib/jgit:jgit-archive'],
+)
+
+maven_jar(
   name = 'dbcp',
   id = 'commons-dbcp:commons-dbcp:1.4',
   sha1 = '30be73c965cc990b153a100aaaaafcf239f82d39',
@@ -76,6 +85,13 @@
 )
 
 maven_jar(
+  name = 'validator',
+  id = 'commons-validator:commons-validator:1.4.0',
+  sha1 = '42fa1046955ade59f5354a1876cfc523cea33815',
+  license = 'Apache2.0',
+)
+
+maven_jar(
   name = 'httpclient',
   id = 'org.apache.httpcomponents:httpclient:4.2.5',
   bin_sha1 = '666e26e76f2e87d84e4f16acb546481ae1b8e9a6',
diff --git a/lib/guice/BUCK b/lib/guice/BUCK
index 4fac2c0..946bf9d 100644
--- a/lib/guice/BUCK
+++ b/lib/guice/BUCK
@@ -9,11 +9,10 @@
 
 java_library(
   name = 'guice',
-  deps = [
+  exported_deps = [
     ':guice_library',
     ':javax-inject',
   ],
-  export_deps = True,
   visibility = ['PUBLIC'],
 )
 
@@ -23,42 +22,12 @@
   sha1 = 'a82be989679df08b66d48b42659a3ca2daaf1d5b',
   license = 'Apache2.0',
   deps = [':aopalliance'],
+  exclude_java_sources = True,
   exclude = EXCLUDE + [
     'META-INF/maven/com.google.guava/guava/pom.properties',
     'META-INF/maven/com.google.guava/guava/pom.xml',
-    'javax/annotation/CheckForNull.java',
-    'javax/annotation/CheckForSigned.java',
-    'javax/annotation/CheckReturnValue.java',
-    'javax/annotation/concurrent/GuardedBy.java',
-    'javax/annotation/concurrent/Immutable.java',
-    'javax/annotation/concurrent/NotThreadSafe.java',
-    'javax/annotation/concurrent/ThreadSafe.java',
-    'javax/annotation/Detainted.java',
-    'javax/annotation/MatchesPattern.java',
-    'javax/annotation/meta/Exclusive.java',
-    'javax/annotation/meta/Exhaustive.java',
-    'javax/annotation/meta/TypeQualifier.java',
-    'javax/annotation/meta/TypeQualifierDefault.java',
-    'javax/annotation/meta/TypeQualifierNickname.java',
-    'javax/annotation/meta/TypeQualifierValidator.java',
-    'javax/annotation/meta/When.java',
-    'javax/annotation/Nonnegative.java',
-    'javax/annotation/Nonnull.java',
-    'javax/annotation/Nullable.java',
-    'javax/annotation/OverridingMethodsMustInvokeSuper.java',
-    'javax/annotation/ParametersAreNonnullByDefault.java',
-    'javax/annotation/ParametersAreNullableByDefault.java',
-    'javax/annotation/PropertyKey.java',
-    'javax/annotation/RegEx.java',
-    'javax/annotation/Signed.java',
-    'javax/annotation/Syntax.java',
-    'javax/annotation/Tainted.java',
-    'javax/annotation/Untainted.java',
-    'javax/annotation/WillClose.java',
-    'javax/annotation/WillCloseWhenClosed.java',
-    'javax/annotation/WillNotClose.java',
   ],
-  visibility = [],
+  visibility = ['PUBLIC'],
 )
 
 maven_jar(
@@ -92,5 +61,5 @@
   id = 'javax.inject:javax.inject:1',
   sha1 = '6975da39a7040257bd51d21a231b76c915872d38',
   license = 'Apache2.0',
-  visibility = ['//lib/guice:guice'],
+  visibility = ['PUBLIC'],
 )
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
index 0ccb22b..537c6d8 100644
--- a/lib/gwt/BUCK
+++ b/lib/gwt/BUCK
@@ -1,11 +1,11 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '2.5.1'
+VERSION = '2.6.0'
 
 maven_jar(
   name = 'user',
   id = 'com.google.gwt:gwt-user:' + VERSION,
-  sha1 = 'a8afe9b0222db730f4ebd02a1aa329a5395473c5',
+  sha1 = '8237d1bd37127188107388c8724be44a0e9f73ca',
   license = 'Apache2.0',
   attach_source = False,
 )
@@ -13,13 +13,14 @@
 maven_jar(
   name = 'dev',
   id = 'com.google.gwt:gwt-dev:' + VERSION,
-  sha1 = 'ba1f05ddd23b51c0d9c813956ca0ea72cb2e7a92',
+  sha1 = 'af3d9ad2fb8be30dc87fdcd6d9a373b2ab675802',
   license = 'Apache2.0',
   deps = [
     ':javax-validation',
     ':javax-validation_src',
   ],
   attach_source = False,
+  exclude = ['org/eclipse/jetty/*'],
 )
 
 maven_jar(
@@ -39,8 +40,8 @@
 
 maven_jar(
   name = 'gwt-test-utils',
-  id = 'com.googlecode.gwt-test-utils:gwt-test-utils:0.45',
-  sha1 = 'ed16fa85defc685802e11cc61f8bc70454412fdb',
+  id = 'com.googlecode.gwt-test-utils:gwt-test-utils:0.47',
+  sha1 = '284749ed37d8034bac05e374070c09cce88db540',
   license = 'Apache2.0',
   deps = [
     ':javassist',
@@ -51,8 +52,8 @@
 
 maven_jar(
   name = 'javassist',
-  id = 'org.javassist:javassist:3.16.1-GA',
-  sha1 = '315891b371395271977af518d4db5cee1a0bc9bf',
+  id = 'org.javassist:javassist:3.18.1-GA',
+  sha1 = 'd9a09f7732226af26bf99f19e2cffe0ae219db5b',
   license = 'Apache2.0',
   visibility = [],
 )
diff --git a/lib/jetty/BUCK b/lib/jetty/BUCK
index 6eac1a9..9637598 100644
--- a/lib/jetty/BUCK
+++ b/lib/jetty/BUCK
@@ -1,16 +1,16 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '8.1.7.v20120910'
+VERSION = '9.1.0.v20131115'
 EXCLUDE = ['about.html']
 
 maven_jar(
   name = 'servlet',
   id = 'org.eclipse.jetty:jetty-servlet:' + VERSION,
-  sha1 = '93da01e3ea26e70449e9a1a0affa5c31436be5a0',
+  sha1 = 'bf92dbb4426e5d14973726861accda38d93baa64',
   license = 'Apache2.0',
   deps = [
     ':security',
-    '//lib:servlet-api-3_0',
+    '//lib:servlet-api-3_1',
   ],
   exclude = EXCLUDE,
 )
@@ -18,7 +18,7 @@
 maven_jar(
   name = 'security',
   id = 'org.eclipse.jetty:jetty-security:' + VERSION,
-  sha1 = '8d78beb7a07f4cccee05a3f16a264f1025946258',
+  sha1 = '0ec8b3000720c746ba03d96a2172c88124509c57',
   license = 'Apache2.0',
   deps = [':server'],
   exclude = EXCLUDE,
@@ -26,22 +26,43 @@
 )
 
 maven_jar(
+  name = 'webapp',
+  id = 'org.eclipse.jetty:jetty-webapp:' + VERSION,
+  sha1 = '3d98b3197fbe453a8df27c106f12363587439ee3',
+  license = 'Apache2.0',
+  deps = [':xml'],
+  exclude = EXCLUDE,
+  visibility = [
+    '//tools/eclipse:classpath',
+    '//gerrit-gwtdebug:gwtdebug',
+  ],
+)
+
+maven_jar(
+  name = 'xml',
+  id = 'org.eclipse.jetty:jetty-xml:' + VERSION,
+  sha1 = '67e8618447a7740b7a95d74d1b6b4b5c8c1024f5',
+  license = 'Apache2.0',
+  exclude = EXCLUDE,
+  visibility = [],
+)
+
+maven_jar(
   name = 'server',
   id = 'org.eclipse.jetty:jetty-server:' + VERSION,
-  sha1 = '6c81f733f28713919e99c2f8952e6ca5178033cd',
+  sha1 = 'c64cb3ab62ff32fcd8b838369a426c688d901103',
   license = 'Apache2.0',
-  deps = [
+  exported_deps = [
     ':continuation',
     ':http',
   ],
-  export_deps = True,
   exclude = EXCLUDE,
 )
 
 maven_jar(
   name = 'continuation',
   id = 'org.eclipse.jetty:jetty-continuation:' + VERSION,
-  sha1 = 'f60cfe6267038000b459508529c88737601081e4',
+  sha1 = '5751f7ea38488dd32180bd3273f7f8591928aee3',
   license = 'Apache2.0',
   exclude = EXCLUDE,
 )
@@ -49,18 +70,18 @@
 maven_jar(
   name = 'http',
   id = 'org.eclipse.jetty:jetty-http:' + VERSION,
-  sha1 = '10126433876cd74534695f7f99c4362596555493',
+  sha1 = '1ee35683e75298b3fe246befd1cd88b6e6087427',
   license = 'Apache2.0',
-  deps = [':io'],
+  exported_deps = [':io'],
   exclude = EXCLUDE,
 )
 
 maven_jar(
   name = 'io',
   id = 'org.eclipse.jetty:jetty-io:' + VERSION,
-  sha1 = 'a81f746ae1b10c37e1bb0a01d1374c202c0bd549',
+  sha1 = '99e4632a0760f5fb2a110d25df992faf959700d6',
   license = 'Apache2.0',
-  deps = [':util'],
+  exported_deps = [':util'],
   exclude = EXCLUDE,
   visibility = [],
 )
@@ -68,7 +89,7 @@
 maven_jar(
   name = 'util',
   id = 'org.eclipse.jetty:jetty-util:' + VERSION,
-  sha1 = '7eb2004ab2c22fd3b00095bd9ba0f32a9e88f6a5',
+  sha1 = '440fc44218366a7b58739aef4402b4927e135b9c',
   license = 'Apache2.0',
   exclude = EXCLUDE,
   visibility = [],
diff --git a/lib/jgit/BUCK b/lib/jgit/BUCK
index b6fbaf0..80924d9 100644
--- a/lib/jgit/BUCK
+++ b/lib/jgit/BUCK
@@ -1,13 +1,13 @@
 include_defs('//lib/maven.defs')
 
-REPO = ECLIPSE
-VERS = '3.2.0.201312181205-r'
+REPO = MAVEN_CENTRAL
+VERS = '3.3.0.201403021825-r'
 
 maven_jar(
   name = 'jgit',
   id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
-  bin_sha1 = '4b99546e8c8a04597b7a4564003e3b554ec12b5c',
-  src_sha1 = '566b20b4798d9fa31fe33298c493ca9601830b6f',
+  bin_sha1 = '01aa346a5040bd541502dfb40e83edb1d1981c67',
+  src_sha1 = 'c27cc089751cc90dbe085ef09dd0c4a2acdb69cf',
   license = 'jgit',
   repository = REPO,
   unsign = True,
@@ -22,7 +22,7 @@
 maven_jar(
   name = 'jgit-servlet',
   id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
-  sha1 = '65c5c90fc3b65c76cb4ac593e93b5fef8bb43e08',
+  sha1 = 'e141488647b80ef25d8d3febffd434a5e2a0a817',
   license = 'jgit',
   repository = REPO,
   deps = [':jgit'],
@@ -34,9 +34,26 @@
 )
 
 maven_jar(
+  name = 'jgit-archive',
+  id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
+  sha1 = '87b2b50eb6e7a18a70fd684cc173f3bd2d8e24e8',
+  license = 'jgit',
+  repository = REPO,
+  deps = [':jgit',
+    '//lib/commons:compress',
+    '//lib:tukaani-xz',
+  ],
+  unsign = True,
+  exclude = [
+    'about.html',
+    'plugin.properties',
+  ],
+)
+
+maven_jar(
   name = 'junit',
   id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
-  sha1 = '10240657c6675cfa2b709c27ea89834aab200667',
+  sha1 = '13d0303a669bc2c44db69f8581e3634412b70eed',
   license = 'DO_NOT_DISTRIBUTE',
   repository = REPO,
   unsign = True,
@@ -45,8 +62,8 @@
 
 maven_jar(
   name = 'ewah',
-  id = 'com.googlecode.javaewah:JavaEWAH:0.5.6',
-  sha1 = '1207c0fc8552d4f5f574b50f29321d923521128e',
+  id = 'com.googlecode.javaewah:JavaEWAH:0.7.9',
+  sha1 = 'eceaf316a8faf0e794296ebe158ae110c7d72a5a',
   license = 'Apache2.0',
 )
 
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK
index 38ece4c..450a88a 100644
--- a/lib/lucene/BUCK
+++ b/lib/lucene/BUCK
@@ -1,10 +1,12 @@
 include_defs('//lib/maven.defs')
 
+VERSION = '4.6.0'
+
 maven_jar(
   name = 'core',
-  id = 'org.apache.lucene:lucene-core:4.4.0',
-  bin_sha1 = 'a9a0b553d5f2444aea3340b22753ea4bbddaa0af',
-  src_sha1 = 'd321e15f688066a3c3598607303e0de452a076da',
+  id = 'org.apache.lucene:lucene-core:' + VERSION,
+  bin_sha1 = 'f1d974facaea30a3a0c1752a24097af5a7d40e60',
+  src_sha1 = '19d4eb5def4bc2517a00b50f7a875b7ce33988a7',
   license = 'Apache2.0',
   exclude = [
     'META-INF/LICENSE.txt',
@@ -14,9 +16,9 @@
 
 maven_jar(
   name = 'analyzers-common',
-  id = 'org.apache.lucene:lucene-analyzers-common:4.4.0',
-  bin_sha1 = 'f58f6b727293b2d4392064db8c91fdf1d0eb4ffe',
-  src_sha1 = '60176bb63009f41104b42656b20c81b66313e7b5',
+  id = 'org.apache.lucene:lucene-analyzers-common:' + VERSION,
+  bin_sha1 = '25dda6706bcb7a741f25f57cdbec6c2f36adc557',
+  src_sha1 = '04866d0e36e3ef708d099014752ad4fef61d4243',
   license = 'Apache2.0',
   exclude = [
     'META-INF/LICENSE.txt',
@@ -25,25 +27,9 @@
 )
 
 maven_jar(
-  name = 'highlighter',
-  id = 'org.apache.lucene:lucene-highlighter:4.4.0',
-  bin_sha1 = 'c55f402683388c0a71a1dfaaff198873dfe5b1e4',
-  src_sha1 = '3a99f84e4b6a8f74c34b2f2bd076c9b2b46fff2e',
-  license = 'Apache2.0',
-)
-
-maven_jar(
-  name = 'queries',
-  id = 'org.apache.lucene:lucene-queries:4.4.0',
-  bin_sha1 = 'c9010f4852345ba2a65163fdeb17b7b653e4a3c4',
-  src_sha1 = 'eefbcd43e66747a412a9f186d183d187405374b8',
-  license = 'Apache2.0',
-)
-
-maven_jar(
-  name = 'spellchecker',
-  id = 'org.apache.lucene:lucene-spellchecker:3.6.2',
-  bin_sha1 = '15db0c0cfee44e275f15ad046e46b9a05910ad24',
-  src_sha1 = 'bbecb3fb725ae594101c165a72c102296007c203',
+  name = 'query-parser',
+  id = 'org.apache.lucene:lucene-queryparser:' + VERSION,
+  bin_sha1 = 'ef35f1eb55e50725777162e376e7b5222f45f7fa',
+  src_sha1 = 'e99e3b298e83461c03ef6eb66ab9798a4b712dc6',
   license = 'Apache2.0',
 )
diff --git a/lib/maven.defs b/lib/maven.defs
index a696580..b874d8e 100644
--- a/lib/maven.defs
+++ b/lib/maven.defs
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 GERRIT = 'GERRIT:'
+GERRIT_API = 'GERRIT_API:'
 ECLIPSE = 'ECLIPSE:'
 MAVEN_CENTRAL = 'MAVEN_CENTRAL:'
 MAVEN_LOCAL = 'MAVEN_LOCAL:'
@@ -35,10 +36,10 @@
     exclude_java_sources = False,
     unsign = False,
     deps = [],
+    exported_deps = [],
     sha1 = '', bin_sha1 = '', src_sha1 = '',
     repository = MAVEN_CENTRAL,
     attach_source = True,
-    export_deps = False,
     visibility = ['PUBLIC']):
   from os import path
 
@@ -103,7 +104,7 @@
       out = '__' + name + '__no_src',
     )
 
-  if export_deps:
+  if exported_deps:
     prebuilt_jar(
       name = name + '__jar',
       deps = deps + license + [':' + name + '__download_bin'],
@@ -112,8 +113,7 @@
     )
     java_library(
       name = name,
-      deps = [':' + name + '__jar'],
-      export_deps = True,
+      exported_deps = exported_deps + [':' + name + '__jar'],
       visibility = visibility,
     )
   else:
@@ -124,3 +124,37 @@
       source_jar = genfile(srcjar) if srcjar else None,
       visibility = visibility,
     )
+
+def local_jar(
+    name,
+    jar,
+    src = None,
+    deps = [],
+    visibility = ['PUBLIC']):
+  binjar = name + '.jar'
+  srcjar = name + '-src.jar'
+  genrule(
+    name = name + '__local_bin',
+    cmd = 'ln -s %s $OUT' % jar,
+    out = binjar)
+  if src:
+    genrule(
+      name = name + '__local_src',
+      cmd = 'ln -s %s $OUT' % src,
+      out = srcjar)
+    prebuilt_jar(
+      name = name + '_src',
+      deps = [':' + name + '__local_src'],
+      binary_jar = genfile(srcjar),
+      visibility = visibility,
+    )
+  else:
+    srcjar = None
+
+  prebuilt_jar(
+    name = name,
+    deps = deps + [':' + name + '__local_bin'],
+    binary_jar = genfile(binjar),
+    source_jar = genfile(srcjar) if srcjar else None,
+    visibility = visibility,
+  )
diff --git a/lib/prolog/java/BuckPrologCompiler.java b/lib/prolog/java/BuckPrologCompiler.java
index 0db6763..17d2d76 100644
--- a/lib/prolog/java/BuckPrologCompiler.java
+++ b/lib/prolog/java/BuckPrologCompiler.java
@@ -23,18 +23,21 @@
 import java.util.jar.JarOutputStream;
 
 public class BuckPrologCompiler {
+  private static File tmpdir;
+
   public static void main(String[] argv) throws IOException, CompileException {
-    File out = new File(argv[argv.length - 1]);
+    int i = 0;
+    tmpdir = new File(argv[i++]);
+    File out = new File(argv[i++]);
     File java = tmpdir("java");
-    for (int i = 0; i < argv.length - 1; i++) {
-      File src = new File(argv[i]);
-      new Compiler().prologToJavaSource(src.getPath(), java.getPath());
+    for (; i < argv.length; i++) {
+      new Compiler().prologToJavaSource(argv[i], java.getPath());
     }
     jar(out, java);
   }
 
   private static File tmpdir(String name) throws IOException {
-    File d = File.createTempFile(name + "_", "");
+    File d = File.createTempFile(name + "_", "", tmpdir);
     if (!d.delete() || !d.mkdir()) {
       throw new IOException("Cannot mkdir " + d);
     }
@@ -42,7 +45,7 @@
   }
 
   private static void jar(File jar, File classes) throws IOException {
-    File tmp = File.createTempFile("prolog", ".jar", jar.getParentFile());
+    File tmp = File.createTempFile("prolog", ".jar", tmpdir);
     try {
       JarOutputStream out = new JarOutputStream(new FileOutputStream(tmp));
       try {
diff --git a/lib/prolog/prolog.defs b/lib/prolog/prolog.defs
index d8df8f7..b91e2de 100644
--- a/lib/prolog/prolog.defs
+++ b/lib/prolog/prolog.defs
@@ -19,9 +19,9 @@
     visibility = []):
   genrule(
     name = name + '__pl2j',
-    cmd = 'cd $SRCDIR;$(exe //lib/prolog:compiler) ' +
-      ' '.join(srcs) +
-      ' $OUT',
+    cmd = 'cd $SRCDIR;$(exe //lib/prolog:compiler)' +
+      ' $TMP $OUT ' +
+      ' '.join(srcs),
     srcs = srcs,
     deps = ['//lib/prolog:compiler'],
     out = name + '.src.zip',
diff --git a/plugins/BUCK b/plugins/BUCK
index 6440fb7..480cd4c 100644
--- a/plugins/BUCK
+++ b/plugins/BUCK
@@ -4,6 +4,7 @@
   'download-commands',
   'replication',
   'reviewnotes',
+  'singleusergroup'
 ]
 
 # buck audit parses and resolves all deps even if not reachable
@@ -30,7 +31,7 @@
     'for s in $SRCS;do ln -s $s $TMP/WEB-INF/plugins;done;' +
     'cd $TMP;' +
     'zip -qr $OUT .',
-  srcs = [genfile('%s/%s.jar' % (n, n)) for n in CORE],
+  srcs = [genfile('%s/%s.jar' % (n, n)) for n in HAVE],
   deps = ['//%s/%s:%s' % (BASE, n, n) for n in HAVE],
   out = 'core.zip',
   visibility = ['//:release'],
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
index 6eba481..c4ac20c 160000
--- a/plugins/commit-message-length-validator
+++ b/plugins/commit-message-length-validator
@@ -1 +1 @@
-Subproject commit 6eba481780ec60391f65b86222a7888e3a41e373
+Subproject commit c4ac20cd951f3efa85a04a272e84c54fac7de346
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index b3bd0ca..dda1b78 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit b3bd0ca4e1d90f7523c5d8ebc319070f87cca8d3
+Subproject commit dda1b787dba031597b83db1eb3c1b57565059b68
diff --git a/plugins/download-commands b/plugins/download-commands
index eb5e44f..6287d6a 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit eb5e44f2d78a53b24db5ab6e277b27992f10b639
+Subproject commit 6287d6a8941f68ba8a3a8c27f2a979c02ede489a
diff --git a/plugins/replication b/plugins/replication
index 24438f6..a6f1995 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 24438f6d8ee145061de1d5efc4a2de78b11a8c4b
+Subproject commit a6f1995cea9cc885f0a2faa174fbafbab5c9f85c
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 6874243..b544447 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 68742432107edeead1b5b9a86f769c60023ea0ea
+Subproject commit b544447649d9ee3b3f78a6a1a7f839cb6a361292
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
new file mode 160000
index 0000000..73c2381
--- /dev/null
+++ b/plugins/singleusergroup
@@ -0,0 +1 @@
+Subproject commit 73c2381c5768f216b3a4abb1c623f8d0134a9600
diff --git a/tools/BUCK b/tools/BUCK
index b03dcbc..08ced89 100644
--- a/tools/BUCK
+++ b/tools/BUCK
@@ -1,6 +1,7 @@
 python_binary(
   name = 'download_file',
   main = 'download_file.py',
+  deps = [':util'],
   visibility = ['PUBLIC'],
 )
 
@@ -13,7 +14,17 @@
 
 python_library(
   name = 'util',
-  srcs = ['util.py'],
+  srcs = [
+    'util.py',
+    '__init__.py'
+  ],
+  visibility = ['PUBLIC'],
+)
+
+python_library(
+  name = 'util_test',
+  srcs = ['util_test.py'],
+  deps = [':util'],
   visibility = ['PUBLIC'],
 )
 
@@ -32,3 +43,14 @@
   out = 'buck.properties',
   visibility = ['PUBLIC'],
 )
+
+java_test(
+  name = 'python_tests',
+  srcs = glob(['PythonTestCaller.java']),
+  deps = [
+    '//lib:guava',
+    '//lib:junit',
+    ':util',
+    ':util_test',
+  ],
+)
diff --git a/tools/GoogleFormat.xml b/tools/GoogleFormat.xml
index c0a008a..8062246 100644
--- a/tools/GoogleFormat.xml
+++ b/tools/GoogleFormat.xml
@@ -45,7 +45,7 @@
 <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
 <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
-<setting id="org.eclipse.jdt.core.compiler.source" value="1.5"/>
+<setting id="org.eclipse.jdt.core.compiler.source" value="1.7"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
@@ -156,7 +156,7 @@
 <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
 <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
-<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.5"/>
+<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.7"/>
 <setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
@@ -227,7 +227,7 @@
 <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
 <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="do not insert"/>
-<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.5"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.7"/>
 <setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
 <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member" value="insert"/>
 <setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="true"/>
diff --git a/tools/PythonTestCaller.java b/tools/PythonTestCaller.java
new file mode 100644
index 0000000..deabeb4
--- /dev/null
+++ b/tools/PythonTestCaller.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.base.Splitter;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.Test;
+
+public class PythonTestCaller {
+
+  @Test
+  public void resolveUrl() throws Exception {
+    PythonTestCaller.pythonUnit("tools", "util_test");
+  }
+
+  private static void pythonUnit(String d, String sut) throws Exception {
+    ProcessBuilder b =
+        new ProcessBuilder(Splitter.on(' ').splitToList(
+                "python -m unittest " + sut))
+            .directory(new File(d))
+            .redirectErrorStream(true);
+    Process p = null;
+    InputStream i = null;
+    byte[] out;
+    try {
+      p = b.start();
+      i = p.getInputStream();
+      out = ByteStreams.toByteArray(i);
+    } catch (IOException e) {
+      throw new Exception(e);
+    } finally {
+      if (p != null) {
+        p.getOutputStream().close();
+      }
+      if (i != null) {
+        i.close();
+      }
+    }
+    int value;
+    try {
+      value = p.waitFor();
+    } catch (InterruptedException e) {
+      throw new Exception("interrupted waiting for process");
+    }
+    String err = new String(out, "UTF-8");
+    if (value != 0) {
+      System.err.print(err);
+    }
+    assertTrue(err, value == 0);
+  }
+}
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/__init__.py
diff --git a/tools/buck.defs b/tools/buck.defs
new file mode 100644
index 0000000..8bcacc3
--- /dev/null
+++ b/tools/buck.defs
@@ -0,0 +1,65 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+original_java_library = java_library
+def java_library(
+    name,
+    srcs=[],
+    resources=[],
+    source='7',
+    target='7',
+    proguard_config=None,
+    deps=[],
+    exported_deps=[],
+    visibility=[],
+    ):
+  original_java_library(
+    name=name,
+    srcs=srcs,
+    resources=resources,
+    source=source,
+    target=target,
+    proguard_config=proguard_config,
+    deps=deps,
+    exported_deps=exported_deps,
+    visibility=visibility,
+  )
+
+original_java_test = java_test
+def java_test(
+    name,
+    srcs=[],
+    labels=[],
+    resources=[],
+    source='7',
+    target='7',
+    vm_args=[],
+    source_under_test=[],
+    contacts=[],
+    deps=[],
+    visibility=[],
+    ):
+  original_java_test(
+    name=name,
+    srcs=srcs,
+    labels=labels,
+    resources=resources,
+    source=source,
+    target=target,
+    vm_args=vm_args,
+    source_under_test=source_under_test,
+    contacts=contacts,
+    deps=deps,
+    visibility=visibility,
+  )
diff --git a/tools/build.defs b/tools/build.defs
index b62c850..4bb48ea 100644
--- a/tools/build.defs
+++ b/tools/build.defs
@@ -14,7 +14,12 @@
 
 # These definitions support building a runnable version of Gerrit.
 
-DOCS = ['//Documentation:html.zip']
+DOCS_SRC = genfile('Documentation/html.zip')
+DOCS_LIB = '//Documentation:index_lib'
+DOCS_DEP = [
+  '//Documentation:html',
+  '//Documentation:index_lib',
+]
 LIBS = [
   '//gerrit-war:log4j-config',
   '//gerrit-war:init',
@@ -36,7 +41,8 @@
     libs = [],
     pgmlibs = [],
     context = [],
-    visibility = []
+    visibility = [],
+    docs = False
     ):
   cmd = ['$(exe //tools:pack_war)', '-o', '$OUT', '--tmp', '$TMP']
   for l in libs:
@@ -46,6 +52,10 @@
 
   src = []
   dep = []
+  if docs:
+    src.append(DOCS_SRC)
+    dep.extend(DOCS_DEP)
+    cmd.extend(['--lib', DOCS_LIB])
   if context:
     root = get_base_path()
     if root:
@@ -56,6 +66,7 @@
         r = root + r[2:]
       r = r.replace(':', '/')
       src.append(genfile(r))
+  if src:
     cmd.append('$SRCS')
 
   genrule(
@@ -67,7 +78,7 @@
     visibility = visibility,
   )
 
-def gerrit_war(name, ui = 'ui_optdbg', context = []):
+def gerrit_war(name, ui = 'ui_optdbg', context = [], docs = False):
   war(
     name = name,
     libs = LIBS + ['//gerrit-war:version'],
@@ -77,4 +88,5 @@
       '//gerrit-war:webapp_assets.zip',
       '//gerrit-gwtui:' + ui + '.zip',
     ] + context,
+    docs = docs,
   )
diff --git a/tools/default.defs b/tools/default.defs
index 76c4e87..be31b85 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -14,6 +14,9 @@
 
 # Rule definitions loaded by default into every BUCK file.
 
+include_defs('//tools/buck.defs')
+include_defs('//tools/gwt-constants.defs')
+
 def genantlr(
     name,
     srcs,
@@ -35,15 +38,20 @@
     gwtxml = None,
     resources = [],
     deps = [],
+    compile_deps = [],
     visibility = []):
   if gwtxml:
     resources = resources + [gwtxml]
-  resources = resources + srcs
   java_library(
     name = name,
+    deps = deps + compile_deps,
+    resources = srcs + resources,
+    visibility = visibility,
+  )
+  java_library(
+    name = name + '_lib',
     srcs = srcs,
-    deps = deps,
-    resources = resources,
+    deps = [':' + name] + [d + '_lib' for d in deps] + compile_deps,
     visibility = visibility,
   )
 
@@ -107,6 +115,7 @@
 def gerrit_extension(
     name,
     deps = [],
+    compile_deps = [],
     srcs = [],
     resources = [],
     manifest_file = None,
@@ -115,6 +124,7 @@
   gerrit_plugin(
     name = name,
     deps = deps,
+    compile_deps = compile_deps,
     srcs = srcs,
     resources = resources,
     manifest_file = manifest_file,
@@ -126,8 +136,10 @@
 def gerrit_plugin(
     name,
     deps = [],
+    compile_deps = [],
     srcs = [],
     resources = [],
+    gwt_module = None,
     manifest_file = None,
     manifest_entries = [],
     type = 'plugin',
@@ -151,20 +163,47 @@
     srcs = mf_src,
     out = 'MANIFEST.MF',
   )
+  gwt_deps = []
+  static_jars = []
+  if gwt_module:
+    gwt_deps = GWT_PLUGIN_DEPS
+    static_jars = [':%s-static-jar' % name]
   java_library2(
     name = name + '__plugin',
     srcs = srcs,
     resources = resources,
     deps = deps,
-    compile_deps = ['//:%s-lib' % type],
+    compile_deps = ['//gerrit-%s-api:lib' % type] + compile_deps + gwt_deps,
   )
+  if gwt_module:
+    prebuilt_jar(
+      name = '%s-static-jar' % name,
+      binary_jar = genfile('%s-static.zip' % name),
+      deps = [':%s-static' % name],
+    )
+    genrule(
+      name = '%s-static' % name,
+      cmd = 'mkdir -p $TMP/static' +
+        ';unzip -qd $TMP/static $(location %s)' %
+        ':%s__gwt_application' % name +
+        ';cd $TMP' +
+        ';zip -qr $OUT .',
+      out = '%s-static.zip' % name,
+      deps = [':%s__gwt_application' % name]
+    )
+    gwt_application(
+      name = name + '__gwt_application',
+      module_target = gwt_module,
+      compiler_opts = GWT_COMPILER_OPTS,
+      deps = [':%s__plugin' % name] + gwt_deps,
+    )
   java_binary(
     name = name,
     manifest_file = genfile('MANIFEST.MF'),
     deps = [
       ':%s__plugin' % name,
       ':%s__manifest' % name,
-    ],
+    ] + static_jars,
     visibility = visibility,
   )
 
@@ -178,3 +217,36 @@
     resources = srcs,
     visibility = visibility,
   )
+
+def java_doc(
+    name,
+    title,
+    pkg,
+    paths,
+    srcs = [],
+    deps = [],
+    visibility = []
+  ):
+  genrule(
+    name = name,
+    cmd = ' '.join([
+      'javadoc',
+      '-quiet',
+      '-protected',
+      '-encoding UTF-8',
+      '-charset UTF-8',
+      '-notimestamp',
+      '-windowtitle "' + title + '"',
+      '-link http://docs.oracle.com/javase/7/docs/api',
+      '-subpackages ' + pkg,
+      '-sourcepath ',
+      ':'.join([n for n in paths]),
+      ' -classpath ',
+      ':'.join(['$(location %s)' % n for n in deps]),
+      '-d $TMP',
+    ]) + ';jar cf $OUT -C $TMP .',
+    srcs = srcs,
+    deps = deps,
+    out = name + '.jar',
+    visibility = visibility,
+)
diff --git a/tools/download_file.py b/tools/download_file.py
index 8d76a40..3e6fca9 100755
--- a/tools/download_file.py
+++ b/tools/download_file.py
@@ -21,20 +21,13 @@
 import shutil
 from subprocess import check_call, CalledProcessError
 from sys import stderr
+from util import resolve_url
 from zipfile import ZipFile, BadZipfile, LargeZipFile
 
-REPO_ROOTS = {
-  'GERRIT': 'http://gerrit-maven.storage.googleapis.com',
-  'ECLIPSE': 'https://repo.eclipse.org/content/groups/releases',
-  'MAVEN_CENTRAL': 'http://repo1.maven.org/maven2',
-  'MAVEN_LOCAL': 'file://' + path.expanduser('~/.m2/repository'),
-}
-
 GERRIT_HOME = path.expanduser('~/.gerritcodereview')
 CACHE_DIR = path.join(GERRIT_HOME, 'buck-cache')
 LOCAL_PROPERTIES = 'local.properties'
 
-
 def hashfile(p):
   d = sha1()
   with open(p, 'rb') as f:
@@ -88,21 +81,6 @@
   name = '%s-%s' % (path.basename(args.o), h)
   return path.join(CACHE_DIR, name)
 
-def resolve_url(url, redirects):
-  s = url.find(':')
-  if s < 0:
-    return url
-  scheme, rest = url[:s], url[s+1:]
-  if scheme not in REPO_ROOTS:
-    return url
-  if scheme in redirects:
-    root = redirects[scheme]
-  else:
-    root = REPO_ROOTS[scheme]
-  root = root.rstrip('/')
-  rest = rest.lstrip('/')
-  return '/'.join([root, rest])
-
 opts = OptionParser()
 opts.add_option('-o', help='local output file')
 opts.add_option('-u', help='URL to download')
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK
index 9d6dd53..81889f2 100644
--- a/tools/eclipse/BUCK
+++ b/tools/eclipse/BUCK
@@ -9,9 +9,13 @@
     '//gerrit-gwtui:ui_tests',
     '//gerrit-httpd:httpd_tests',
     '//gerrit-main:main_lib',
+    '//gerrit-patch-jgit:jgit_patch_tests',
+    '//gerrit-plugin-gwtui:gwtui-api',
     '//gerrit-server:server__compile',
     '//lib/asciidoctor:asciidoc_lib',
     '//lib/asciidoctor:doc_indexer_lib',
+    '//lib/jetty:webapp',
     '//lib/prolog:compiler_lib',
+    '//Documentation:index_lib',
   ] + scan_plugins(),
 )
diff --git a/tools/eclipse/buck_daemon_ui_dbg.launch b/tools/eclipse/buck_daemon_ui_dbg.launch
deleted file mode 100644
index a345f8a..0000000
--- a/tools/eclipse/buck_daemon_ui_dbg.launch
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit/gerrit-main/src/main/java/Main.java"/>
-</listAttribute>
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
-<listEntry value="1"/>
-</listAttribute>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_dbg"/>
-</launchConfiguration>
diff --git a/tools/eclipse/buck_daemon_ui_firefox.launch b/tools/eclipse/buck_daemon_ui_firefox.launch
deleted file mode 100644
index 383b051..0000000
--- a/tools/eclipse/buck_daemon_ui_firefox.launch
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit/gerrit-main/src/main/java/Main.java"/>
-</listAttribute>
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
-<listEntry value="1"/>
-</listAttribute>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_firefox"/>
-</launchConfiguration>
diff --git a/tools/eclipse/buck_daemon_ui_ie9.launch b/tools/eclipse/buck_daemon_ui_ie9.launch
deleted file mode 100644
index 18863e7..0000000
--- a/tools/eclipse/buck_daemon_ui_ie9.launch
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit/gerrit-main/src/main/java/Main.java"/>
-</listAttribute>
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
-<listEntry value="1"/>
-</listAttribute>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_ie9"/>
-</launchConfiguration>
diff --git a/tools/eclipse/buck_daemon_ui_safari.launch b/tools/eclipse/buck_daemon_ui_safari.launch
deleted file mode 100644
index 55259a9..0000000
--- a/tools/eclipse/buck_daemon_ui_safari.launch
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit/gerrit-main/src/main/java/Main.java"/>
-</listAttribute>
-<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
-<listEntry value="1"/>
-</listAttribute>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_safari"/>
-</launchConfiguration>
diff --git a/tools/eclipse/buck_daemon_ui_chrome.launch b/tools/eclipse/gerrit_daemon.launch
similarity index 80%
rename from tools/eclipse/buck_daemon_ui_chrome.launch
rename to tools/eclipse/gerrit_daemon.launch
index efe2623..cbc6204 100644
--- a/tools/eclipse/buck_daemon_ui_chrome.launch
+++ b/tools/eclipse/gerrit_daemon.launch
@@ -9,8 +9,9 @@
 <listAttribute key="org.eclipse.debug.ui.favoriteGroups">
 <listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
 </listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../test_site"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../gerrit_testsite"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.browser=ui_chrome"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.plugin-classes=${resource_loc:/gerrit/buck-out}/eclipse/plugins"/>
 </launchConfiguration>
diff --git a/tools/eclipse/buck_gwt_debug.launch b/tools/eclipse/gerrit_gwt_debug.launch
similarity index 80%
rename from tools/eclipse/buck_gwt_debug.launch
rename to tools/eclipse/gerrit_gwt_debug.launch
index 1723cbf..945050d 100644
--- a/tools/eclipse/buck_gwt_debug.launch
+++ b/tools/eclipse/gerrit_gwt_debug.launch
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit/buck-out/gen/lib/gwt/dev/gwt-dev-2.5.0.jar"/>
+<listEntry value="/gerrit/buck-out/gen/lib/gwt/dev/gwt-dev-2.6.0.jar"/>
 </listAttribute>
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
 <listEntry value="1"/>
@@ -9,8 +9,9 @@
 <listAttribute key="org.eclipse.debug.ui.favoriteGroups">
 <listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
 </listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-startupUrl /&#10;-war ${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui/ui_dbg__tmp/war&#10;-server com.google.gerrit.gwtdebug.GerritDebugLauncher&#10;com.google.gerrit.GerritGwtUI"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M&#10;-XX:MaxPermSize=128M&#10;-Dgwt.persistentunitcachedir=${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui/ui_dbg__tmp/unit_cache&#10;-Dgerrit.source_root=${resource_loc:/gerrit}&#10;-Dgerrit.site_path=${resource_loc:/gerrit}/../test_site&#10;-da:com.google.gwtexpui.globalkey.client.KeyCommandSet"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M&#10;-XX:MaxPermSize=128M&#10;-Dgwt.persistentunitcachedir=${resource_loc:/gerrit}/buck-out/gen/gerrit-gwtui/ui_dbg__tmp/unit_cache&#10;-Dgerrit.source_root=${resource_loc:/gerrit}&#10;-Dgerrit.site_path=${resource_loc:/gerrit}/../gerrit_testsite&#10;-da:com.google.gwtexpui.globalkey.client.KeyCommandSet"/>
 </launchConfiguration>
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index 4707de5..50644eb 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -28,7 +28,7 @@
 JRE = '/'.join([
   'org.eclipse.jdt.launching.JRE_CONTAINER',
   'org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType',
-  'JavaSE-1.6',
+  'JavaSE-1.7',
 ])
 
 ROOT = path.abspath(__file__)
@@ -72,12 +72,14 @@
     impl = minidom.getDOMImplementation()
     return impl.createDocument(None, 'classpath', None)
 
-  def classpathentry(kind, path, src=None):
+  def classpathentry(kind, path, src=None, out=None):
     e = doc.createElement('classpathentry')
     e.setAttribute('kind', kind)
     e.setAttribute('path', path)
     if src:
       e.setAttribute('sourcepath', src)
+    if out:
+      e.setAttribute('output', out)
     doc.documentElement.appendChild(e)
 
   doc = make_classpath()
@@ -112,16 +114,29 @@
       gwt_src.add(m.group(1))
 
   for s in sorted(src):
+    out = None
+
+    if s.startswith('lib/'):
+      out = 'buck-out/eclipse/lib'
+    elif s.startswith('plugins/'):
+      out = 'buck-out/eclipse/' + s
+
     p = path.join(s, 'java')
     if path.exists(p):
-      classpathentry('src', p)
+      classpathentry('src', p, out=out)
       continue
 
     for env in ['main', 'test']:
+      o = None
+      if out:
+        o = out + '/' + env
+      elif env == 'test':
+        o = 'buck-out/eclipse/test'
+
       for srctype in ['java', 'resources']:
         p = path.join(s, 'src', env, srctype)
         if path.exists(p):
-          classpathentry('src', p)
+          classpathentry('src', p, out=o)
 
   for libs in [lib, gwt_lib]:
     for j in sorted(libs):
@@ -133,14 +148,15 @@
       classpathentry('lib', j, s)
 
   for s in sorted(gwt_src):
-    classpathentry('lib', path.join(ROOT, s, 'src', 'main', 'java'))
+    p = path.join(ROOT, s, 'src', 'main', 'java')
+    classpathentry('lib', p, out='buck-out/eclipse/gwtsrc')
 
   classpathentry('con', JRE)
-  classpathentry('output', 'buck-out/classes')
+  classpathentry('output', 'buck-out/eclipse/classes')
 
   p = path.join(ROOT, '.classpath')
   with open(p, 'w') as fd:
-    doc.writexml(fd, addindent='  ', newl='\n', encoding='UTF-8')
+    doc.writexml(fd, addindent='\t', newl='\n', encoding='UTF-8')
 
 try:
   if args.src:
diff --git a/tools/gwt-constants.defs b/tools/gwt-constants.defs
new file mode 100644
index 0000000..75c7503
--- /dev/null
+++ b/tools/gwt-constants.defs
@@ -0,0 +1,12 @@
+GWT_COMPILER_OPTS = [
+  '-strict',
+  '-style', 'OBF',
+  '-optimize', '9',
+  '-XdisableClassMetadata',
+  '-XdisableCastChecking',
+]
+
+GWT_PLUGIN_DEPS = [
+  '//gerrit-plugin-gwtui:gwtui-api-lib',
+  '//lib/gwt:user',
+]
diff --git a/tools/maven/BUCK b/tools/maven/BUCK
index 2553d03..f625b7b 100644
--- a/tools/maven/BUCK
+++ b/tools/maven/BUCK
@@ -8,14 +8,19 @@
   url = 'gs://gerrit-api/%s' % TYPE,
   version = GERRIT_VERSION,
   jar = {
-    'gerrit-extension-api': '//:extension-api',
-    'gerrit-plugin-api': '//:plugin-api',
-    'gerrit-plugin-gwtui': '//:plugin-gwtui',
+    'gerrit-extension-api': '//gerrit-extension-api:extension-api',
+    'gerrit-plugin-api': '//gerrit-plugin-api:plugin-api',
+    'gerrit-plugin-gwtui': '//gerrit-plugin-gwtui:gwtui-api',
   },
   src = {
-    'gerrit-extension-api': '//:extension-api-src',
-    'gerrit-plugin-api': '//:plugin-api-src',
-    'gerrit-plugin-gwtui': '//:plugin-gwtui-src',
+    'gerrit-extension-api': '//gerrit-extension-api:extension-api-src',
+    'gerrit-plugin-api': '//gerrit-plugin-api:plugin-api-src',
+    'gerrit-plugin-gwtui': '//gerrit-plugin-gwtui:gwtui-api-src',
+  },
+  doc = {
+    'gerrit-extension-api': '//gerrit-extension-api:extension-api-javadoc',
+    'gerrit-plugin-api': '//gerrit-plugin-api:plugin-api-javadoc',
+    'gerrit-plugin-gwtui': '//gerrit-plugin-gwtui:gwtui-api-javadoc',
   },
 )
 
diff --git a/tools/maven/mvn.py b/tools/maven/mvn.py
index 18f52cb..250e89f 100644
--- a/tools/maven/mvn.py
+++ b/tools/maven/mvn.py
@@ -16,8 +16,12 @@
 from __future__ import print_function
 from optparse import OptionParser
 from os import path
+
 from sys import stderr
-from util import check_output
+from tools.util import check_output
+
+def mvn(action):
+  return ['mvn', '--file', path.join(self, 'fake_pom_%s.xml' % action)]
 
 def mvn(action):
   return ['mvn', '--file', path.join(self, 'fake_pom_%s.xml' % action)]
diff --git a/tools/maven/package.defs b/tools/maven/package.defs
index 7306031..3171a31 100644
--- a/tools/maven/package.defs
+++ b/tools/maven/package.defs
@@ -17,11 +17,12 @@
     repository = None,
     url = None,
     jar = {},
-    src = {}):
+    src = {},
+    doc = {}):
   cmd = ['$(exe //tools/maven:mvn)', '-v', version, '-o', '$OUT']
   dep = []
 
-  for type,d in [('jar', jar), ('java-source', src)]:
+  for type,d in [('jar', jar), ('java-source', src), ('javadoc', doc)]:
     for a,t in d.iteritems():
       cmd.append('-s %s:%s:$(location %s)' % (a,type,t))
       dep.append(t)
diff --git a/tools/util.py b/tools/util.py
index 0c121e1..9115ac7 100644
--- a/tools/util.py
+++ b/tools/util.py
@@ -12,9 +12,47 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from os import path
+
 try:
   from subprocess import check_output
 except ImportError:
   from subprocess import Popen, PIPE
   def check_output(*cmd):
     return Popen(*cmd, stdout=PIPE).communicate()[0]
+
+REPO_ROOTS = {
+  'GERRIT': 'http://gerrit-maven.storage.googleapis.com',
+  'GERRIT_API': 'https://gerrit-api.commondatastorage.googleapis.com/release',
+  'ECLIPSE': 'https://repo.eclipse.org/content/groups/releases',
+  'MAVEN_CENTRAL': 'http://repo1.maven.org/maven2',
+  'MAVEN_LOCAL': 'file://' + path.expanduser('~/.m2/repository'),
+}
+
+def resolve_url(url, redirects):
+  """ Resolve URL of a Maven artifact.
+
+  prefix:path is passed as URL. prefix identifies known or custom
+  repositories that can be rewritten in redirects set, passed as
+  second arguments.
+
+  A special case is supported, when prefix neither exists in
+  REPO_ROOTS, no in redirects set: the url is returned as is.
+  This enables plugins to pass custom maven_repository URL as is
+  directly to maven_jar().
+
+  Returns a resolved path for Maven artifact.
+  """
+  s = url.find(':')
+  if s < 0:
+    return url
+  scheme, rest = url[:s], url[s+1:]
+  if scheme in redirects:
+    root = redirects[scheme]
+  elif scheme in REPO_ROOTS:
+    root = REPO_ROOTS[scheme]
+  else:
+    return url
+  root = root.rstrip('/')
+  rest = rest.lstrip('/')
+  return '/'.join([root, rest])
diff --git a/tools/util_test.py b/tools/util_test.py
new file mode 100644
index 0000000..f116171
--- /dev/null
+++ b/tools/util_test.py
@@ -0,0 +1,43 @@
+#!/usr/bin/python
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+from util import resolve_url
+
+class TestResolveUrl(unittest.TestCase):
+  """ run to test:
+    python -m unittest -v util_test
+  """
+
+  def testKnown(self):
+    url = resolve_url('GERRIT:foo.jar', {})
+    self.assertEqual(url, 'http://gerrit-maven.storage.googleapis.com/foo.jar')
+
+  def testKnownRedirect(self):
+    url = resolve_url('MAVEN_CENTRAL:foo.jar',
+                      {'MAVEN_CENTRAL': 'http://my.company.mirror/maven2'})
+    self.assertEqual(url, 'http://my.company.mirror/maven2/foo.jar')
+
+  def testCustom(self):
+    url = resolve_url('http://maven.example.com/release/foo.jar', {})
+    self.assertEqual(url, 'http://maven.example.com/release/foo.jar')
+
+  def testCustomRedirect(self):
+    url = resolve_url('MAVEN_EXAMPLE:foo.jar',
+                      {'MAVEN_EXAMPLE': 'http://maven.example.com/release'})
+    self.assertEqual(url, 'http://maven.example.com/release/foo.jar')
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/website/releases/index.html b/website/releases/index.html
index 4a854d6..b8d7905 100644
--- a/website/releases/index.html
+++ b/website/releases/index.html
@@ -47,7 +47,12 @@
   var docs = 'https://gerrit-documentation.storage.googleapis.com/';
   var src = 'https://gerrit.googlesource.com/gerrit/+/'
 
-  data.items.sort(function(a,b) {
+  var items = data.items.filter(function(i) {
+    return i.name.indexOf('gerrit-snapshot-') != 0;
+  });
+
+
+  items.sort(function(a,b) {
     var av = rx.exec(a.name);
     var bv = rx.exec(b.name);
     if (!av || !bv) {
@@ -75,8 +80,8 @@
   });
 
   var latest = false;
-  for (var i = 0; i < data.items.length; i++) {
-    var f = data.items[i];
+  for (var i = 0; i < items.length; i++) {
+    var f = items[i];
     var v = rx.exec(f.name);
 
     if ('index.html' == f.name) {