Merge "Added OutgoingEmailValidationListener"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 0fefc82..50686d2 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1515,10 +1515,10 @@
 +
 Optional command to install the `commit-msg` hook. Typically of the
 form:
++
 ----
 fetch-cmd some://url/to/commit-msg .git/hooks/commit-msg ; chmod +x .git/hooks/commit-msg
 ----
-
 +
 By default unset; falls back to using scp from the canonical SSH host,
 or curl from the canonical HTTP URL for the server.  Only necessary if a
@@ -1722,41 +1722,61 @@
 +
 Optional path to hooks, if not specified then `'$site_path'/hooks` will be used.
 
-[[hooks.patchsetCreatedHook]]hooks.patchsetCreatedHook::
+[[hooks.syncHookTimeout]]hooks.syncHookTimeout::
 +
-Optional filename for the patchset created hook, if not specified then
-`patchset-created` will be used.
-
-[[hooks.draftPublishedHook]]hooks.draftPublishedHook::
-+
-Optional filename for the draft published hook, if not specified then
-`draft-published` will be used.
-
-[[hooks.commentAddedHook]]hooks.commentAddedHook::
-+
-Optional filename for the comment added hook, if not specified then
-`comment-added` will be used.
-
-[[hooks.changeMergedHook]]hooks.changeMergedHook::
-+
-Optional filename for the change merged hook, if not specified then
-`change-merged` will be used.
-
-[[hooks.mergeFailedHook]]hooks.mergeFailedHook::
-+
-Optional filename for the merge failed hook, if not specified then
-`merge-failed` will be used.
+Optional timeout value in seconds for synchronous hooks, if not specified
+then 30 seconds will be used.
 
 [[hooks.changeAbandonedHook]]hooks.changeAbandonedHook::
 +
 Optional filename for the change abandoned hook, if not specified then
 `change-abandoned` will be used.
 
+[[hooks.changeMergedHook]]hooks.changeMergedHook::
++
+Optional filename for the change merged hook, if not specified then
+`change-merged` will be used.
+
 [[hooks.changeRestoredHook]]hooks.changeRestoredHook::
 +
 Optional filename for the change restored hook, if not specified then
 `change-restored` will be used.
 
+[[hooks.claSignedHook]]hooks.claSignedHook::
++
+Optional filename for the CLA signed hook, if not specified then
+`cla-signed` will be used.
+
+[[hooks.commentAddedHook]]hooks.commentAddedHook::
++
+Optional filename for the comment added hook, if not specified then
+`comment-added` will be used.
+
+[[hooks.draftPublishedHook]]hooks.draftPublishedHook::
++
+Optional filename for the draft published hook, if not specified then
+`draft-published` will be used.
+
+[[hooks.hashtagsChangedHook]]hooks.hashtagsChangedHook::
++
+Optional filename for the hashtags changed hook, if not specified then
+`hashtags-changed` will be used.
+
+[[hooks.mergeFailedHook]]hooks.mergeFailedHook::
++
+Optional filename for the merge failed hook, if not specified then
+`merge-failed` will be used.
+
+[[hooks.patchsetCreatedHook]]hooks.patchsetCreatedHook::
++
+Optional filename for the patchset created hook, if not specified then
+`patchset-created` will be used.
+
+[[hooks.refUpdateHook]]hooks.refUpdateHook::
++
+Optional filename for the ref update hook, if not specified then
+`ref-update` will be used.
+
 [[hooks.refUpdatedHook]]hooks.refUpdatedHook::
 +
 Optional filename for the ref updated hook, if not specified then
@@ -1772,26 +1792,6 @@
 Optional filename for the topic changed hook, if not specified then
 `topic-changed` will be used.
 
-[[hooks.claSignedHook]]hooks.claSignedHook::
-+
-Optional filename for the CLA signed hook, if not specified then
-`cla-signed` will be used.
-
-[[hooks.refUpdateHook]]hooks.refUpdateHook::
-+
-Optional filename for the ref update hook, if not specified then
-`ref-update` will be used.
-
-[[hooks.hashtagsChangedHook]]hooks.hashtagsChangedHook::
-+
-Optional filename for the hashtags changed hook, if not specified then
-`hashtags-changed` will be used.
-
-[[hooks.syncHookTimeout]]hooks.syncHookTimeout::
-+
-Optional timeout value in seconds for synchronous hooks, if not specified
-then 30 seconds will be used.
-
 [[http]]
 === Section http
 
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index c356dab..f5caf40 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -336,6 +336,33 @@
 * Update to the same GWT version in the `gwtjsonrpc` project, and release a
 new version.
 
+=== Updating to new version of CodeMirror
+
+* Clone the git from https://github.com/codemirror/CodeMirror
+* Checkout the version needed
+* If the needed version is not a tagged version, use `git describe` to determine
+the version number:
++
+----
+ git describe --tags
+----
+
+* Create the release zip file:
++
+----
+ git archive --format=zip --prefix=codemirror-4.10.0-6-gd0a2dda/ d0a2dda > 4.10.0-6-gd0a2dda.zip
+----
+
+* Determine the sha1 hash of the zip file:
++
+----
+ openssl sha1 4.10.0-6-gd0a2dda.zip
+----
+
+* Upload the zip file to the
+link:https://console.developers.google.com/project/164060093628/storage/gerrit-maven/[
+gerrit-maven] storage bucket
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/images/inline-edit-add-file-suggestion.png b/Documentation/images/inline-edit-add-file-suggestion.png
new file mode 100644
index 0000000..25a33f8
--- /dev/null
+++ b/Documentation/images/inline-edit-add-file-suggestion.png
Binary files differ
diff --git a/Documentation/images/inline-edit-confirm-unsaved-edits.png b/Documentation/images/inline-edit-confirm-unsaved-edits.png
new file mode 100644
index 0000000..87e4a32
--- /dev/null
+++ b/Documentation/images/inline-edit-confirm-unsaved-edits.png
Binary files differ
diff --git a/Documentation/images/inline-edit-edit-in-diff-screen-patch-list.png b/Documentation/images/inline-edit-edit-in-diff-screen-patch-list.png
new file mode 100644
index 0000000..bdbc59d
--- /dev/null
+++ b/Documentation/images/inline-edit-edit-in-diff-screen-patch-list.png
Binary files differ
diff --git a/Documentation/images/inline-edit-edit-in-patch-list.png b/Documentation/images/inline-edit-edit-in-patch-list.png
new file mode 100644
index 0000000..9a31e02
--- /dev/null
+++ b/Documentation/images/inline-edit-edit-in-patch-list.png
Binary files differ
diff --git a/Documentation/images/inline-edit-enter-edit-mode-from-diff.png b/Documentation/images/inline-edit-enter-edit-mode-from-diff.png
new file mode 100644
index 0000000..46dd0ff
--- /dev/null
+++ b/Documentation/images/inline-edit-enter-edit-mode-from-diff.png
Binary files differ
diff --git a/Documentation/images/inline-edit-enter-edit-mode-from-file-list.png b/Documentation/images/inline-edit-enter-edit-mode-from-file-list.png
new file mode 100644
index 0000000..b8c52c9
--- /dev/null
+++ b/Documentation/images/inline-edit-enter-edit-mode-from-file-list.png
Binary files differ
diff --git a/Documentation/images/inline-edit-file-list-in-edit-mode.png b/Documentation/images/inline-edit-file-list-in-edit-mode.png
new file mode 100644
index 0000000..8f355335
--- /dev/null
+++ b/Documentation/images/inline-edit-file-list-in-edit-mode.png
Binary files differ
diff --git a/Documentation/images/inline-edit-full-screen-editor.png b/Documentation/images/inline-edit-full-screen-editor.png
new file mode 100644
index 0000000..474fae5
--- /dev/null
+++ b/Documentation/images/inline-edit-full-screen-editor.png
Binary files differ
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index bb5e148..6fd0ef2 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1408,8 +1408,16 @@
   GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo HTTP/1.0
 ----
 
-The content of the file is returned as text encoded inside base64. When
-specified file was deleted in the change edit "`204 No Content`" is returned.
+The content of the file is returned as text encoded inside base64.
+The Content-Type header will always be `text/plain` reflecting the
+outer base64 encoding. A Gerrit-specific `X-FYI-Content-Type` header
+can be examined to find the server detected content type of the file.
+
+When the specified file was deleted in the change edit
+"`204 No Content`" is returned.
+
+If only the content type is required, callers should use HEAD to
+avoid downloading the encoded file contents.
 
 .Response
 ----
@@ -1417,33 +1425,11 @@
   Content-Disposition: attachment
   Content-Type: text/plain; charset=ISO-8859-1
   X-FYI-Content-Encoding: base64
+  X-FYI-Content-Type: text/xml
 
   RnJvbSA3ZGFkY2MxNTNmZGVhMTdhYTg0ZmYzMmE2ZTI0NWRiYjY...
 ----
 
-[[get-edit-file-mime-type]]
-=== Retrieve file content MIME type from Change Edit
---
-'GET /changes/link:#change-id[\{change-id\}]/edit/path%2fto%2ffile/type
---
-
-Retrieves content MIME type of a file from a change edit.
-
-.Request
-----
-  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit/foo%2fbar%2fbaz%2fqux.txt/type HTTP/1.0
-----
-
-.Response
-----
-  HTTP/1.1 200 OK
-  Content-Disposition: attachment
-  Content-Type: application/json; charset=UTF-8
-
-  )]}'
-  "text/plain"
-----
-
 [[get-edit-message]]
 === Retrieve commit message from Change Edit or current patch set of the change
 --
@@ -2081,6 +2067,9 @@
   }
 ----
 
+A review cannot be set on a change edit. Trying to post a review for a
+change edit fails with `409 Conflict`.
+
 [[rebase-revision]]
 === Rebase Revision
 --
@@ -2758,6 +2747,11 @@
 of the paths the caller has marked as reviewed.  Clients that also
 need the FileInfo should make two requests.
 
+The request parameter `q` changes the response to return a list
+of all files (modified or unmodified) that contain that substring
+in the path name. This is useful to implement suggestion services
+finding a file by partial name.
+
 .Request
 ----
   GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/?reviewed HTTP/1.0
@@ -2789,43 +2783,25 @@
   GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/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.
+The content is returned as base64 encoded string.  The HTTP response
+Content-Type is always `text/plain`, reflecting the base64 wrapping.
+A Gerrit-specific `X-FYI-Content-Type` header is returned describing
+the server detected content type of the file.
+
+If only the content type is required, callers should use HEAD to
+avoid downloading the encoded file contents.
 
 .Response
 ----
   HTTP/1.1 200 OK
   Content-Disposition: attachment
-  Content-Type: text/plain; charset=UTF-8
+  Content-Type: text/plain; charset=ISO-8859-1
+  X-FYI-Content-Encoding: base64
+  X-FYI-Content-Type: text/xml
 
   Ly8gQ29weXJpZ2h0IChDKSAyMDEwIFRoZSBBbmRyb2lkIE9wZW4gU291cmNlIFByb2plY...
 ----
 
-[[get-content-type]]
-=== Get Content MIME Type
---
-'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/files/link:#file-id[\{file-id\}]/type'
---
-
-Gets the content MIME type of a file from a certain revision.
-
-.Request
-----
-  GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/readme.txt/type HTTP/1.0
-----
-
-The content MIME type is returned as string. The content type for the commit
-message (`/COMMIT_MSG`) is always returned as "text/plain".
-
-.Response
-----
-  HTTP/1.1 200 OK
-  Content-Disposition: attachment
-  Content-Type: application/json; charset=UTF-8
-
-  )]}'
-  "text/plain"
-----
-
 [[get-diff]]
 === Get Diff
 --
@@ -4072,10 +4048,6 @@
 |`commit`      ||The commit of change edit as
 link:#commit-info[CommitInfo] entity.
 |`baseRevision`||The revision of the patch set change edit is based on.
-|`actions`     ||
-Actions the caller might be able to perform on this change edit. The
-information is a map of view name to link:#action-info[ActionInfo]
-entities.
 |`fetch`       ||
 Information about how to fetch this patch set. The fetch information is
 provided as a map that maps the protocol name ("`git`", "`http`",
diff --git a/Documentation/user-inline-edit.txt b/Documentation/user-inline-edit.txt
index e44fb94..9be89a2 100644
--- a/Documentation/user-inline-edit.txt
+++ b/Documentation/user-inline-edit.txt
@@ -29,6 +29,70 @@
 
 image::images/inline-edit-create-follow-up-change.png[width=800, link="images/inline-edit-create-follow-up-change.png"]
 
+[[editing-change]]
+== Editing Changes
+
+To switch to edit mode, press the 'Edit' button at the top of the file list:
+
+[[switch-to-edit-mode]]
+image::images/inline-edit-enter-edit-mode-from-file-list.png[width=800, link="images/inline-edit-enter-edit-mode-from-file-list.png"]
+
+While in edit mode, it is possible to add new files to the change by clicking
+the 'Add...' button at the top of the file list.
+
+Files can be removed from the change, or restored, by clicking the icon to the
+left of the file name.
+
+To switch from edit mode back to review mode, click the 'Done Editing' button.
+
+image::images/inline-edit-file-list-in-edit-mode.png[width=800, link="images/inline-edit-file-list-in-edit-mode.png"]
+
+[[open-full-screen-editor]]
+While in edit mode, clicking on a file name in the file list opens a full
+screen editor for that file.
+
+To save edits, click the 'Save' button or press `CTRL-S`.  To return to the
+change screen, click the 'Close' button.
+
+image::images/inline-edit-full-screen-editor.png[width=800, link="images/inline-edit-full-screen-editor.png"]
+
+If there are unsaved edits when the 'Close' button is pressed, a dialog will
+pop up asking to confirm the edits.
+
+image::images/inline-edit-confirm-unsaved-edits.png[width=800, link="images/inline-edit-confirm-unsaved-edits.png"]
+
+To discard the unsaved edits and return to the change screen, click the 'OK'
+button. To continue editing, click 'Cancel'.
+
+[[switch-to-edit-mode-from-side-by-side]]
+
+While in review mode, it is possible to switch directly to edit mode and into an
+editor for a file under review by clicking on the edit icon in the patch set list
+on the side-by-side diff view.
+
+image::images/inline-edit-enter-edit-mode-from-diff.png[width=800, link="images/inline-edit-enter-edit-mode-from-diff.png"]
+
+[[reviewing-changes-made-in-change-edit]]
+== Reviewing Change Edits
+
+Change edits are reviewed in the same way as regular patch sets, using the
+side-by-side diff screen. Change edits are shown as 'edit' in the patch list
+on the diff screen:
+
+image::images/inline-edit-edit-in-diff-screen-patch-list.png[width=800, link="images/inline-edit-edit-in-diff-screen-patch-list.png"]
+
+and on the change screen:
+
+image::images/inline-edit-edit-in-patch-list.png[width=800, link="images/inline-edit-edit-in-patch-list.png"]
+
+Note that patch sets may exist that were created after the change edit was created.
+
+For example this sequence:
+
+`1 2 3 4 5 6 7 8 9 edit 10`
+
+means that the change edit was created on top of patch set number 9 and a regular
+patch set was uploaded later.
 
 GERRIT
 ------
diff --git a/ReleaseNotes/ReleaseNotes-2.11.txt b/ReleaseNotes/ReleaseNotes-2.11.txt
index 34f2997..43a8ce6 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.txt
@@ -21,7 +21,18 @@
 a later 2.1.x version), and then to 2.11.x.  If you are upgrading from 2.2.x.x or
 later, you may ignore this warning and upgrade directly to 2.11.x.
 
-*WARNING:* The 'Generate HTTP Password' capability has been removed.
+*WARNING:* The 'Generate HTTP Password' capability has been
+link:#remove-generate-http-password-capability[removed].
+
+*WARNING:* Google will
+link:https://developers.google.com/+/api/auth-migration[shut down their OpenID
+service on 20th April 2015]. Administrators of sites whose users are registered
+with Google OpenID accounts should encourage the users to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-sso.html#_multiple_identities[
+add an alternative identity to their account] before this date. Users who do
+not add an alternative identity before this date will need to create a new
+account and ask the site administrator to
+link:https://code.google.com/p/gerrit/wiki/SqlMergeUserAccounts[merge it].
 
 
 Release Highlights
@@ -29,10 +40,39 @@
 
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=505[Issue 505]:
-Inline editing: Changes can be edited directly in the browser.
+Changes can be created and edited directly in the browser. See the
+link:#inline-editing[Inline editing] section for more details.
+
+
+Experimental Features
+---------------------
+
+The following new features are experimental. They are not fully documented yet,
+and it is not recommended to enable them in live production systems.
+
+* Migration of review information from database to git notes.
 +
-Files can be added, deleted, restored or edited directly in the browser. Change
-edits can be published, deleted and rebased on top of the latest patch set.
+Groundwork has been done to implement migration of review information from the
+database to a git notes based backend.
++
+Existing review information can be migrated from the review database to
+git notes with the `RebuildNotedb` program.
++
+This feature can be enabled with the following settings in `gerrit.config`:
+----
+[gerrit]
+  notedbpath = notedb
+[notedb "changes"]
+  write = true
+  read = true
+----
+
+* Hashtags.
++
+Hashtags can be added to changes. The hashtags are stored in git notes and
+are indexed in the secondary index.
++
+This feature requires the notedb to be enabled.
 
 
 New Features
@@ -42,32 +82,348 @@
 Web UI
 ~~~~~~
 
-TODO
+[[inline-editing]]
+Inline Editing
+^^^^^^^^^^^^^^
 
-Global
-^^^^^^
+Refer to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/user-inline-edit.html[
+inline editing user guide] for detailed instructions.
 
-TODO
+* The inline editing feature only works with the new change screen.
+
+* New changes can be created directly in the browser via a 'Create Change'
+button on the project info screen.
+
+* New follow-up changes can be created via a 'Follow-Up' button on the change
+screen.
+
+* File content can be edited in a full screen CodeMirror window with support for
+themes, syntax highlighting, different key maps (Emacs, Vim, Default).
+
+* The CodeMirror screen can be configured in the same way as the side-by-side
+diff screen.
+
+* The file table in the change screen supports edit mode with seamless navigation
+to CodeMirror for editing.
+
+* Edit mode can be started from the side-by-side diff screen with seamless
+navigation to CodeMirror.
+
+* The commit message can be changed in context of change edit. The 'Edit Message'
+button is still supported, but now it creates a change edit that must be published.
+
+* Files can be added, deleted, restored and modified directly in browser.
+
+* User-specific configuration dedicated to edit mode in CodeMirror are stored in
+the `All-Users` repository rather than in the database.
+
+Change Screen
+^^^^^^^^^^^^^
+
+* Show the parent commit's subject as a tooltip.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=2541[Issue 2541],
+link:http://code.google.com/p/gerrit/issues/detail?id=2974[Issue 2974]:
+Allow the 'Reply' button's
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#change.replyLabel[
+label and tooltip] to be configured.
+
+Side-By-Side Diff
+^^^^^^^^^^^^^^^^^
+
+* New preference setting to toggle auto-hiding of the diff table header.
++
+The setting determines whether or not the diff table header with the patch set
+selection should be automatically hidden when scrolling down more than half of
+a page.
+
+* Highlight search results on scrollbar.
++
+Search results in vim mode are highlighted in the scrollbar with gold
+colored annotations.
+
+* Set line length to 72 characters for commit messages.
+
+* Add syntax highlighting for several new modes:
+
+** link:https://code.google.com/p/gerrit/issues/detail?id=2848[Issue 2848]: CSharp
+** Dart
+** Dockerfile
+** GLSL shader
+** Objective C
+** link:http://code.google.com/p/gerrit/issues/detail?id=2779[Issue 2779]: reStructured text
+** Soy
 
 REST
 ~~~~
 
-TODO
+Changes
+^^^^^^^
 
-SSH
+* Add new
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#check-change[
+check change endpoint].
++
+In the past, Gerrit bugs, lack of transactions, and unreliable NoSQL backends
+have at various times produced a bewildering variety of corrupt states.
++
+This endpoint can be used to detect and explain some of these possible states
+of a change.
+
+Change Edits
+^^^^^^^^^^^^
+
+Several new endpoints are added to support the inline edit feature.
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#get-edit-detail[
+Get Edit Detail].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#put-edit-file[
+Change file content in Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#post-edit[
+Restore file content in Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#put-change-edit-message[
+Change commit message in Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#delete-edit-file[
+Delete file in Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#get-edit-file[
+Retrieve file content from Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#get-edit-message[
+Retrieve commit message from Change Edit or current patch set of the change].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#publish-edit[
+Publish Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#rebase-edit[
+Rebase Change Edit].
+
+* link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#delete-edit[
+Delete Change Edit].
+
+
+Projects
+^^^^^^^^
+
+* Add filtering and pagination options on the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#list-branches[
+list branches] endpoint.
+
+* Add new
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#list-tags[
+list tags] endpoint.
+
+* Add new
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#get-tag[
+get tag] endpoint.
+
+
+Configuration
+~~~~~~~~~~~~~
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2786[Issue 2786]:
+Allow non-administrators to modify user accounts.
++
+A new global capability,
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/access-control.html#capability_modifyAccount[
+'Modify Account'], allows the granted group members to modify user account
+settings via the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-set-account.html[
+`set-account` SSH command].
++
+Modification of users' SSH keys is still restricted to administrators.
+
+* Add support for
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#ldap.useConnectionPooling[
+LDAP connection pooling].
+
+* Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#receive.maxBatchChanges[
+limit max number of changes pushed in a batch].
++
+Can be overridden by members of groups that are granted the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/access-control.html#capability_batchChangesLimit[
+Batch Changes Limit] capability.
+
+* Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#gerrit.disableReverseDnsLookup[
+disable reverse DNS lookup].
++
+This option can be set to improve push time from hosts without a reverse DNS
+entry.
+
+* Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#cache.projects.loadOnStartup[
+load the project cache at server startup].
+
+* Allow members of groups granted the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/access-control.html#capability_accessDatabase[
+AccessDatabase capability] to view metadata refs.
+
+* Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#http.addUserAsRequestAttribute[
+add the user to the http request attributes].
+
+* Allow to
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#suggest.fullTextSearch[
+enable full text search in memory for review suggestions].
++
+The maximum number of reviewers evaluated can be limited with
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#suggest.fullTextSearchMaxMatches[
+suggest.fullTextSearchMaxMatches].
+
+* Allow to provide an alternative
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#gerrit.secureStoreClass[
+secure store implementation].
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=1195[Issue 1195]:
+Allow projects to be configured to create a new change for every uploaded commit that is not in the target branch.
+
+* Allow to configure
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#container.daemonOpt[
+options to pass to the daemon].
+
+* Remove support for Google accounts and add support for Launchpad accounts on
+the OpenID login page.
+
+Daemon
+~~~~~~
+
+* Allow to enable the http daemon when running in slave mode.
+
+The `--enable-httpd` option can be used in conjunction with the `--slave` option
+to allow clients to fetch from the slave over the http protocol.
+
+* Include the submitter's name in the change message when a change is submitted.
+
+
+ssh
 ~~~
 
-TODO
+* Add new commands
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-logging-ls-level.html[
+`logging ls-level`] and
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-logging-set-level.html[
+`logging set-level`] to show and set the logging level at runtime.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=602[Issue 602]:
+Add `--json` option to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-review.html[
+`review` SSH command].
++
+Review input can be given to the `review` command in JSON format corresponding
+to the REST API's
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#review-input[
+ReviewInput] entity.
+
+*  link:https://code.google.com/p/gerrit/issues/detail?id=2824[Issue 2824]:
+Add `--rebase` option to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-review.html[
+`review` SSH command].
+
+* Add `--clear-http-password` option to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-set-account.html[
+`set-account` SSH command].
+
+* Add `--preferred-email` option to the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-set-account.html[
+`set-account` SSH command].
+
 
 Plugins
 ~~~~~~~
 
-TODO
+General
+^^^^^^^
 
-Other
-~~~~~
+* Plugins can listen to account group membership changes
++
+The audit log service allows to register listeners to group member added and
+group member deleted events. A default listener logs these events to the database
+as before, but additional listeners may now be registered for these events using
+the `GroupMemberAuditListener` interface.
 
-* The 'Generate HTTP Password' capability is removed.
+* Plugins can validate ref operations.
++
+Plugins implementing the `RefOperationValidationListener` interface can
+perform additional validation checks against ref creation/deletion operations
+before they are applied to the git repository.
+
+* Plugins can provide project-aware top menu extensions
++
+Plugins can provide sub-menu items within the 'Projects' context. The
+'${projectName}' placeholder is replaced by the project name.
+
+* Auto register static/init.js as JavaScript plugin.
++
+When a plugin does not expose Guice Modules explicitly, auto discover and
+register static/init.js as WebUi extension if found by the plugin content
+scanner.
+
+* Plugins that provide initialization steps may now use functionality
+from InitUtil in core Gerrit.
+
+* New extensions in the Java Plugin API:
+
+** Set/Put topic.
+** Get mergeable status.
+** link:https://code.google.com/p/gerrit/issues/detail?id=461[Issue 461]:
+Get current user.
+
+Replication
+^^^^^^^^^^^
+
+* Projects can be specified with wildcard in the `start` command.
+
+
+Bug Fixes
+---------
+
+Daemon
+~~~~~~
+
+* Change "Merge topic" to "Merge changes from topic".
++
+When multiple changes from a topic are submitted resulting in a merge commit,
+the title of the merge commit is now "Merge changes from topic" instead of
+"Merge topic".
+
+* Fix visibility checks for `refs/meta/config`.
++
+Under some conditions it was possible for the `refs/meta/config` branch to be
+erroneously considerered not visible to the user.
+
+* Sort list of updated changes in output from push.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2940[Issue 2940]:
+Improve warning messages when Change-Id is missing in the commit message.
+
+** Add a hint to amend the commit after installing the commit-msg hook.
+** Don't show "Suggestion for commit message" when Change-Id is missing.
+
+
+Secondary Index / Search
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2822[Issue 2822]:
+Improve Lucene analysis of words linked with '_' or '.'.
++
+Instead of treating words linked with '_' or '.' as one word, Lucene now
+treats them as separate words.
+
+* Fix support for change~branch~id in query syntax.
+
+
+Configuration
+~~~~~~~~~~~~~
+
+[[remove-generate-http-password-capability]]
+* Remove the 'Generate HTTP Password' capability.
 +
 The 'Generate HTTP Password' capability has been removed to close a security
 vulnerability.  Now only administrators are allowed to generate and delete other
@@ -75,7 +431,127 @@
 +
 It is encouraged to clean up your `project.config` settings after upgrading.
 
+
+Web UI
+~~~~~~
+
+Change Screen
+^^^^^^^^^^^^^
+
+* Differentiate between conflicts and already merged errors in cherry-pick
++
+When a cherry-pick operation failed with "Cherry pick failed" error, there was no
+way to know the reason for the failure: merge conflict or the commit is already
+on the target branch.  These failures are now differentiated and a proper error
+is reported to the client.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2837[Issue 2837]:
+Improve display of long user names for collapsed comments in history.
++
+If there were several users with long user names with the same prefix, e.g.
+'AutomaticGerritVoterLinux' and 'AutomaticGerritVoterWindows', they would both
+be shown as 'AutomaticGerritVo...' and users had to expand the comment to see
+the full user name.
++
+The ellipsis is now inserted in the middle of the user name so that the start
+and end of the user name are always visible, e.g. 'AutomaticG...VoterLinux' and
+'AutomaticG...terWindows'.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2992[Issue 2992]:
+Fix display of review comments for Chrome on Android.
++
+Chrome for Android has Font Boosting, which caused the review comments to
+be displayed too large.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=2909[Issue 2909]:
+Make change owner votes removable.
++
+If a change owner voted on a change, it was not possible for anyone other
+than the owner to remove the vote.
+
+* Preserve topic when cherry-picking.
++
+When a change is cherry-picked, the topic from the source change is preserved
+on the newly created change.
+
+* link:http://code.google.com/p/gerrit/issues/detail?id=3007[Issue 3007]:
+Make the selected tab persistent.
++
+If a change from the "Same Topic" tab was clicked, the selected tab would reset
+to the default tab ("Related Changes").
+
+
+Side-By-Side Diff
+^^^^^^^^^^^^^^^^^
+
+* Return to normal mode after editing a draft comment.
++
+Previously it would remain in visual mode.
+
+
+Project Screen
+^^^^^^^^^^^^^^
+
+* Fix alignment of checkboxes on project access screen.
++
+The "Exclusive" checkbox was not aligned with the other checkboxes.
+
+REST API
+~~~~~~~~
+
+Changes
+^^^^^^^
+
+* Remove the administrator restriction on the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#index-change[
+index change] endpoint.
++
+The endpoint can now be used by any user who has visibility of the change.
+
+* Only include account ID in responses unless `DETAILED_ACCOUNTS` option is set.
++
+The behaviour was inconsistent with the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-accounts.html#account-info[
+documentation]. In the default case it was including only the account name,
+rather than only the account ID.
+
+* Include revision's ref in responses.
++
+The ref of a revision was only returned as part of the fetch info, which is only
+available if the download commands are installed.
+
+* Correctly set the limit to the default when no limit is given in the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#suggest-reviewers[
+suggest reviewers] endpoint.
+
+Projects
+^^^^^^^^
+
+* Make it mandatory to specify at least one of the `--prefix`, `--match` or `--regex`
+options in the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#list-projects[
+list projects] endpoint.
+
+
 Upgrades
 --------
 
-TODO
+* Update Antlr to 3.5.2.
+
+* Update ASM to 5.0.3.
+
+* Update CodeMirror to 4.10.0-6-gd0a2dda.
+
+* Update Guava to 18.0.
+
+* Update Guice to 4.0-beta5.
+
+* Update GWT to 2.7.
+
+* Update Jetty to 9.2.2.
+
+* Update JGit to 3.6.0.201412230720-r.
+
+* Update Lucene to 4.10.2.
+
+* Update Pegdown to 1.4.2.
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index abcdf3f..aec1845 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -59,6 +59,7 @@
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.codec.binary.StringUtils;
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.joda.time.DateTime;
@@ -194,10 +195,10 @@
     Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen();
     modifier.rebaseEdit(edit, current);
     edit = editUtil.byChange(change).get();
-    assertByteArray(fileUtil.getContent(edit.getChange().getProject(), edit
-        .getRevision().get(), FILE_NAME), CONTENT_NEW);
-    assertByteArray(fileUtil.getContent(edit.getChange().getProject(), edit
-        .getRevision().get(), FILE_NAME2), CONTENT_NEW2);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
+        ObjectId.fromString(edit.getRevision().get()), FILE_NAME), CONTENT_NEW);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
+        ObjectId.fromString(edit.getRevision().get()), FILE_NAME2), CONTENT_NEW2);
     assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(
         current.getPatchSetId());
     Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
@@ -218,10 +219,10 @@
     RestResponse r = adminSession.post(urlRebase());
     assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
     edit = editUtil.byChange(change).get();
-    assertByteArray(fileUtil.getContent(edit.getChange().getProject(), edit
-        .getRevision().get(), FILE_NAME), CONTENT_NEW);
-    assertByteArray(fileUtil.getContent(edit.getChange().getProject(), edit
-        .getRevision().get(), FILE_NAME2), CONTENT_NEW2);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
+        ObjectId.fromString(edit.getRevision().get()), FILE_NAME), CONTENT_NEW);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
+        ObjectId.fromString(edit.getRevision().get()), FILE_NAME2), CONTENT_NEW2);
     assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(
         current.getPatchSetId());
     Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
@@ -235,9 +236,8 @@
     assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
         .isEqualTo(RefUpdate.Result.FORCED);
     edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), CONTENT_NEW);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
     editUtil.delete(edit.get());
     edit = editUtil.byChange(change);
     assertThat(edit.isPresent()).isFalse();
@@ -364,8 +364,8 @@
         RefUpdate.Result.FORCED);
     edit = editUtil.byChange(change);
     try {
-      fileUtil.getContent(edit.get().getChange().getProject(),
-          edit.get().getRevision().get(), FILE_NAME);
+      fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+          ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
       fail("ResourceNotFoundException expected");
     } catch (ResourceNotFoundException rnfe) {
     }
@@ -377,8 +377,8 @@
     assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
     Optional<ChangeEdit> edit = editUtil.byChange(change);
     try {
-      fileUtil.getContent(edit.get().getChange().getProject(),
-          edit.get().getRevision().get(), FILE_NAME);
+      fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+          ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
       fail("ResourceNotFoundException expected");
     } catch (ResourceNotFoundException rnfe) {
     }
@@ -397,8 +397,8 @@
         SC_NO_CONTENT);
     Optional<ChangeEdit> edit = editUtil.byChange(change);
     try {
-      fileUtil.getContent(edit.get().getChange().getProject(),
-          edit.get().getRevision().get(), FILE_NAME);
+      fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+          ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
       fail("ResourceNotFoundException expected");
     } catch (ResourceNotFoundException rnfe) {
     }
@@ -412,9 +412,8 @@
     assertThat(modifier.restoreFile(edit.get(), FILE_NAME)).isEqualTo(
         RefUpdate.Result.FORCED);
     edit = editUtil.byChange(change2);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), CONTENT_OLD);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
   }
 
   @Test
@@ -424,9 +423,8 @@
     assertThat(adminSession.post(urlEdit2(), in).getStatusCode()).isEqualTo(
         SC_NO_CONTENT);
     Optional<ChangeEdit> edit = editUtil.byChange(change2);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), CONTENT_OLD);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
   }
 
   @Test
@@ -436,15 +434,13 @@
     assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
         .isEqualTo(RefUpdate.Result.FORCED);
     edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), CONTENT_NEW);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
     assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2)))
         .isEqualTo(RefUpdate.Result.FORCED);
     edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), CONTENT_NEW2);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW2);
   }
 
   @Test
@@ -454,16 +450,14 @@
     assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
         .isEqualTo(SC_NO_CONTENT);
     Optional<ChangeEdit> edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), CONTENT_NEW);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
     in.content = RestSession.newRawInput(CONTENT_NEW2);
     assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
         .isEqualTo(SC_NO_CONTENT);
     edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), CONTENT_NEW2);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW2);
   }
 
   @Test
@@ -474,9 +468,8 @@
     assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
         .isEqualTo(SC_NO_CONTENT);
     Optional<ChangeEdit> edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), CONTENT_NEW);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
   }
 
   @Test
@@ -485,9 +478,8 @@
     assertThat(adminSession.put(urlEditFile()).getStatusCode()).isEqualTo(
         SC_NO_CONTENT);
     Optional<ChangeEdit> edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), "".getBytes());
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), "".getBytes());
   }
 
   @Test
@@ -495,9 +487,8 @@
     assertThat(adminSession.post(urlEdit()).getStatusCode()).isEqualTo(
         SC_NO_CONTENT);
     Optional<ChangeEdit> edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME), CONTENT_OLD);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
   }
 
   @Test
@@ -518,26 +509,14 @@
   }
 
   @Test
-  public void getFileContentTypeRest() throws Exception {
-    Put.Input in = new Put.Input();
-    in.content = RestSession.newRawInput(CONTENT_NEW);
-    assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode())
-        .isEqualTo(SC_NO_CONTENT);
-    RestResponse r = adminSession.get(urlEditFileContentType());
-    assertThat(r.getStatusCode()).isEqualTo(SC_OK);
-    String res = newGson().fromJson(r.getReader(), String.class);
-    assertThat(res).isEqualTo("application/octet-stream");
-  }
-
-  @Test
   public void getFileNotFoundRest() throws Exception {
     assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
     assertThat(adminSession.delete(urlEditFile()).getStatusCode()).isEqualTo(
         SC_NO_CONTENT);
     Optional<ChangeEdit> edit = editUtil.byChange(change);
     try {
-      fileUtil.getContent(edit.get().getChange().getProject(),
-          edit.get().getRevision().get(), FILE_NAME);
+      fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+          ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
       fail("ResourceNotFoundException expected");
     } catch (ResourceNotFoundException rnfe) {
     }
@@ -552,9 +531,8 @@
     assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW)))
         .isEqualTo(RefUpdate.Result.FORCED);
     edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME2), CONTENT_NEW);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW);
   }
 
   @Test
@@ -564,15 +542,13 @@
     assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW)))
         .isEqualTo(RefUpdate.Result.FORCED);
     edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME2), CONTENT_NEW);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW);
     assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW2)))
         .isEqualTo(RefUpdate.Result.FORCED);
     edit = editUtil.byChange(change);
-    assertByteArray(
-        fileUtil.getContent(edit.get().getChange().getProject(), edit.get()
-            .getRevision().get(), FILE_NAME2), CONTENT_NEW2);
+    assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
+        ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW2);
   }
 
   @Test
@@ -684,13 +660,6 @@
         + FILE_NAME;
   }
 
-  private String urlEditFileContentType() {
-    return urlEdit()
-        + "/"
-        + FILE_NAME
-        + "/type";
-  }
-
   private String urlGetFiles() {
     return urlEdit()
         + "?list";
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java b/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
index fc5bb56..ffdae9d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PluginData.java
@@ -14,9 +14,8 @@
 
 package com.google.gerrit.common;
 
-import com.google.common.base.Objects;
-
 import java.io.File;
+import java.util.Objects;
 
 public class PluginData {
   public final String name;
@@ -33,14 +32,14 @@
   public boolean equals(Object obj) {
     if (obj instanceof PluginData) {
       PluginData o = (PluginData) obj;
-      return Objects.equal(name, o.name) && Objects.equal(version, o.version)
-          && Objects.equal(pluginFile, o.pluginFile);
+      return Objects.equals(name, o.name) && Objects.equals(version, o.version)
+          && Objects.equals(pluginFile, o.pluginFile);
     }
     return super.equals(obj);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hashCode(name, version, pluginFile);
+    return Objects.hash(name, version, pluginFile);
   }
 }
\ No newline at end of file
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 a060245..f76252f 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
@@ -56,7 +56,6 @@
   protected AccountGeneralPreferences.ChangeScreen changeScreen;
   protected List<String> archiveFormats;
   protected int largeChangeSize;
-  protected boolean newFeatures;
   protected String replyLabel;
   protected String replyTitle;
 
@@ -303,14 +302,6 @@
     archiveFormats = formats;
   }
 
-  public boolean getNewFeatures() {
-    return newFeatures;
-  }
-
-  public void setNewFeatures(boolean n) {
-    newFeatures = n;
-  }
-
   public String getReplyTitle() {
     return replyTitle;
   }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
index 5b2fd78..9dc92a8 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/EditInfo.java
@@ -19,7 +19,6 @@
 public class EditInfo {
   public CommitInfo commit;
   public String baseRevision;
-  public Map<String, ActionInfo> actions;
   public Map<String, FetchInfo> fetch;
   public Map<String, FileInfo> files;
 }
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
index b1312db..c4e2167 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/Resources.java
@@ -81,9 +81,6 @@
   @Source("merge.png")
   public ImageResource merge();
 
-  @Source("removeReviewer.png")
-  public ImageResource removeReviewer();
-
   @Source("deleteNormal.png")
   public ImageResource deleteNormal();
 
diff --git a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/removeReviewer.png b/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/removeReviewer.png
deleted file mode 100644
index 5a3e6f0..0000000
--- a/gerrit-gwtui-common/src/main/resources/com/google/gerrit/client/removeReviewer.png
+++ /dev/null
Binary files differ
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 a9cdbb3..8af7e74 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
@@ -628,9 +628,7 @@
   }
 
   public static boolean isChangeScreen2() {
-    if (!Gerrit.getConfig().getNewFeatures()) {
-      return false;
-    } else if (changeScreen2) {
+    if (changeScreen2) {
       return true;
     }
 
@@ -707,7 +705,7 @@
       sbs1(token, baseId, id, patchIndex, patchSetDetail, patchTable, topView,
           PatchScreen.Type.UNIFIED);
       return;
-    } else if ("cm".equals(panel) && Gerrit.getConfig().getNewFeatures()) {
+    } else if ("cm".equals(panel)) {
       if (Gerrit.isSignedIn()
           && DiffView.UNIFIED_DIFF.equals(Gerrit.getUserAccount()
               .getGeneralPreferences().getDiffView())) {
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 6fb2fe0..9e78866 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
@@ -196,32 +196,29 @@
     formGrid.setWidget(row, fieldIdx, dateTimePanel);
     row++;
 
-    if (Gerrit.getConfig().getNewFeatures()) {
-      formGrid.setText(row, labelIdx, "");
-      formGrid.setWidget(row, fieldIdx, relativeDateInChangeTable);
-      row++;
+    formGrid.setText(row, labelIdx, "");
+    formGrid.setWidget(row, fieldIdx, relativeDateInChangeTable);
+    row++;
 
-      formGrid.setText(row, labelIdx, "");
-      formGrid.setWidget(row, fieldIdx, sizeBarInChangeTable);
-      row++;
+    formGrid.setText(row, labelIdx, "");
+    formGrid.setWidget(row, fieldIdx, sizeBarInChangeTable);
+    row++;
 
-      formGrid.setText(row, labelIdx, "");
-      formGrid.setWidget(row, fieldIdx, legacycidInChangeTable);
-      row++;
-    }
+    formGrid.setText(row, labelIdx, "");
+    formGrid.setWidget(row, fieldIdx, legacycidInChangeTable);
+    row++;
 
     formGrid.setText(row, labelIdx, Util.C.commentVisibilityLabel());
     formGrid.setWidget(row, fieldIdx, commentVisibilityStrategy);
     row++;
 
-    if (Gerrit.getConfig().getNewFeatures()) {
-      formGrid.setText(row, labelIdx, Util.C.changeScreenLabel());
-      formGrid.setWidget(row, fieldIdx, changeScreen);
-      row++;
+    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);
-    }
+    formGrid.setText(row, labelIdx, Util.C.diffViewLabel());
+    formGrid.setWidget(row, fieldIdx, diffView);
+
     add(formGrid);
 
     save = new Button(Util.C.buttonSaveChanges());
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 b5090ca..bee2f53 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
@@ -19,7 +19,6 @@
 import com.google.gerrit.client.actions.ActionInfo;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
-import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.reviewdb.client.Change;
@@ -45,12 +44,6 @@
   private static final Binder uiBinder = GWT.create(Binder.class);
 
   @UiField Button cherrypick;
-  @UiField Button deleteChange;
-  @UiField Button deleteRevision;
-  @UiField Button deleteEdit;
-  @UiField Button publishEdit;
-  @UiField Button rebaseEdit;
-  @UiField Button publish;
   @UiField Button rebase;
   @UiField Button revert;
   @UiField Button submit;
@@ -95,7 +88,6 @@
 
     initChangeActions(info, hasUser);
     initRevisionActions(info, revInfo, hasUser);
-    initEditActions(info, info.edit(), hasUser);
   }
 
   private void initChangeActions(ChangeInfo info, boolean hasUser) {
@@ -105,7 +97,6 @@
     actions.copyKeysIntoChildren("id");
 
     if (hasUser) {
-      a2b(actions, "/", deleteChange);
       a2b(actions, "abandon", abandon);
       a2b(actions, "restore", restore);
       a2b(actions, "revert", revert);
@@ -116,26 +107,6 @@
     }
   }
 
-  private void initEditActions(ChangeInfo info, EditInfo editInfo,
-      boolean hasUser) {
-    if (!info.has_edit() || !info.current_revision().equals(editInfo.name())) {
-      return;
-    }
-    NativeMap<ActionInfo> actions = editInfo.has_actions()
-        ? editInfo.actions()
-        : NativeMap.<ActionInfo> create();
-    actions.copyKeysIntoChildren("id");
-
-    if (hasUser) {
-      a2b(actions, "/", deleteEdit);
-      a2b(actions, "publish", publishEdit);
-      a2b(actions, "rebase", rebaseEdit);
-      for (String id : filterNonCore(actions)) {
-        add(new ActionButton(info, editInfo, actions.get(id)));
-      }
-    }
-  }
-
   private void initRevisionActions(ChangeInfo info, RevisionInfo revInfo,
       boolean hasUser) {
     NativeMap<ActionInfo> actions = revInfo.has_actions()
@@ -154,9 +125,7 @@
             .append(action.label())
             .closeDiv());
       }
-      a2b(actions, "/", deleteRevision);
       a2b(actions, "cherrypick", cherrypick);
-      a2b(actions, "publish", publish);
       a2b(actions, "rebase", rebase);
       for (String id : filterNonCore(actions)) {
         add(new ActionButton(info, revInfo, actions.get(id)));
@@ -201,36 +170,6 @@
     abandonAction.show();
   }
 
-  @UiHandler("publish")
-  void onPublish(@SuppressWarnings("unused") ClickEvent e) {
-    DraftActions.publish(changeId, revision);
-  }
-
-  @UiHandler("deleteEdit")
-  void onDeleteEdit(@SuppressWarnings("unused") ClickEvent e) {
-    EditActions.deleteEdit(changeId);
-  }
-
-  @UiHandler("publishEdit")
-  void onPublishEdit(@SuppressWarnings("unused") ClickEvent e) {
-    EditActions.publishEdit(changeId);
-  }
-
-  @UiHandler("rebaseEdit")
-  void onRebaseEdit(@SuppressWarnings("unused") ClickEvent e) {
-    EditActions.rebaseEdit(changeId);
-  }
-
-  @UiHandler("deleteRevision")
-  void onDeleteRevision(@SuppressWarnings("unused") ClickEvent e) {
-    DraftActions.delete(changeId, revision);
-  }
-
-  @UiHandler("deleteChange")
-  void onDeleteChange(@SuppressWarnings("unused") ClickEvent e) {
-    DraftActions.delete(changeId);
-  }
-
   @UiHandler("restore")
   void onRestore(@SuppressWarnings("unused") ClickEvent e) {
     if (restoreAction == null) {
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 496e048..40d732a 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
@@ -45,12 +45,6 @@
       line-height: BUTTON_HEIGHT;
     }
 
-    #change_actions button.red {
-      color: #d14836;
-      background-color: #d14836;
-    }
-    #change_actions button.red div {color: #fff;}
-
     #change_actions button.submit {
       float: right;
       background-color: #4d90fe;
@@ -75,29 +69,10 @@
     <g:Button ui:field='revert' styleName='' visible='false'>
       <div><ui:msg>Revert</ui:msg></div>
     </g:Button>
-    <g:Button ui:field='deleteChange' styleName='' visible='false'>
-      <div><ui:msg>Delete Change</ui:msg></div>
-    </g:Button>
-    <g:Button ui:field='deleteRevision' styleName='' visible='false'>
-      <div><ui:msg>Delete Revision</ui:msg></div>
-    </g:Button>
-    <g:Button ui:field='publish' styleName='' visible='false'>
-      <div><ui:msg>Publish</ui:msg></div>
-    </g:Button>
-    <g:Button ui:field='deleteEdit' styleName='' visible='false'>
-      <div><ui:msg>Delete Edit</ui:msg></div>
-    </g:Button>
-    <g:Button ui:field='publishEdit' styleName='' visible='false'>
-      <div><ui:msg>Publish Edit</ui:msg></div>
-    </g:Button>
-    <g:Button ui:field='rebaseEdit' styleName='' visible='false'>
-      <div><ui:msg>Rebase Edit</ui:msg></div>
-    </g:Button>
-
-    <g:Button ui:field='abandon' styleName='{style.red}' visible='false'>
+    <g:Button ui:field='abandon' styleName='' visible='false'>
       <div><ui:msg>Abandon</ui:msg></div>
     </g:Button>
-    <g:Button ui:field='restore' styleName='{style.red}' visible='false'>
+    <g:Button ui:field='restore' styleName='' visible='false'>
       <div><ui:msg>Restore</ui:msg></div>
     </g:Button>
     <g:Button ui:field='followUp' styleName='' visible='false'>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
new file mode 100644
index 0000000..6e2ee00
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileAction.java
@@ -0,0 +1,71 @@
+//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.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.reviewdb.client.Change;
+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.Widget;
+import com.google.gwtexpui.globalkey.client.GlobalKey;
+import com.google.gwtexpui.user.client.PluginSafePopupPanel;
+
+class AddFileAction {
+  private final Change.Id changeId;
+  private final RevisionInfo revision;
+  private final ChangeScreen2.Style style;
+  private final Widget addButton;
+
+  private AddFileBox addBox;
+  private PopupPanel popup;
+
+  AddFileAction(Change.Id changeId, RevisionInfo revision,
+      ChangeScreen2.Style style, Widget addButton) {
+    this.changeId = changeId;
+    this.revision = revision;
+    this.style = style;
+    this.addButton = addButton;
+  }
+
+  public void onEdit() {
+    if (popup != null) {
+      popup.hide();
+      return;
+    }
+
+    if (addBox == null) {
+      addBox = new AddFileBox(changeId, revision);
+    }
+    addBox.clearPath();
+
+    final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
+    p.setStyleName(style.replyBox());
+    p.addAutoHidePartner(addButton.getElement());
+    p.addCloseHandler(new CloseHandler<PopupPanel>() {
+      @Override
+      public void onClose(CloseEvent<PopupPanel> event) {
+        if (popup == p) {
+          popup = null;
+        }
+      }
+    });
+    p.add(addBox);
+    p.showRelativeTo(addButton);
+    GlobalKey.dialog(p);
+    addBox.setFocus(true);
+    popup = p;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java
new file mode 100644
index 0000000..6e47f26
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.java
@@ -0,0 +1,159 @@
+//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.changes.ChangeApi;
+import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.client.ui.RemoteSuggestBox;
+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.JsArrayString;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.logical.shared.SelectionEvent;
+import com.google.gwt.event.logical.shared.SelectionHandler;
+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.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class AddFileBox extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, AddFileBox> {}
+  private static final Binder uiBinder = GWT.create(Binder.class);
+
+  private final Change.Id changeId;
+  private final RevisionInfo revision;
+
+  @UiField Button open;
+  @UiField Button cancel;
+
+  @UiField(provided = true)
+  RemoteSuggestBox path;
+
+  AddFileBox(Change.Id changeId, RevisionInfo revision) {
+    this.changeId = changeId;
+    this.revision = revision;
+
+    path = new RemoteSuggestBox(new PathSuggestOracle());
+    path.addSelectionHandler(new SelectionHandler<String>() {
+      @Override
+      public void onSelection(SelectionEvent<String> event) {
+        open(event.getSelectedItem());
+      }
+    });
+    path.addCloseHandler(new CloseHandler<RemoteSuggestBox>() {
+      @Override
+      public void onClose(CloseEvent<RemoteSuggestBox> event) {
+        hide();
+      }
+    });
+
+    initWidget(uiBinder.createAndBindUi(this));
+  }
+
+  void setFocus(boolean focus) {
+    path.setFocus(focus);
+  }
+
+  void clearPath() {
+    path.setText("");
+  }
+
+  @UiHandler("open")
+  void onOpen(@SuppressWarnings("unused") ClickEvent e) {
+    open(path.getText());
+  }
+
+  private void open(String path) {
+    hide();
+    Gerrit.display(Dispatcher.toEditScreen(
+        new PatchSet.Id(changeId, revision._number()),
+        path));
+  }
+
+  @UiHandler("cancel")
+  void onCancel(@SuppressWarnings("unused") ClickEvent e) {
+    hide();
+  }
+
+  private void hide() {
+    for (Widget w = getParent(); w != null; w = w.getParent()) {
+      if (w instanceof PopupPanel) {
+        ((PopupPanel) w).hide();
+        break;
+      }
+    }
+  }
+
+  private class PathSuggestOracle extends HighlightSuggestOracle {
+    @Override
+    protected void onRequestSuggestions(final Request req, final Callback cb) {
+      ChangeApi.revision(changeId.get(), revision.name())
+        .view("files")
+        .addParameter("q", req.getQuery())
+        .background()
+        .get(new AsyncCallback<JsArrayString>() {
+            @Override
+            public void onSuccess(JsArrayString result) {
+              List<Suggestion> r = new ArrayList<>();
+              for (String path : Natives.asList(result)) {
+                r.add(new PathSuggestion(path));
+              }
+              cb.onSuggestionsReady(req, new Response(r));
+            }
+
+            @Override
+            public void onFailure(Throwable caught) {
+              List<Suggestion> none = Collections.emptyList();
+              cb.onSuggestionsReady(req, new Response(none));
+            }
+          });
+    }
+  }
+
+  private static class PathSuggestion implements Suggestion {
+    private final String path;
+
+    PathSuggestion(String path) {
+      this.path = path;
+    }
+
+    @Override
+    public String getDisplayString() {
+      return path;
+    }
+
+    @Override
+    public String getReplacementString() {
+      return path;
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditFileBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.ui.xml
similarity index 65%
rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditFileBox.ui.xml
rename to gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.ui.xml
index 3a0e0be..d8236e6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditFileBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/AddFileBox.ui.xml
@@ -16,40 +16,22 @@
 -->
 <ui:UiBinder
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
-    xmlns:c='urn:import:com.google.gwtexpui.globalkey.client'
-    xmlns:f='urn:import:com.google.gerrit.client.change'
+    xmlns:u='urn:import:com.google.gerrit.client.ui'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'>
   <ui:with field='res' type='com.google.gerrit.client.change.Resources'/>
   <ui:style>
-    .fileContent {
-      background-color: white;
-      font-family: monospace;
-    }
     .cancel { float: right; }
   </ui:style>
   <g:HTMLPanel>
     <div class='{res.style.section}'>
-      <div>
-         <ui:msg>Path:</ui:msg>
-      </div>
-      <div>
-        <f:FileTextBox ui:field='file' visibleLength='79'/>
-      </div>
-      <div>
-        <ui:msg>Content:</ui:msg>
-      </div>
-      <c:NpTextArea
-         visibleLines='30'
-         characterWidth='78'
-         styleName='{style.fileContent}'
-         ui:field='content'/>
+      <ui:msg>Path: <u:RemoteSuggestBox ui:field='path' visibleLength='86'/></ui:msg>
     </div>
     <div class='{res.style.section}'>
-      <g:Button ui:field='save'
-          title='Create new revision edit'
+      <g:Button ui:field='open'
+          title='Open file in editor'
           styleName='{res.style.button}'>
         <ui:attribute name='title'/>
-        <div><ui:msg>Save</ui:msg></div>
+        <div><ui:msg>Open</ui:msg></div>
       </g:Button>
       <g:Button ui:field='cancel'
           styleName='{res.style.button}'
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java
index a7d4945..be6879e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.java
@@ -49,4 +49,8 @@
   String indirectAncestor();
   String merged();
   String abandoned();
+
+  String deleteChangeEdit();
+  String deleteDraftChange();
+  String deleteDraftRevision();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.properties
index 8852d4e..682cd18 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeConstants.properties
@@ -30,3 +30,9 @@
 indirectAncestor = Indirect ancestor
 merged = Merged
 abandoned = Abandoned
+
+deleteChangeEdit = Delete Change Edit?\n\
+  \n\
+  All changes made in the edit revision will be lost.
+deleteDraftChange = Delete Draft Change?
+deleteDraftRevision = Delete Draft Revision?
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 5d6fa38..2ef12e3 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
@@ -77,6 +77,7 @@
 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.Anchor;
 import com.google.gwt.user.client.ui.Button;
@@ -179,6 +180,12 @@
   @UiField Element patchSetsText;
   @UiField Button download;
   @UiField Button reply;
+  @UiField Button publishEdit;
+  @UiField Button rebaseEdit;
+  @UiField Button deleteEdit;
+  @UiField Button publish;
+  @UiField Button deleteChange;
+  @UiField Button deleteRevision;
   @UiField Button openAll;
   @UiField Button editMode;
   @UiField Button reviewMode;
@@ -193,7 +200,7 @@
   private IncludedInAction includedInAction;
   private PatchSetsAction patchSetsAction;
   private DownloadAction downloadAction;
-  private EditFileAction editFileAction;
+  private AddFileAction addFileAction;
 
   public ChangeScreen2(Change.Id changeId, String base, String revision,
       boolean openReplyBox, FileTable.Mode mode) {
@@ -215,11 +222,15 @@
     CallbackGroup group = new CallbackGroup();
     if (Gerrit.isSignedIn()) {
       ChangeApi.editWithFiles(changeId.get(), group.add(
-          new GerritCallback<EditInfo>() {
+          new AsyncCallback<EditInfo>() {
             @Override
             public void onSuccess(EditInfo result) {
               edit = result;
             }
+
+            @Override
+            public void onFailure(Throwable caught) {
+            }
           }));
     }
     loadChangeInfo(true, group.addFinal(
@@ -269,8 +280,6 @@
     reviewers.init(style, ccText);
     hashtags.init(style);
 
-    initReplyButton();
-
     keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
     keysNavigation.add(new KeyCommand(0, 'u', Util.C.upToChangeList()) {
       @Override
@@ -335,12 +344,15 @@
     }
   }
 
-  private void initReplyButton() {
-    reply.setTitle(Gerrit.getConfig().getReplyTitle());
-    reply.setHTML(new SafeHtmlBuilder()
-      .openDiv()
-      .append(Gerrit.getConfig().getReplyLabel())
-      .closeDiv());
+  private void initReplyButton(ChangeInfo info, String revision) {
+    if (!info.revision(revision).is_edit()) {
+      reply.setTitle(Gerrit.getConfig().getReplyTitle());
+      reply.setHTML(new SafeHtmlBuilder()
+        .openDiv()
+        .append(Gerrit.getConfig().getReplyLabel())
+        .closeDiv());
+      reply.setVisible(true);
+    }
   }
 
   private void gotoSibling(final int offset) {
@@ -376,6 +388,19 @@
     }
   }
 
+  private void initChangeAction(ChangeInfo info) {
+    if (info.status() == Status.DRAFT) {
+      NativeMap<ActionInfo> actions = info.has_actions()
+          ? info.actions()
+          : NativeMap.<ActionInfo> create();
+      actions.copyKeysIntoChildren("id");
+      if (actions.containsKey("/")) {
+        deleteChange.setVisible(true);
+        deleteChange.setTitle(actions.get("/").title());
+      }
+    }
+  }
+
   private void initRevisionsAction(ChangeInfo info, String revision) {
     int currentPatchSet;
     if (info.current_revision() != null
@@ -401,6 +426,23 @@
     patchSetsAction = new PatchSetsAction(
         info.legacy_id(), revision,
         style, headerLine, patchSets);
+
+    RevisionInfo revInfo = info.revision(revision);
+    if (revInfo.draft()) {
+      NativeMap<ActionInfo> actions = revInfo.has_actions()
+          ? revInfo.actions()
+          : NativeMap.<ActionInfo> create();
+      actions.copyKeysIntoChildren("id");
+
+      if (actions.containsKey("publish")) {
+        publish.setVisible(true);
+        publish.setTitle(actions.get("publish").title());
+      }
+      if (actions.containsKey("/")) {
+        deleteRevision.setVisible(true);
+        deleteRevision.setTitle(actions.get("/").title());
+      }
+    }
   }
 
   private void initDownloadAction(ChangeInfo info, String revision) {
@@ -444,14 +486,23 @@
         editMode.setVisible(fileTableMode == FileTable.Mode.REVIEW);
         addFile.setVisible(!editMode.isVisible());
         reviewMode.setVisible(!editMode.isVisible());
-        editFileAction = new EditFileAction(
-            new PatchSet.Id(changeId, edit == null ? rev._number() : 0),
-            "", "", style, editMessage, reply);
+        addFileAction = new AddFileAction(
+            changeId, info.revision(revision),
+            style, addFile);
       } else {
         editMode.setVisible(false);
         addFile.setVisible(false);
         reviewMode.setVisible(false);
       }
+
+      if (rev.is_edit()) {
+        if (isEditBasedOnCurrentPatchSet(info)) {
+          publishEdit.setVisible(true);
+        } else {
+          rebaseEdit.setVisible(true);
+        }
+        deleteEdit.setVisible(true);
+      }
     }
   }
 
@@ -466,6 +517,13 @@
         info.revisions().values());
   }
 
+  private boolean isEditBasedOnCurrentPatchSet(ChangeInfo info) {
+    JsArray<RevisionInfo> revList = info.revisions().values();
+    RevisionInfo.sortRevisionInfoByNumber(revList);
+    int currentPatchSetOrEdit = revList.get(revList.length() - 1)._number();
+    return currentPatchSetOrEdit == 0;
+  }
+
   private void initEditMessageAction(ChangeInfo info, String revision) {
     RevisionInfo revisionInfo = info.revision(revision);
     NativeMap<ActionInfo> actions = revisionInfo.actions();
@@ -487,6 +545,42 @@
     }
   }
 
+  @UiHandler("publishEdit")
+  void onPublishEdit(@SuppressWarnings("unused") ClickEvent e) {
+    EditActions.publishEdit(changeId);
+  }
+
+  @UiHandler("rebaseEdit")
+  void onRebaseEdit(@SuppressWarnings("unused") ClickEvent e) {
+    EditActions.rebaseEdit(changeId);
+  }
+
+  @UiHandler("deleteEdit")
+  void onDeleteEdit(@SuppressWarnings("unused") ClickEvent e) {
+    if (Window.confirm(Resources.C.deleteChangeEdit())) {
+      EditActions.deleteEdit(changeId);
+    }
+  }
+
+  @UiHandler("publish")
+  void onPublish(@SuppressWarnings("unused") ClickEvent e) {
+    DraftActions.publish(changeId, revision);
+  }
+
+  @UiHandler("deleteRevision")
+  void onDeleteRevision(@SuppressWarnings("unused") ClickEvent e) {
+    if (Window.confirm(Resources.C.deleteDraftRevision())) {
+      DraftActions.delete(changeId, revision);
+    }
+  }
+
+  @UiHandler("deleteChange")
+  void onDeleteChange(@SuppressWarnings("unused") ClickEvent e) {
+    if (Window.confirm(Resources.C.deleteDraftChange())) {
+      DraftActions.delete(changeId);
+    }
+  }
+
   @Override
   public void registerKeys() {
     super.registerKeys();
@@ -621,7 +715,7 @@
 
   @UiHandler("addFile")
   void onAddFile(@SuppressWarnings("unused") ClickEvent e) {
-    editFileAction.onEdit();
+    addFileAction.onEdit();
   }
 
   private void refreshFileTable() {
@@ -975,7 +1069,9 @@
     renderOwner(info);
     renderActionTextDate(info);
     renderDiffBaseListBox(info);
+    initReplyButton(info, revision);
     initIncludedInAction(info);
+    initChangeAction(info);
     initRevisionsAction(info, revision);
     initDownloadAction(info, revision);
     initProjectLinks(info);
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 85c93a1..cecf5b3 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
@@ -272,10 +272,10 @@
       height: BUTTON_HEIGHT;
       line-height: BUTTON_HEIGHT;
     }
-    button.quickApprove {
+    button.highlight {
       background-color: #4d90fe;
     }
-    button.quickApprove div { color: #fff; }
+    button.highlight div { color: #fff; }
 
     .sectionHeader {
       position: relative;
@@ -355,14 +355,36 @@
         <div class='{style.headerButtons} {style.infoLineHeaderButtons}'>
           <g:Button ui:field='reply'
               styleName=''
-              title=''>
+              title=''
+              visible='false'>
             <ui:attribute name='title'/>
           </g:Button>
           <c:QuickApprove ui:field='quickApprove'
-              styleName='{style.quickApprove}'
+              styleName='{style.highlight}'
               title='Apply score with one click'>
             <ui:attribute name='title'/>
           </c:QuickApprove>
+          <g:Button ui:field='publishEdit'
+              styleName='{style.highlight}' visible='false'>
+            <div><ui:msg>Publish Edit</ui:msg></div>
+          </g:Button>
+          <g:Button ui:field='rebaseEdit'
+              styleName='{style.highlight}' visible='false'>
+            <div><ui:msg>Rebase Edit</ui:msg></div>
+          </g:Button>
+          <g:Button ui:field='deleteEdit' styleName='' visible='false'>
+            <div><ui:msg>Delete Edit</ui:msg></div>
+          </g:Button>
+          <g:Button ui:field='publish'
+              styleName='{style.highlight}' visible='false'>
+            <div><ui:msg>Publish</ui:msg></div>
+          </g:Button>
+          <g:Button ui:field='deleteChange' styleName='' visible='false'>
+            <div><ui:msg>Delete Change</ui:msg></div>
+          </g:Button>
+          <g:Button ui:field='deleteRevision' styleName='' visible='false'>
+            <div><ui:msg>Delete Revision</ui:msg></div>
+          </g:Button>
         </div>
       </div>
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditFileAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditFileAction.java
deleted file mode 100644
index 951c84d..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditFileAction.java
+++ /dev/null
@@ -1,80 +0,0 @@
-//Copyright (C) 2013 The Android Open Source Project
-//
-//Licensed under the Apache License, Version 2.0 (the "License");
-//you may not use this file except in compliance with the License.
-//You may obtain a copy of the License at
-//
-//http://www.apache.org/licenses/LICENSE-2.0
-//
-//Unless required by applicable law or agreed to in writing, software
-//distributed under the License is distributed on an "AS IS" BASIS,
-//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//See the License for the specific language governing permissions and
-//limitations under the License.
-
-package com.google.gerrit.client.change;
-
-import com.google.gerrit.reviewdb.client.PatchSet;
-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.Widget;
-import com.google.gwtexpui.globalkey.client.GlobalKey;
-import com.google.gwtexpui.user.client.PluginSafePopupPanel;
-
-class EditFileAction {
- private final PatchSet.Id id;
- private final String content;
- private final String file;
- private final ChangeScreen2.Style style;
- private final Widget editMessageButton;
- private final Widget relativeTo;
-
- private EditFileBox editBox;
- private PopupPanel popup;
-
- EditFileAction(
-     PatchSet.Id id,
-     String content,
-     String file,
-     ChangeScreen2.Style style,
-     Widget editButton,
-     Widget relativeTo) {
-   this.id = id;
-   this.content = content;
-   this.file = file;
-   this.style = style;
-   this.editMessageButton = editButton;
-   this.relativeTo = relativeTo;
- }
-
- public void onEdit() {
-   if (popup != null) {
-     popup.hide();
-     return;
-   }
-
-   if (editBox == null) {
-     editBox = new EditFileBox(
-         id,
-         content,
-         file);
-   }
-
-   final PluginSafePopupPanel p = new PluginSafePopupPanel(true);
-   p.setStyleName(style.replyBox());
-   p.addAutoHidePartner(editMessageButton.getElement());
-   p.addCloseHandler(new CloseHandler<PopupPanel>() {
-     @Override
-     public void onClose(CloseEvent<PopupPanel> event) {
-       if (popup == p) {
-         popup = null;
-       }
-     }
-   });
-   p.add(editBox);
-   p.showRelativeTo(relativeTo);
-   GlobalKey.dialog(p);
-   popup = p;
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditFileBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditFileBox.java
deleted file mode 100644
index 67f9265..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/EditFileBox.java
+++ /dev/null
@@ -1,119 +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.VoidResult;
-import com.google.gerrit.client.changes.ChangeFileApi;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.ui.TextBoxChangeListener;
-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.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-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.Button;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.TextBoxBase;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtexpui.globalkey.client.NpTextArea;
-
-class EditFileBox extends Composite {
-  interface Binder extends UiBinder<HTMLPanel, EditFileBox> {}
-  private static final Binder uiBinder = GWT.create(Binder.class);
-
-  private final PatchSet.Id id;
-  private final String fileName;
-  private final String fileContent;
-
-  @UiField FileTextBox file;
-  @UiField NpTextArea content;
-  @UiField Button save;
-  @UiField Button cancel;
-
-  EditFileBox(
-      PatchSet.Id id,
-      String fileC,
-      String fileName) {
-    this.id = id;
-    this.fileName = fileName;
-    this.fileContent = fileC;
-    initWidget(uiBinder.createAndBindUi(this));
-    new EditFileBoxListener(content);
-    new EditFileBoxListener(file);
-  }
-
-  @Override
-  protected void onLoad() {
-    file.set(id, content);
-    file.setText(fileName);
-    file.setEnabled(fileName.isEmpty());
-    content.setText(fileContent);
-    save.setEnabled(false);
-    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-      @Override
-      public void execute() {
-        if (fileName.isEmpty()) {
-          file.setFocus(true);
-        } else {
-          content.setFocus(true);
-        }
-      }});
-  }
-
-  @UiHandler("save")
-  void onSave(@SuppressWarnings("unused") ClickEvent e) {
-    ChangeFileApi.putContent(id, file.getText(), content.getText(),
-        new GerritCallback<VoidResult>() {
-          @Override
-          public void onSuccess(VoidResult result) {
-            Gerrit.display(PageLinks.toChangeInEditMode(id.getParentKey()));
-            hide();
-          }
-        });
-  }
-
-  @UiHandler("cancel")
-  void onCancel(@SuppressWarnings("unused") ClickEvent e) {
-    hide();
-  }
-
-  protected void hide() {
-    for (Widget w = getParent(); w != null; w = w.getParent()) {
-      if (w instanceof PopupPanel) {
-        ((PopupPanel) w).hide();
-        break;
-      }
-    }
-  }
-
-  private class EditFileBoxListener extends TextBoxChangeListener {
-    public EditFileBoxListener(TextBoxBase base) {
-      super(base);
-    }
-
-    @Override
-    public void onTextChanged(String newText) {
-      save.setEnabled(!file.getText().trim().isEmpty()
-          && !newText.trim().equals(fileContent));
-    }
-  }
-}
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 74600ab..317e9ac 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
@@ -16,7 +16,7 @@
 
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.VoidResult;
-import com.google.gerrit.client.changes.ChangeFileApi;
+import com.google.gerrit.client.changes.ChangeEditApi;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.ui.TextBoxChangeListener;
 import com.google.gerrit.common.PageLinks;
@@ -79,7 +79,7 @@
   @UiHandler("save")
   void onSave(@SuppressWarnings("unused") ClickEvent e) {
     save.setEnabled(false);
-    ChangeFileApi.putMessage(changeId, message.getText().trim(),
+    ChangeEditApi.putMessage(changeId.get(), message.getText().trim(),
         new GerritCallback<VoidResult>() {
           @Override
           public void onSuccess(VoidResult result) {
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 429dd55..c0fe3e0 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
@@ -18,7 +18,7 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.VoidResult;
 import com.google.gerrit.client.changes.ChangeApi;
-import com.google.gerrit.client.changes.ChangeFileApi;
+import com.google.gerrit.client.changes.ChangeEditApi;
 import com.google.gerrit.client.changes.CommentInfo;
 import com.google.gerrit.client.changes.ReviewInfo;
 import com.google.gerrit.client.changes.Util;
@@ -317,7 +317,7 @@
 
     void onDelete(int idx) {
       String path = list.get(idx).path();
-      ChangeFileApi.deleteContent(curr, path,
+      ChangeEditApi.delete(curr.getParentKey().get(), path,
           new AsyncCallback<VoidResult>() {
             @Override
             public void onSuccess(VoidResult result) {
@@ -333,7 +333,7 @@
 
     void onRestore(int idx) {
       String path = list.get(idx).path();
-      ChangeFileApi.restoreContent(curr, path,
+      ChangeEditApi.restore(curr.getParentKey().get(), path,
           new AsyncCallback<VoidResult>() {
             @Override
             public void onSuccess(VoidResult result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTextBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTextBox.java
deleted file mode 100644
index 52d2a25..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTextBox.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (C) 2014 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.client.change;
-
-import com.google.gerrit.client.changes.ChangeFileApi;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.rpc.RestApi;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwtexpui.globalkey.client.NpTextArea;
-import com.google.gwtexpui.globalkey.client.NpTextBox;
-
-class FileTextBox extends NpTextBox {
-  private HandlerRegistration blurHandler;
-  private NpTextArea textArea;
-  private PatchSet.Id id;
-
-  @Override
-  protected void onLoad() {
-    blurHandler = addBlurHandler(new BlurHandler() {
-      @Override
-      public void onBlur(BlurEvent event) {
-        loadFileContent();
-      }
-    });
-  }
-
-  @Override
-  protected void onUnload() {
-    super.onUnload();
-    blurHandler.removeHandler();
-  }
-
-  void set(PatchSet.Id id, NpTextArea content) {
-    this.id = id;
-    this.textArea = content;
-  }
-
-  private void loadFileContent() {
-    ChangeFileApi.getContent(id, getText(), new GerritCallback<String>() {
-      @Override
-      public void onSuccess(String result) {
-        textArea.setText(result);
-      }
-
-      @Override
-      public void onFailure(Throwable caught) {
-        if (RestApi.isNotFound(caught)) {
-          // that means that the file doesn't exist in the repository
-        } else {
-          super.onFailure(caught);
-        }
-      }
-    });
-  }
-}
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 6e86730..6638dbe 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
@@ -46,6 +46,10 @@
       setVisible(false);
       return;
     }
+    if (info.revision(commit).is_edit() || info.revision(commit).draft()) {
+      setVisible(false);
+      return;
+    }
 
     String qName = null;
     String qValueStr = null;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css
index 612ffed..bb7cb27 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/common.css
@@ -29,7 +29,7 @@
 .popup button,
 .popup input[type='button'] {
   margin: 0 3px 0 0;
-  border-color: rgba(0, 0, 0, 0.1);
+  border-color: rgba(0, 0, 0, 0.15) !important;
   text-align: center;
   font-size: 11px;
   font-weight: bold;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java
new file mode 100644
index 0000000..ca5d434
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java
@@ -0,0 +1,90 @@
+// 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.changes;
+
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.HttpCallback;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/** REST API helpers to remotely edit a change. */
+public class ChangeEditApi {
+  /** Get file (or commit message) contents. */
+  public static void get(PatchSet.Id id, String path,
+      HttpCallback<NativeString> cb) {
+    RestApi api;
+    if (id.get() != 0) {
+      // Read from a published revision, when change edit doesn't
+      // exist for the caller, or is not currently active.
+      api = ChangeApi.revision(id).view("files").id(path).view("content");
+    } else if (Patch.COMMIT_MSG.equals(path)) {
+      api = editMessage(id.getParentKey().get());
+    } else {
+      api = editFile(id.getParentKey().get(), path);
+    }
+    api.get(cb);
+  }
+
+  /** Put message into a change edit. */
+  public static void putMessage(int id, String m, GerritCallback<VoidResult> cb) {
+    editMessage(id).put(m, cb);
+  }
+
+  /** Put contents into a file or commit message in a change edit. */
+  public static void put(int id, String path, String content,
+      GerritCallback<VoidResult> cb) {
+    if (Patch.COMMIT_MSG.equals(path)) {
+      putMessage(id, content, cb);
+    } else {
+      editFile(id, path).put(content, cb);
+    }
+  }
+
+  /** Delete a file in the pending edit. */
+  public static void delete(int id, String path, AsyncCallback<VoidResult> cb) {
+    editFile(id, path).delete(cb);
+  }
+
+  /** Restore (undo delete/modify) a file in the pending edit. */
+  public static void restore(int id, String path, AsyncCallback<VoidResult> cb) {
+    Input in = Input.create();
+    in.restore_path(path);
+    ChangeApi.edit(id).post(in, cb);
+  }
+
+  private static RestApi editMessage(int id) {
+    return ChangeApi.change(id).view("edit:message");
+  }
+
+  private static RestApi editFile(int id, String path) {
+    return ChangeApi.edit(id).id(path);
+  }
+
+  private static class Input extends JavaScriptObject {
+    static Input create() {
+      return createObject().cast();
+    }
+
+    final native void restore_path(String p) /*-{ this.restore_path=p }-*/;
+
+    protected Input() {
+    }
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeFileApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeFileApi.java
deleted file mode 100644
index c25f0cf..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeFileApi.java
+++ /dev/null
@@ -1,166 +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.client.changes;
-
-import com.google.gerrit.client.VoidResult;
-import com.google.gerrit.client.rpc.GerritCallback;
-import com.google.gerrit.client.rpc.NativeString;
-import com.google.gerrit.client.rpc.RestApi;
-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.JavaScriptObject;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-
-/**
- * A collection of static methods which work on the Gerrit REST API for specific
- * files in a change.
- */
-public class ChangeFileApi {
-  static abstract class CallbackWrapper<I, O> implements AsyncCallback<I> {
-    protected AsyncCallback<O> wrapped;
-
-    public CallbackWrapper(AsyncCallback<O> callback) {
-      wrapped = callback;
-    }
-
-    @Override
-    public abstract void onSuccess(I result);
-
-    @Override
-    public void onFailure(Throwable caught) {
-      wrapped.onFailure(caught);
-    }
-  }
-
-  private static CallbackWrapper<NativeString, String> wrapper(
-      AsyncCallback<String> cb) {
-    return new CallbackWrapper<NativeString, String>(cb) {
-      @Override
-      public void onSuccess(NativeString b64) {
-        if (b64 != null) {
-          wrapped.onSuccess(b64decode(b64.asString()));
-        }
-      }
-    };
-  }
-
-  /** Get the contents of a File in a PatchSet or change edit. */
-  public static void getContent(PatchSet.Id id, String filename,
-      AsyncCallback<String> cb) {
-    contentEditOrPs(id, filename).get(wrapper(cb));
-  }
-
-  /** Get the content type of a File in a PatchSet or change edit. */
-  public static void getContentType(PatchSet.Id id, String filename,
-      AsyncCallback<String> cb) {
-    contentTypeEditOrPs(id, filename).get(
-        new CallbackWrapper<NativeString, String>(cb) {
-          @Override
-          public void onSuccess(NativeString str) {
-            if (str != null) {
-              wrapped.onSuccess(str.asString());
-            }
-          }
-        });
-  }
-
-  /**
-   * Get the contents of a file or commit message in a PatchSet or change
-   * edit.
-   **/
-  public static void getContentOrMessage(PatchSet.Id id, String path,
-      AsyncCallback<String> cb) {
-    RestApi api = (Patch.COMMIT_MSG.equals(path) && id.get() == 0)
-        ? messageEdit(id)
-        : contentEditOrPs(id, path);
-    api.get(wrapper(cb));
-  }
-
-  /** Put contents into a File in a change edit. */
-  public static void putContent(PatchSet.Id id, String filename,
-      String content, GerritCallback<VoidResult> result) {
-    contentEdit(id.getParentKey(), filename).put(content, result);
-  }
-
-  /** Put contents into a File or commit message in a change edit. */
-  public static void putContentOrMessage(PatchSet.Id id, String path,
-      String content, GerritCallback<VoidResult> result) {
-    if (Patch.COMMIT_MSG.equals(path)) {
-      putMessage(id, content, result);
-    } else {
-      contentEdit(id.getParentKey(), path).put(content, result);
-    }
-  }
-
-  /** Put message into a change edit. */
-  private static void putMessage(PatchSet.Id id, String m,
-      GerritCallback<VoidResult> r) {
-    putMessage(id.getParentKey(), m, r);
-  }
-
-  /** Put message into a change edit. */
-  public static void putMessage(Change.Id id, String m,
-      GerritCallback<VoidResult> r) {
-    ChangeApi.change(id.get()).view("edit:message").put(m, r);
-  }
-
-  /** Restore contents of a File in a change edit. */
-  public static void restoreContent(PatchSet.Id id, String filename,
-      AsyncCallback<VoidResult> result) {
-    Input in = Input.create();
-    in.restore_path(filename);
-    ChangeApi.edit(id.getParentKey().get()).post(in, result);
-  }
-
-  /** Delete a file from a change edit. */
-  public static void deleteContent(PatchSet.Id id, String filename,
-      AsyncCallback<VoidResult> result) {
-    contentEdit(id.getParentKey(), filename).delete(result);
-  }
-
-  private static RestApi contentEditOrPs(PatchSet.Id id, String filename) {
-    return id.get() == 0
-        ? contentEdit(id.getParentKey(), filename)
-        : ChangeApi.revision(id).view("files").id(filename).view("content");
-  }
-
-  private static RestApi messageEdit(PatchSet.Id id) {
-    return ChangeApi.change(id.getParentKey().get()).view("edit:message");
-  }
-
-  private static RestApi contentTypeEditOrPs(PatchSet.Id id, String filename) {
-    return id.get() == 0
-        ? contentEdit(id.getParentKey(), filename).view("type")
-        : ChangeApi.revision(id).view("files").id(filename).view("type");
-  }
-
-  private static RestApi contentEdit(Change.Id id, String filename) {
-    return ChangeApi.edit(id.get()).id(filename);
-  }
-
-  private static native String b64decode(String a) /*-{ return window.atob(a); }-*/;
-
-  private static class Input extends JavaScriptObject {
-    final native void restore_path(String p) /*-{ if(p)this.restore_path=p; }-*/;
-
-    static Input create() {
-      return (Input) createObject();
-    }
-
-    protected Input() {
-    }
-  }
-}
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 5f5cf8f..a4f3145 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
@@ -122,8 +122,7 @@
     }
     table.setText(R_SUBMIT_TYPE, 1, submitType);
     final Change.Status status = chg.getStatus();
-    if (Gerrit.getConfig().getNewFeatures()
-        && (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT))) {
+    if (status.equals(Change.Status.NEW) || status.equals(Change.Status.DRAFT)) {
       table.getRowFormatter().setVisible(R_MERGE_TEST, true);
       table.setText(R_MERGE_TEST, 1, changeDetail.isMergeable() ? Util.C
           .changeInfoBlockCanMergeYes() : Util.C.changeInfoBlockCanMergeNo());
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 ca7157a..4c37a28 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
@@ -59,19 +59,21 @@
   private static final int C_SIZE = 9;
   private static final int BASE_COLUMNS = 10;
 
-
-  private final boolean useNewFeatures = Gerrit.getConfig().getNewFeatures();
   private final List<Section> sections;
   private int columns;
+  private boolean showLegacyId;
   private List<String> labelNames;
 
   public ChangeTable2() {
     super(Util.C.changeItemHelp());
-    columns = useNewFeatures ? BASE_COLUMNS : BASE_COLUMNS - 1;
+    columns = BASE_COLUMNS;
     labelNames = Collections.emptyList();
 
     if (Gerrit.isSignedIn()) {
       keysAction.add(new StarKeyCommand(0, 's', Util.C.changeTableStar()));
+      showLegacyId = Gerrit.getUserAccount()
+          .getGeneralPreferences()
+          .isLegacycidInChangeTable();
     }
 
     sections = new ArrayList<>();
@@ -83,19 +85,14 @@
     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());
-    }
+    table.setText(0, C_SIZE, Util.C.changeTableColumnSize());
 
     final FlexCellFormatter fmt = table.getFlexCellFormatter();
     fmt.addStyleName(0, C_STAR, Gerrit.RESOURCES.css().iconHeader());
     for (int i = C_ID; i < columns; i++) {
       fmt.addStyleName(0, i, Gerrit.RESOURCES.css().dataHeader());
     }
-
-    if (!Gerrit.isSignedIn() ||
-       (!Gerrit.getUserAccount().getGeneralPreferences()
-         .isLegacycidInChangeTable())) {
+    if (!showLegacyId) {
       fmt.addStyleName(0, C_ID, Gerrit.RESOURCES.css().dataHeaderHidden());
     }
 
@@ -152,22 +149,16 @@
     for (int i = C_ID; i < columns; i++) {
       fmt.addStyleName(row, i, Gerrit.RESOURCES.css().dataCell());
     }
+    if (!showLegacyId) {
+      fmt.addStyleName(row, C_ID, Gerrit.RESOURCES.css().dataCellHidden());
+    }
     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());
+    fmt.addStyleName(row, C_SIZE, Gerrit.RESOURCES.css().cSIZE());
 
-    if (!Gerrit.isSignedIn() ||
-       (!Gerrit.getUserAccount().getGeneralPreferences()
-         .isLegacycidInChangeTable())) {
-      fmt.addStyleName(row, C_ID, Gerrit.RESOURCES.css().dataCellHidden());
-    }
-
-    int i = C_SIZE;
-    if (useNewFeatures) {
-      fmt.addStyleName(row, i++, Gerrit.RESOURCES.css().cSIZE());
-    }
-    for (; i < columns; i++) {
+    for (int i = C_SIZE + 1; i < columns; i++) {
       fmt.addStyleName(row, i, Gerrit.RESOURCES.css().cAPPROVAL());
     }
   }
@@ -185,7 +176,7 @@
     }
     Collections.sort(labelNames);
 
-    int baseColumns = useNewFeatures ? BASE_COLUMNS : BASE_COLUMNS - 1;
+    int baseColumns = BASE_COLUMNS;
     if (baseColumns + labelNames.size() < columns) {
       int n = columns - (baseColumns + labelNames.size());
       for (int row = 0; row < table.getRowCount(); row++) {
@@ -228,7 +219,7 @@
     Change.Status status = c.status();
     if (status != Change.Status.NEW) {
       table.setText(row, C_STATUS, Util.toLongString(status));
-    } else if (!c.mergeable() && useNewFeatures) {
+    } else if (!c.mergeable()) {
       table.setText(row, C_STATUS, Util.C.changeTableNotMergeable());
     }
 
@@ -248,20 +239,19 @@
     } 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++;
+    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 displayInfo = Gerrit.isSignedIn() && Gerrit.getUserAccount()
         .getGeneralPreferences().isShowInfoInReviewCategory();
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
index 887978f..c829976 100644
--- 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
@@ -341,14 +341,13 @@
       @Override
       public void run() {
         if (cm.extras().hasActiveLine()) {
-          newDraft(cm);
+          newDraft(cm, cm.getLineNumber(cm.extras().activeLine()) + 1);
         }
       }
     };
   }
 
-  private void newDraft(CodeMirror cm) {
-    int line = cm.getLineNumber(cm.extras().activeLine()) + 1;
+  void newDraft(CodeMirror cm, int line) {
     if (cm.somethingSelected()) {
       FromTo fromTo = cm.getSelectedRange();
       Pos end = fromTo.to();
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
index 1fe85b3..b23a8cf 100644
--- 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
@@ -18,11 +18,11 @@
 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 com.google.gwt.user.client.rpc.AsyncCallback;
 
 import java.util.Collections;
 import java.util.Comparator;
@@ -53,39 +53,55 @@
     }
   }
 
-  private GerritCallback<NativeMap<JsArray<CommentInfo>>> publishedBase() {
-    return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+  private AsyncCallback<NativeMap<JsArray<CommentInfo>>> publishedBase() {
+    return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
       @Override
       public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
         publishedBase = sort(result.get(path));
       }
+
+      @Override
+      public void onFailure(Throwable caught) {
+      }
     };
   }
 
-  private GerritCallback<NativeMap<JsArray<CommentInfo>>> publishedRevision() {
-    return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+  private AsyncCallback<NativeMap<JsArray<CommentInfo>>> publishedRevision() {
+    return new AsyncCallback<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));
+      public void onFailure(Throwable caught) {
       }
     };
   }
 
-  private GerritCallback<NativeMap<JsArray<CommentInfo>>> draftsRevision() {
-    return new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
+  private AsyncCallback<NativeMap<JsArray<CommentInfo>>> draftsBase() {
+    return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
+      @Override
+      public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
+        draftsBase = sort(result.get(path));
+      }
+
+      @Override
+      public void onFailure(Throwable caught) {
+      }
+    };
+  }
+
+  private AsyncCallback<NativeMap<JsArray<CommentInfo>>> draftsRevision() {
+    return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
       @Override
       public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
         draftsRevision = sort(result.get(path));
       }
+
+      @Override
+      public void onFailure(Throwable caught) {
+      }
     };
   }
 
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
index 4eec4ba..70bde00 100644
--- 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
@@ -32,6 +32,7 @@
 import com.google.gerrit.extensions.common.Theme;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.Patch.ChangeType;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.event.dom.client.ChangeEvent;
@@ -147,7 +148,13 @@
 
     setIgnoreWhitespace(prefs.ignoreWhitespace());
     tabWidth.setIntValue(prefs.tabSize());
-    lineLength.setIntValue(prefs.lineLength());
+    if (Patch.COMMIT_MSG.equals(view.getPath())) {
+      lineLength.setEnabled(false);
+      lineLength.setIntValue(72);
+    } else {
+      lineLength.setEnabled(true);
+      lineLength.setIntValue(prefs.lineLength());
+    }
     syntaxHighlighting.setValue(prefs.syntaxHighlighting());
     whitespaceErrors.setValue(prefs.showWhitespaceErrors());
     showTabs.setValue(prefs.showTabs());
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
index 2f22cdd..62d2eac 100644
--- 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
@@ -73,6 +73,10 @@
       color: #dddd00;
     }
 
+    .box input.gwt-TextBox:disabled {
+      background-color: #cacaca;
+    }
+
     .box .gwt-ToggleButton {
       position: relative;
       height: 19px;
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 2675235..5195024 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
@@ -40,6 +40,7 @@
 import com.google.gerrit.common.PageLinks;
 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.PatchSet;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
@@ -167,26 +168,36 @@
   protected void onLoad() {
     super.onLoad();
 
-    CallbackGroup cmGroup = new CallbackGroup();
-    CodeMirror.initLibrary(cmGroup.<Void> addEmpty());
+    CallbackGroup group1 = new CallbackGroup();
+    final CallbackGroup group2 = new CallbackGroup();
 
-    final CallbackGroup group = new CallbackGroup();
-    final AsyncCallback<Void> themeCallback = group.addEmpty();
-    final AsyncCallback<Void> modeInjectorCb = group.addEmpty();
+    CodeMirror.initLibrary(group1.add(new AsyncCallback<Void>() {
+      final AsyncCallback<Void> themeCallback = group2.addEmpty();
+
+      @Override
+      public void onSuccess(Void result) {
+        // Load theme after CM library to ensure theme can override CSS.
+        ThemeLoader.loadTheme(prefs.theme(), themeCallback);
+      }
+
+      @Override
+      public void onFailure(Throwable caught) {
+      }
+    }));
 
     DiffApi.diff(revision, path)
       .base(base)
       .wholeFile()
       .intraline(prefs.intralineDifference())
       .ignoreWhitespace(prefs.ignoreWhitespace())
-      .get(cmGroup.addFinal(new GerritCallback<DiffInfo>() {
+      .get(group1.addFinal(new GerritCallback<DiffInfo>() {
+        final AsyncCallback<Void> modeInjectorCb = group2.addEmpty();
+
         @Override
         public void onSuccess(DiffInfo diffInfo) {
           diff = diffInfo;
           fileSize = bucketFileSize(diffInfo);
 
-          // Load theme after CM library to ensure theme can override CSS.
-          ThemeLoader.loadTheme(prefs.theme(), themeCallback);
           if (prefs.syntaxHighlighting()) {
             if (fileSize.compareTo(FileSize.SMALL) > 0) {
               modeInjectorCb.onSuccess(null);
@@ -200,22 +211,26 @@
       }));
 
     if (Gerrit.isSignedIn()) {
-      ChangeApi.edit(changeId.get(), group.add(
-          new GerritCallback<EditInfo>() {
+      ChangeApi.edit(changeId.get(), group2.add(
+          new AsyncCallback<EditInfo>() {
             @Override
             public void onSuccess(EditInfo result) {
               edit = result;
             }
+
+            @Override
+            public void onFailure(Throwable caught) {
+            }
           }));
     }
 
     final CommentsCollections comments = new CommentsCollections();
-    comments.load(base, revision, path, group);
+    comments.load(base, revision, path, group2);
 
     RestApi call = ChangeApi.detail(changeId.get());
     ChangeList.addOptions(call, EnumSet.of(
         ListChangesOption.ALL_REVISIONS));
-    call.get(group.add(new GerritCallback<ChangeInfo>() {
+    call.get(group2.add(new AsyncCallback<ChangeInfo>() {
       @Override
       public void onSuccess(ChangeInfo info) {
         info.revisions().copyKeysIntoChildren("name");
@@ -230,9 +245,14 @@
         diffTable.set(prefs, list, diff, edit != null, currentPatchSet,
             info.status().isOpen());
         header.setChangeInfo(info);
-      }}));
+      }
 
-    ConfigInfoCache.get(changeId, group.addFinal(
+      @Override
+      public void onFailure(Throwable caught) {
+      }
+    }));
+
+    ConfigInfoCache.get(changeId, group2.addFinal(
         new ScreenLoadCallback<ConfigInfoCache.Entry>(SideBySide2.this) {
           @Override
           protected void preDisplay(ConfigInfoCache.Entry result) {
@@ -270,7 +290,7 @@
         cmB.refresh();
       }
     });
-    setLineLength(prefs.lineLength());
+    setLineLength(Patch.COMMIT_MSG.equals(path) ? 72 : prefs.lineLength());
     diffTable.refresh();
 
     if (startLine == 0) {
@@ -797,21 +817,18 @@
   private GutterClickHandler onGutterClick(final CodeMirror cm) {
     return new GutterClickHandler() {
       @Override
-      public void handle(CodeMirror instance, int line, String gutter,
+      public void handle(CodeMirror instance, final int line, String gutter,
           NativeEvent clickEvent) {
         if (clickEvent.getButton() == NativeEvent.BUTTON_LEFT
             && !clickEvent.getMetaKey()
             && !clickEvent.getAltKey()
             && !clickEvent.getCtrlKey()
             && !clickEvent.getShiftKey()) {
-          if (!(cm.extras().hasActiveLine() &&
-              cm.getLineNumber(cm.extras().activeLine()) == line)) {
-            cm.setCursor(Pos.create(line));
-          }
+          cm.setCursor(Pos.create(line));
           Scheduler.get().scheduleDeferred(new ScheduledCommand() {
             @Override
             public void execute() {
-              commentManager.insertNewDraft(cm).run();
+              commentManager.newDraft(cm, line + 1);
             }
           });
         }
@@ -931,6 +948,10 @@
       .inject(cb);
   }
 
+  String getPath() {
+    return path;
+  }
+
   DiffPreferences getPrefs() {
     return prefs;
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
index 70855ce..6566ee4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
@@ -14,17 +14,24 @@
 
 package com.google.gerrit.client.editor;
 
+import static com.google.gwt.dom.client.Style.Visibility.HIDDEN;
+import static com.google.gwt.dom.client.Style.Visibility.VISIBLE;
+
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.JumpKeys;
 import com.google.gerrit.client.VoidResult;
 import com.google.gerrit.client.account.DiffPreferences;
 import com.google.gerrit.client.changes.ChangeApi;
-import com.google.gerrit.client.changes.ChangeFileApi;
+import com.google.gerrit.client.changes.ChangeEditApi;
 import com.google.gerrit.client.changes.ChangeInfo;
 import com.google.gerrit.client.diff.FileInfo;
 import com.google.gerrit.client.diff.Header;
 import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.HttpCallback;
+import com.google.gerrit.client.rpc.HttpResponse;
+import com.google.gerrit.client.rpc.NativeString;
+import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.common.PageLinks;
@@ -68,11 +75,14 @@
   private final String path;
   private DiffPreferences prefs;
   private CodeMirror cm;
-  private String type;
+  private HttpResponse<NativeString> content;
 
   @UiField Element header;
   @UiField Element project;
   @UiField Element filePath;
+  @UiField Element cursLine;
+  @UiField Element cursCol;
+  @UiField Element dirty;
   @UiField Button close;
   @UiField Button save;
   @UiField Element editor;
@@ -100,10 +110,12 @@
   protected void onLoad() {
     super.onLoad();
 
-    CallbackGroup cmGroup = new CallbackGroup();
-    final CallbackGroup group = new CallbackGroup();
-    CodeMirror.initLibrary(cmGroup.add(new AsyncCallback<Void>() {
-      final AsyncCallback<Void> themeCallback = group.addEmpty();
+    CallbackGroup group1 = new CallbackGroup();
+    CallbackGroup group2 = new CallbackGroup();
+    final CallbackGroup group3 = new CallbackGroup();
+
+    CodeMirror.initLibrary(group1.add(new AsyncCallback<Void>() {
+      final AsyncCallback<Void> themeCallback = group3.addEmpty();
 
       @Override
       public void onSuccess(Void result) {
@@ -116,36 +128,55 @@
       }
     }));
 
-    if (prefs.syntaxHighlighting() && !Patch.COMMIT_MSG.equals(path)) {
-      final AsyncCallback<Void> modeInjectorCb = group.addEmpty();
-      ChangeFileApi.getContentType(revision, path,
-          cmGroup.add(new GerritCallback<String>() {
-            @Override
-            public void onSuccess(String result) {
-              ModeInfo mode = ModeInfo.findMode(result, path);
-              type = mode != null ? mode.mime() : null;
-              injectMode(result, modeInjectorCb);
-            }
-          }));
-    }
-    cmGroup.done();
-
     ChangeApi.detail(revision.getParentKey().get(),
-        group.add(new GerritCallback<ChangeInfo>() {
+        group1.add(new AsyncCallback<ChangeInfo>() {
           @Override
           public void onSuccess(ChangeInfo c) {
             project.setInnerText(c.project());
             SafeHtml.setInnerHTML(filePath, Header.formatPath(path, null, null));
           }
-        }));
 
-    ChangeFileApi.getContentOrMessage(revision, path,
-        group.addFinal(new ScreenLoadCallback<String>(this) {
           @Override
-          protected void preDisplay(String content) {
-            initEditor(content);
+          public void onFailure(Throwable caught) {
           }
         }));
+
+    ChangeEditApi.get(revision, path,
+        group2.add(new HttpCallback<NativeString>() {
+          final AsyncCallback<Void> modeCallback = group3.addEmpty();
+
+          @Override
+          public void onSuccess(HttpResponse<NativeString> fc) {
+            content = fc;
+            if (prefs.syntaxHighlighting()) {
+              injectMode(fc.getContentType(), modeCallback);
+            } else {
+              modeCallback.onSuccess(null);
+            }
+          }
+
+          @Override
+          public void onFailure(Throwable e) {
+            // "Not Found" means it's a new file.
+            if (RestApi.isNotFound(e)) {
+              content = null;
+              modeCallback.onSuccess(null);
+            } else {
+              GerritCallback.showFailure(e);
+            }
+          }
+        }));
+
+    group3.addListener(new ScreenLoadCallback<Void>(this) {
+      @Override
+      protected void preDisplay(Void result) {
+        initEditor(content);
+        content = null;
+      }
+    });
+    group1.done();
+    group2.done();
+    group3.done();
   }
 
   @Override
@@ -172,18 +203,19 @@
     });
 
     generation = cm.changeGeneration(true);
-    save.setEnabled(false);
+    setClean(true);
     cm.on(new ChangesHandler() {
       @Override
       public void handle(CodeMirror cm) {
-        save.setEnabled(!cm.isClean(generation));
+        setClean(cm.isClean(generation));
       }
     });
 
     cm.adjustHeight(header.getOffsetHeight());
     cm.on("cursorActivity", updateCursorPosition());
     cm.extras().showTabs(prefs.showTabs());
-    cm.extras().lineLength(prefs.lineLength());
+    cm.extras().lineLength(
+        Patch.COMMIT_MSG.equals(path) ? 72 : prefs.lineLength());
     cm.refresh();
     cm.focus();
     updateActiveLine();
@@ -223,10 +255,15 @@
     Gerrit.display(PageLinks.toChangeInEditMode(revision.getParentKey()));
   }
 
-  private void initEditor(String content) {
-    ModeInfo mode = prefs.syntaxHighlighting()
-        ? ModeInfo.findMode(type, path)
-        : null;
+  private void initEditor(HttpResponse<NativeString> file) {
+    ModeInfo mode = null;
+    String content = "";
+    if (file != null) {
+      content = file.getResult().asString();
+      if (prefs.syntaxHighlighting()) {
+        mode = ModeInfo.findMode(file.getContentType(), path);
+      }
+    }
     cm = CodeMirror.create(editor, Configuration.create()
         .set("value", content)
         .set("readOnly", false)
@@ -272,9 +309,16 @@
 
   private void updateActiveLine() {
     Pos p = cm.getCursor("end");
+    cursLine.setInnerText(Integer.toString(p.line() + 1));
+    cursCol.setInnerText(Integer.toString(p.ch() + 1));
     cm.extras().activeLine(cm.getLineHandleVisualStart(p.line()));
   }
 
+  private void setClean(boolean clean) {
+    save.setEnabled(!clean);
+    dirty.getStyle().setVisibility(!clean ? VISIBLE : HIDDEN);
+  }
+
   private Runnable save() {
     return new Runnable() {
       @Override
@@ -282,12 +326,12 @@
         if (!cm.isClean(generation)) {
           String text = cm.getValue();
           final int g = cm.changeGeneration(false);
-          ChangeFileApi.putContentOrMessage(revision, path, text,
+          ChangeEditApi.put(revision.getParentKey().get(), path, text,
               new GerritCallback<VoidResult>() {
                 @Override
                 public void onSuccess(VoidResult result) {
                   generation = g;
-                  save.setEnabled(!cm.isClean(g));
+                  setClean(cm.isClean(g));
                 }
               });
         }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
index 50bd3bc..1fd44d2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
@@ -62,6 +62,34 @@
     .path {
       white-space: nowrap;
     }
+
+    .statusLine {
+      position: fixed;
+      bottom: 0;
+      left: 0;
+      width: 175px;
+      height: 19px;
+      background-color: #f7f7f7;
+      border-top: 1px solid #ddd;
+      border-right: 1px solid #ddd;
+    }
+    .statusLine div {
+      height: inherit;
+    }
+
+    .cursorPosition {
+      display: inline-block;
+      margin: 0 5px 0 35px;
+      white-space: nowrap;
+    }
+
+    .dirty {
+      display: inline-block;
+      margin: 0 5px 0 5px;
+      padding: 0 0 0 5px;
+      border-left: 1px solid #ddd;
+      font-weight: bold;
+    }
   </ui:style>
   <g:HTMLPanel>
     <div class='{style.headerLine}' ui:field='header'>
@@ -82,5 +110,9 @@
        <span class='{style.path}'><span ui:field='project'/> / <span ui:field='filePath'/></span>
     </div>
     <div ui:field='editor' />
+    <div class='{style.statusLine}'>
+      <div class='{style.cursorPosition}'><span ui:field='cursLine'/> : <span ui:field='cursCol'/></div>
+      <div class='{style.dirty}' ui:field='dirty'>Unsaved</div>
+    </div>
   </g:HTMLPanel>
 </ui:UiBinder>
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 6a9ddb5..071ca72 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
@@ -42,8 +42,8 @@
  * processing it.
  */
 public class CallbackGroup {
-  private final List<CallbackImpl<?>> callbacks;
-  private final Set<CallbackImpl<?>> remaining;
+  private final List<CallbackGlue> callbacks;
+  private final Set<CallbackGlue> remaining;
   private boolean finalAdded;
 
   private boolean failed;
@@ -76,6 +76,27 @@
     return handleAdd(cb);
   }
 
+  public <T> HttpCallback<T> add(HttpCallback<T> cb) {
+    checkFinalAdded();
+    if (failed) {
+      cb.onFailure(failedThrowable);
+      return new HttpCallback<T>() {
+        @Override
+        public void onSuccess(HttpResponse<T> result) {
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+        }
+      };
+    }
+
+    HttpCallbackImpl<T> w = new HttpCallbackImpl<>(cb);
+    callbacks.add(w);
+    remaining.add(w);
+    return w;
+  }
+
   public <T> Callback<T> addFinal(final AsyncCallback<T> cb) {
     checkFinalAdded();
     finalAdded = true;
@@ -84,7 +105,7 @@
 
   public void done() {
     finalAdded = true;
-    applyAllSuccess();
+    apply();
   }
 
   public void addListener(AsyncCallback<Void> cb) {
@@ -99,19 +120,30 @@
     addListener(group.<Void> addEmpty());
   }
 
-  private void applyAllSuccess() {
-    if (!failed && finalAdded && remaining.isEmpty()) {
-      for (CallbackImpl<?> cb : callbacks) {
-        cb.applySuccess();
-      }
-      callbacks.clear();
-    }
+  private void success(CallbackGlue cb) {
+    remaining.remove(cb);
+    apply();
   }
 
-  private void applyAllFailed() {
-    if (failed && finalAdded && remaining.isEmpty()) {
-      for (CallbackImpl<?> cb : callbacks) {
-        cb.applyFailed();
+  private <T> void failure(CallbackGlue w, Throwable caught) {
+    if (!failed) {
+      failed = true;
+      failedThrowable = caught;
+    }
+    remaining.remove(w);
+    apply();
+  }
+
+  private void apply() {
+    if (finalAdded && remaining.isEmpty()) {
+      if (failed) {
+        for (CallbackGlue cb : callbacks) {
+          cb.applyFailed();
+        }
+      } else {
+        for (CallbackGlue cb : callbacks) {
+          cb.applySuccess();
+        }
       }
       callbacks.clear();
     }
@@ -139,7 +171,12 @@
       extends AsyncCallback<T>, com.google.gwtjsonrpc.common.AsyncCallback<T> {
   }
 
-  private class CallbackImpl<T> implements Callback<T> {
+  private interface CallbackGlue {
+    void applySuccess();
+    void applyFailed();
+  }
+
+  private class CallbackImpl<T> implements Callback<T>, CallbackGlue {
     AsyncCallback<T> delegate;
     T result;
 
@@ -150,21 +187,16 @@
     @Override
     public void onSuccess(T value) {
       this.result = value;
-      remaining.remove(this);
-      CallbackGroup.this.applyAllSuccess();
+      success(this);
     }
 
     @Override
     public void onFailure(Throwable caught) {
-      if (!failed) {
-        failed = true;
-        failedThrowable = caught;
-      }
-      remaining.remove(this);
-      CallbackGroup.this.applyAllFailed();
+      failure(this, caught);
     }
 
-    void applySuccess() {
+    @Override
+    public void applySuccess() {
       AsyncCallback<T> cb = delegate;
       if (cb != null) {
         delegate = null;
@@ -173,7 +205,8 @@
       }
     }
 
-    void applyFailed() {
+    @Override
+    public void applyFailed() {
       AsyncCallback<T> cb = delegate;
       if (cb != null) {
         delegate = null;
@@ -182,4 +215,44 @@
       }
     }
   }
+
+  private class HttpCallbackImpl<T> implements HttpCallback<T>, CallbackGlue {
+    private HttpCallback<T> delegate;
+    private HttpResponse<T> result;
+
+    HttpCallbackImpl(HttpCallback<T> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void onSuccess(HttpResponse<T> result) {
+      this.result = result;
+      success(this);
+    }
+
+    @Override
+    public void onFailure(Throwable caught) {
+      failure(this, caught);
+    }
+
+    @Override
+    public void applySuccess() {
+      HttpCallback<T> cb = delegate;
+      if (cb != null) {
+        delegate = null;
+        cb.onSuccess(result);
+        result = null;
+      }
+    }
+
+    @Override
+    public void applyFailed() {
+      HttpCallback<T> cb = delegate;
+      if (cb != null) {
+        delegate = null;
+        result = null;
+        cb.onFailure(failedThrowable);
+      }
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
index 08ff7d9..bccd237 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/GerritCallback.java
@@ -23,7 +23,6 @@
 import com.google.gerrit.common.errors.NoSuchEntityException;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.common.errors.NotSignedInException;
-import com.google.gwt.core.client.GWT;
 import com.google.gwt.user.client.rpc.InvocationException;
 import com.google.gwtjsonrpc.client.RemoteJsonException;
 import com.google.gwtjsonrpc.client.ServerUnavailableException;
@@ -35,6 +34,10 @@
     com.google.gwt.user.client.rpc.AsyncCallback<T> {
   @Override
   public void onFailure(final Throwable caught) {
+    showFailure(caught);
+  }
+
+  public static void showFailure(Throwable caught) {
     if (isNotSignedIn(caught) || isInvalidXSRF(caught)) {
       new NotSignedInDialog().center();
 
@@ -70,7 +73,6 @@
       new ErrorDialog(RpcConstants.C.errorServerUnavailable()).center();
 
     } else {
-      GWT.log(getClass().getName() + " caught " + caught, caught);
       new ErrorDialog(caught).center();
     }
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpCallback.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpCallback.java
new file mode 100644
index 0000000..a97642e
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpCallback.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.rpc;
+
+/** AsyncCallback supplied with HTTP response headers. */
+public interface HttpCallback<T> {
+  void onSuccess(HttpResponse<T> result);
+  void onFailure(Throwable caught);
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpResponse.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpResponse.java
new file mode 100644
index 0000000..969dd30
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/HttpResponse.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.client.rpc;
+
+import com.google.gwt.http.client.Response;
+
+/** Wraps decoded server reply with HTTP headers. */
+public class HttpResponse<T> {
+  private final Response httpResponse;
+  private final String contentType;
+  private final T result;
+
+  HttpResponse(Response httpResponse, String contentType, T result) {
+    this.httpResponse = httpResponse;
+    this.contentType = contentType;
+    this.result = result;
+  }
+
+  /** HTTP status code, always in the 2xx family. */
+  public int getStatusCode() {
+    return httpResponse.getStatusCode();
+  }
+
+  /**
+   * Content type supplied by the server.
+   *
+   * This helper simplifies the common {@code getHeader("Content-Type")} case.
+   */
+  public String getContentType() {
+    return contentType;
+  }
+
+  /** Lookup an arbitrary reply header. */
+  public String getHeader(String header) {
+    if ("Content-Type".equals(header)) {
+      return contentType;
+    }
+    return httpResponse.getHeader(header);
+  }
+
+  public T getResult() {
+    return result;
+  }
+}
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 b677276..e87853b 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
@@ -104,21 +104,21 @@
     }
   }
 
-  private static class HttpCallback<T extends JavaScriptObject>
+  private static class HttpImpl<T extends JavaScriptObject>
       implements RequestCallback {
     private final boolean background;
-    private final AsyncCallback<T> cb;
+    private final HttpCallback<T> cb;
 
-    HttpCallback(boolean bg, AsyncCallback<T> cb) {
+    HttpImpl(boolean bg, HttpCallback<T> cb) {
       this.background = bg;
       this.cb = cb;
     }
 
     @Override
-    public void onResponseReceived(Request req, Response res) {
+    public void onResponseReceived(Request req, final Response res) {
       int status = res.getStatusCode();
       if (status == Response.SC_NO_CONTENT) {
-        cb.onSuccess(null);
+        cb.onSuccess(new HttpResponse<T>(res, null, null));
         if (!background) {
           RpcStatus.INSTANCE.onRpcComplete();
         }
@@ -126,12 +126,12 @@
       } else if (200 <= status && status < 300) {
         long start = System.currentTimeMillis();
         final T data;
-        if (isTextBody(res)) {
-          data = NativeString.wrap(res.getText()).cast();
-        } else if (isJsonBody(res)) {
+        final String type;
+        if (isJsonBody(res)) {
           try {
             // javac generics bug
-            data = RestApi.<T>cast(parseJson(res));
+            data = RestApi.<T> cast(parseJson(res));
+            type = JSON_TYPE;
           } catch (JSONException e) {
             if (!background) {
               RpcStatus.INSTANCE.onRpcComplete();
@@ -140,6 +140,12 @@
                 "Invalid JSON: " + e.getMessage()));
             return;
           }
+        } else if (isEncodedBase64(res)) {
+          data = NativeString.wrap(decodeBase64(res.getText())).cast();
+          type = simpleType(res.getHeader("X-FYI-Content-Type"));
+        } else if (isTextBody(res)) {
+          data = NativeString.wrap(res.getText()).cast();
+          type = TEXT_TYPE;
         } else {
           if (!background) {
             RpcStatus.INSTANCE.onRpcComplete();
@@ -154,7 +160,7 @@
           @Override
           public void execute() {
             try {
-              cb.onSuccess(data);
+              cb.onSuccess(new HttpResponse<>(res, type, data));
             } finally {
               if (!background) {
                 RpcStatus.INSTANCE.onRpcComplete();
@@ -318,21 +324,24 @@
   }
 
   public <T extends JavaScriptObject> void get(AsyncCallback<T> cb) {
+    get(wrap(cb));
+  }
+
+  public <T extends JavaScriptObject> void get(HttpCallback<T> cb) {
     send(GET, cb);
   }
 
   public <T extends JavaScriptObject> void delete(AsyncCallback<T> cb) {
+    delete(wrap(cb));
+  }
+
+  public <T extends JavaScriptObject> void delete(HttpCallback<T> cb) {
     send(DELETE, cb);
   }
 
-  public <T extends JavaScriptObject> void delete(JavaScriptObject content,
-      AsyncCallback<T> cb) {
-    sendJSON(DELETE, content, cb);
-  }
-
-  private <T extends JavaScriptObject> void send(
-      Method method, AsyncCallback<T> cb) {
-    HttpCallback<T> httpCallback = new HttpCallback<>(background, cb);
+  private <T extends JavaScriptObject> void send(Method method,
+      HttpCallback<T> cb) {
+    HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
     try {
       if (!background) {
         RpcStatus.INSTANCE.onRpcStart();
@@ -346,33 +355,59 @@
   public <T extends JavaScriptObject> void post(
       JavaScriptObject content,
       AsyncCallback<T> cb) {
+    post(content, wrap(cb));
+  }
+
+  public <T extends JavaScriptObject> void post(
+      JavaScriptObject content,
+      HttpCallback<T> cb) {
     sendJSON(POST, content, cb);
   }
 
   public <T extends JavaScriptObject> void post(String content,
       AsyncCallback<T> cb) {
+    post(content, wrap(cb));
+  }
+
+  public <T extends JavaScriptObject> void post(String content,
+      HttpCallback<T> cb) {
     sendRaw(POST, content, cb);
   }
 
   public <T extends JavaScriptObject> void put(AsyncCallback<T> cb) {
+    put(wrap(cb));
+  }
+
+  public <T extends JavaScriptObject> void put(HttpCallback<T> cb) {
     send(PUT, cb);
   }
 
   public <T extends JavaScriptObject> void put(String content,
       AsyncCallback<T> cb) {
+    put(content, wrap(cb));
+  }
+
+  public <T extends JavaScriptObject> void put(String content,
+      HttpCallback<T> cb) {
     sendRaw(PUT, content, cb);
   }
 
   public <T extends JavaScriptObject> void put(
       JavaScriptObject content,
       AsyncCallback<T> cb) {
+    put(content, wrap(cb));
+  }
+
+  public <T extends JavaScriptObject> void put(
+      JavaScriptObject content,
+      HttpCallback<T> cb) {
     sendJSON(PUT, content, cb);
   }
 
   private <T extends JavaScriptObject> void sendJSON(
       Method method, JavaScriptObject content,
-      AsyncCallback<T> cb) {
-    HttpCallback<T> httpCallback = new HttpCallback<>(background, cb);
+      HttpCallback<T> cb) {
+    HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
     try {
       if (!background) {
         RpcStatus.INSTANCE.onRpcStart();
@@ -385,11 +420,15 @@
     }
   }
 
-  private static native String str(JavaScriptObject jso) /*-{ return JSON.stringify(jso); }-*/;
+  private static native String str(JavaScriptObject jso)
+  /*-{ return JSON.stringify(jso) }-*/;
+
+  private static native String decodeBase64(String a)
+  /*-{ return $wnd.atob(a) }-*/;
 
   private <T extends JavaScriptObject> void sendRaw(Method method, String body,
-      AsyncCallback<T> cb) {
-    HttpCallback<T> httpCallback = new HttpCallback<>(background, cb);
+      HttpCallback<T> cb) {
+    HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
     try {
       if (!background) {
         RpcStatus.INSTANCE.onRpcStart();
@@ -422,16 +461,22 @@
     return isContentType(res, TEXT_TYPE);
   }
 
+  private static boolean isEncodedBase64(Response res) {
+    return "base64".equals(res.getHeader("X-FYI-Content-Encoding"))
+        && isTextBody(res);
+  }
+
   private static boolean isContentType(Response res, String want) {
     String type = res.getHeader("Content-Type");
-    if (type == null) {
-      return false;
-    }
+    return type != null && want.equals(simpleType(type));
+  }
+
+  private static String simpleType(String type) {
     int semi = type.indexOf(';');
     if (semi >= 0) {
-      type = type.substring(0, semi).trim();
+      return type.substring(0, semi).trim();
     }
-    return want.equals(type);
+    return type;
   }
 
   private static JSONValue parseJson(Response res)
@@ -464,4 +509,19 @@
       throw new JSONException("unsupported JSON type");
     }
   }
+
+  private static <T extends JavaScriptObject> HttpCallback<T> wrap(
+      final AsyncCallback<T> cb) {
+    return new HttpCallback<T>() {
+      @Override
+      public void onSuccess(HttpResponse<T> r) {
+        cb.onSuccess(r.getResult());
+      }
+
+      @Override
+      public void onFailure(Throwable e) {
+        cb.onFailure(e);
+      }
+    };
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java
index 4f54ba5..26c0ce6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/SuggestAfterTypingNCharsOracle.java
@@ -17,6 +17,9 @@
 import com.google.gerrit.client.Gerrit;
 import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
 
+import java.util.Collections;
+import java.util.List;
+
 /**
  * Suggest oracle that only provides suggestions if the user has typed at least
  * as many characters as configured by 'suggest.from'. If 'suggest.from' is set
@@ -25,10 +28,12 @@
 public abstract class SuggestAfterTypingNCharsOracle extends HighlightSuggestOracle {
 
   @Override
-  protected void onRequestSuggestions(final Request request, final Callback done) {
-    final int suggestFrom = Gerrit.getConfig().getSuggestFrom();
-    if (suggestFrom == 0 || request.getQuery().length() >= suggestFrom) {
-      _onRequestSuggestions(request, done);
+  protected void onRequestSuggestions(Request req, Callback cb) {
+    if (req.getQuery().length() >= Gerrit.getConfig().getSuggestFrom()) {
+      _onRequestSuggestions(req, cb);
+    } else {
+      List<Suggestion> none = Collections.emptyList();
+      cb.onSuggestionsReady(req, new Response(none));
     }
   }
 
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 c731476..e607b43 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -326,7 +326,7 @@
   }-*/;
 
   public final native Element scrollbarV() /*-{
-    return this.display.scrollbarV
+    return this.display.scrollbars.vert.node;
   }-*/;
 
   public static final native KeyMap cloneKeyMap(String name) /*-{
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 f2822b7..379cb3c 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
@@ -15,7 +15,6 @@
 package net.codemirror.lib;
 
 import com.google.gerrit.client.rpc.CallbackGroup;
-import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gwt.core.client.Callback;
 import com.google.gwt.core.client.ScriptInjector;
 import com.google.gwt.dom.client.ScriptElement;
@@ -39,11 +38,15 @@
 
     CallbackGroup group = new CallbackGroup();
     injectCss(Lib.I.css(), group.<Void> addEmpty());
-    injectScript(Lib.I.js().getSafeUri(), group.add(new GerritCallback<Void>() {
+    injectScript(Lib.I.js().getSafeUri(), group.add(new AsyncCallback<Void>() {
       @Override
       public void onSuccess(Void result) {
         Vim.initKeyMap();
       }
+
+      @Override
+      public void onFailure(Throwable caught) {
+      }
     }));
     group.addListener(cb);
     group.done();
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 c4ca15d..b727bc6 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
@@ -146,8 +146,6 @@
           }
         })));
 
-    config.setNewFeatures(cfg.getBoolean("gerrit", "enableNewFeatures", true));
-
     config.setReportBugUrl(cfg.getString("gerrit", null, "reportBugUrl"));
     config.setReportBugText(cfg.getString("gerrit", null, "reportBugText"));
 
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
index 02ad0c4..81ffe1c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
@@ -18,7 +18,6 @@
 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;
@@ -47,6 +46,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.NavigableSet;
+import java.util.Objects;
 import java.util.SortedSet;
 import java.util.TreeMap;
 
@@ -156,7 +156,7 @@
     LabelType type = project.getLabelTypes().byLabel(psa.getLabelId());
     if (type == null) {
       return false;
-    } else if (Objects.equal(n, previous(allPsIds, psId.get())) && (
+    } else if (Objects.equals(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
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 3e169f3..92b78d1 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
@@ -18,7 +18,6 @@
 
 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;
@@ -57,6 +56,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -91,7 +91,7 @@
     return Iterables.filter(psas, new Predicate<PatchSetApproval>() {
       @Override
       public boolean apply(PatchSetApproval input) {
-        return Objects.equal(input.getAccountId(), accountId);
+        return Objects.equals(input.getAccountId(), accountId);
       }
     });
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 566b298..ab788c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -187,7 +187,7 @@
       return ImmutableSet.copyOf((Iterable<String>) listFiles
           .get().setReviewed(true)
           .apply(revision).value());
-    } catch (OrmException e) {
+    } catch (OrmException | IOException e) {
       throw new RestApiException("Cannot list reviewed files", e);
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java
index 09ab56b..e194eb7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/AuthRequest.java
@@ -14,9 +14,10 @@
 
 package com.google.gerrit.server.auth;
 
-import com.google.common.base.Objects;
 import com.google.gerrit.common.Nullable;
 
+import java.util.Objects;
+
 /**
  * Defines an abstract request for user authentication to Gerrit.
  */
@@ -50,7 +51,7 @@
   }
 
   public void checkPassword(String pwd) throws AuthException {
-    if (!Objects.equal(getPassword(), pwd)) {
+    if (!Objects.equals(getPassword(), pwd)) {
       throw new InvalidCredentialsException();
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
index a2e4604..4f11044 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
@@ -52,6 +52,7 @@
 import com.google.inject.Singleton;
 import com.google.inject.assistedinject.Assisted;
 
+import org.eclipse.jgit.lib.ObjectId;
 import org.kohsuke.args4j.Option;
 
 import java.io.IOException;
@@ -433,8 +434,8 @@
         throws ResourceNotFoundException, IOException {
       try {
         return Response.ok(fileContentUtil.getContent(
-              rsrc.getChangeEdit().getChange().getProject(),
-              rsrc.getChangeEdit().getRevision().get(),
+              rsrc.getControl().getProjectControl().getProjectState(),
+              ObjectId.fromString(rsrc.getChangeEdit().getRevision().get()),
               rsrc.getPath()));
       } catch (ResourceNotFoundException rnfe) {
         return Response.none();
@@ -502,29 +503,12 @@
         IOException, ResourceNotFoundException {
       Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
       if (edit.isPresent()) {
-        return BinaryResult.create(
-            edit.get().getEditCommit().getFullMessage()).base64();
+        String msg = edit.get().getEditCommit().getFullMessage();
+        return BinaryResult.create(msg)
+            .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
+            .base64();
       }
       throw new ResourceNotFoundException();
     }
   }
-
-  @Singleton
-  public static class GetType implements RestReadView<ChangeEditResource> {
-    private final FileContentUtil fileContentUtil;
-
-    @Inject
-    GetType(FileContentUtil fileContentUtil) {
-      this.fileContentUtil = fileContentUtil;
-    }
-
-    @Override
-    public String apply(ChangeEditResource rsrc)
-        throws ResourceNotFoundException, IOException {
-      return fileContentUtil.getContentType(
-          rsrc.getChangeEdit().getChange().getProject(),
-          rsrc.getChangeEdit().getRevision().get(),
-          rsrc.getPath());
-    }
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index 8d98a4c..a5e7d12 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -19,7 +19,6 @@
 import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.cache.Weigher;
@@ -53,6 +52,7 @@
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.util.Collection;
+import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 
 public class ChangeKindCacheImpl implements ChangeKindCache {
@@ -156,16 +156,16 @@
     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 Objects.equals(prior, k.prior)
+            && Objects.equals(next, k.next)
+            && Objects.equals(strategyName, k.strategyName);
       }
       return false;
     }
 
     @Override
     public int hashCode() {
-      return Objects.hashCode(prior, next, strategyName);
+      return Objects.hash(prior, next, strategyName);
     }
 
     private void writeObject(ObjectOutputStream out) throws IOException {
@@ -185,7 +185,7 @@
   private static class Loader extends CacheLoader<Key, ChangeKind> {
     @Override
     public ChangeKind load(Key key) throws IOException {
-      if (Objects.equal(key.prior, key.next)) {
+      if (Objects.equals(key.prior, key.next)) {
         return ChangeKind.NO_CODE_CHANGE;
       }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
index 5d07402..bc48917 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -16,14 +16,19 @@
 
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
+import com.google.gerrit.common.data.PatchScript.FileMode;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.server.FileTypeRegistry;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
@@ -36,6 +41,11 @@
 
 @Singleton
 public class FileContentUtil {
+  public static final String TEXT_X_GERRIT_COMMIT_MESSAGE = "text/x-gerrit-commit-message";
+  private static final String X_GIT_SYMLINK = "x-git/symlink";
+  private static final String X_GIT_GITLINK = "x-git/gitlink";
+  private static final int MAX_SIZE = 5 << 20;
+
   private final GitRepositoryManager repoManager;
   private final FileTypeRegistry registry;
 
@@ -46,28 +56,50 @@
     this.registry = ftr;
   }
 
-  public BinaryResult getContent(Project.NameKey project, String revstr,
+  public BinaryResult getContent(ProjectState project, ObjectId revstr,
       String path) throws ResourceNotFoundException, IOException {
-    Repository repo = repoManager.openRepository(project);
+    Repository repo = openRepository(project);
     try {
       RevWalk rw = new RevWalk(repo);
       try {
-        RevCommit commit = rw.parseCommit(repo.resolve(revstr));
-        TreeWalk tw =
-            TreeWalk.forPath(rw.getObjectReader(), path,
-                commit.getTree().getId());
+        RevCommit commit = rw.parseCommit(revstr);
+        ObjectReader reader = rw.getObjectReader();
+        TreeWalk tw = TreeWalk.forPath(reader, path, commit.getTree());
         if (tw == null) {
           throw new ResourceNotFoundException();
         }
-        final ObjectLoader object = repo.open(tw.getObjectId(0));
-        @SuppressWarnings("resource")
-        BinaryResult result = new BinaryResult() {
-          @Override
-          public void writeTo(OutputStream os) throws IOException {
-            object.copyTo(os);
-          }
-        };
-        return result.setContentLength(object.getSize()).base64();
+
+        org.eclipse.jgit.lib.FileMode mode = tw.getFileMode(0);
+        ObjectId id = tw.getObjectId(0);
+        if (mode == org.eclipse.jgit.lib.FileMode.GITLINK) {
+          return BinaryResult.create(id.name())
+              .setContentType(X_GIT_GITLINK)
+              .base64();
+        }
+
+        final ObjectLoader obj = repo.open(id, OBJ_BLOB);
+        byte[] raw;
+        try {
+          raw = obj.getCachedBytes(MAX_SIZE);
+        } catch (LargeObjectException e) {
+          raw = null;
+        }
+
+        BinaryResult result;
+        if (raw != null) {
+          result = BinaryResult.create(raw);
+        } else {
+          result = asBinaryResult(obj);
+        }
+
+        String type;
+        if (mode == org.eclipse.jgit.lib.FileMode.SYMLINK) {
+          type = X_GIT_SYMLINK;
+        } else {
+          type = registry.getMimeType(path, raw).toString();
+          type = resolveContentType(project, path, FileMode.FILE, type);
+        }
+        return result.setContentType(type).base64();
       } finally {
         rw.release();
       }
@@ -76,31 +108,44 @@
     }
   }
 
-  public String getContentType(Project.NameKey project, String revstr,
-      String path) throws ResourceNotFoundException, IOException {
-    Repository repo = repoManager.openRepository(project);
-    try {
-      RevWalk rw = new RevWalk(repo);
-      ObjectReader reader = repo.newObjectReader();
-      try {
-        RevCommit commit = rw.parseCommit(repo.resolve(revstr));
-        TreeWalk tw =
-            TreeWalk.forPath(rw.getObjectReader(), path,
-                commit.getTree().getId());
-        if (tw == null) {
-          throw new ResourceNotFoundException();
-        }
-        ObjectLoader blobLoader = reader.open(tw.getObjectId(0), OBJ_BLOB);
-        byte[] raw = blobLoader.isLarge()
-            ? null
-            : blobLoader.getCachedBytes();
-        return registry.getMimeType(path, raw).toString();
-      } finally {
-        reader.release();
-        rw.release();
+  private static BinaryResult asBinaryResult(final ObjectLoader obj) {
+    @SuppressWarnings("resource")
+    BinaryResult result = new BinaryResult() {
+      @Override
+      public void writeTo(OutputStream os) throws IOException {
+        obj.copyTo(os);
       }
-    } finally {
-      repo.close();
+    }.setContentLength(obj.getSize());
+    return result;
+  }
+
+  public static String resolveContentType(ProjectState project, String path,
+      FileMode fileMode, String mimeType) {
+    switch (fileMode) {
+      case FILE:
+        if (Patch.COMMIT_MSG.equals(path)) {
+          return TEXT_X_GERRIT_COMMIT_MESSAGE;
+        }
+        if (project != null) {
+          for (ProjectState p : project.tree()) {
+            String t = p.getConfig().getMimeTypes().getMimeType(path);
+            if (t != null) {
+              return t;
+            }
+          }
+        }
+        return mimeType;
+      case GITLINK:
+        return X_GIT_GITLINK;
+      case SYMLINK:
+        return X_GIT_SYMLINK;
+      default:
+        throw new IllegalStateException("file mode: " + fileMode);
     }
   }
+
+  private Repository openRepository(ProjectState project)
+      throws RepositoryNotFoundException, IOException {
+    return repoManager.openRepository(project.getProject().getNameKey());
+  }
 }
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 3035ce1..4a3082c 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
@@ -43,8 +43,11 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@@ -53,6 +56,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -96,6 +100,9 @@
     @Option(name = "--reviewed")
     boolean reviewed;
 
+    @Option(name = "-q")
+    String query;
+
     private final Provider<ReviewDb> db;
     private final Provider<CurrentUser> self;
     private final FileInfoJson fileInfoJson;
@@ -125,11 +132,13 @@
 
     @Override
     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) {
+        BadRequestException, ResourceNotFoundException, OrmException,
+        RepositoryNotFoundException, IOException {
+      checkOptions();
+      if (reviewed) {
         return Response.ok(reviewed(resource));
+      } else if (query != null) {
+        return Response.ok(query(resource));
       }
 
       PatchSet basePatchSet = null;
@@ -152,6 +161,51 @@
       }
     }
 
+    private void checkOptions() throws BadRequestException {
+      int supplied = 0;
+      if (base != null) {
+        supplied++;
+      }
+      if (reviewed) {
+        supplied++;
+      }
+      if (query != null) {
+        supplied++;
+      }
+      if (supplied > 1) {
+        throw new BadRequestException("cannot combine base, reviewed, query");
+      }
+    }
+
+    private List<String> query(RevisionResource resource)
+        throws RepositoryNotFoundException, IOException {
+      Repository git =
+          gitManager.openRepository(resource.getChange().getProject());
+      try {
+        TreeWalk tw = new TreeWalk(git);
+        try {
+          RevCommit c = new RevWalk(tw.getObjectReader())
+            .parseCommit(ObjectId.fromString(
+                resource.getPatchSet().getRevision().get()));
+
+          tw.addTree(c.getTree());
+          tw.setRecursive(true);
+          List<String> paths = new ArrayList<>();
+          while (tw.next() && paths.size() < 20) {
+            String s = tw.getPathString();
+            if (s.contains(query)) {
+              paths.add(s);
+            }
+          }
+          return paths;
+        } finally {
+          tw.release();
+        }
+      } finally {
+        git.close();
+      }
+    }
+
     private List<String> reviewed(RevisionResource resource)
         throws AuthException, OrmException {
       CurrentUser user = self.get();
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 67c68fc..810a3a6 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
@@ -24,6 +24,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.lib.ObjectId;
+
 import java.io.IOException;
 
 @Singleton
@@ -44,12 +46,14 @@
       OrmException {
     String path = rsrc.getPatchKey().get();
     if (Patch.COMMIT_MSG.equals(path)) {
-      return BinaryResult.create(
-          changeUtil.getMessage(rsrc.getRevision().getChange())).base64();
+      String msg = changeUtil.getMessage(rsrc.getRevision().getChange());
+      return BinaryResult.create(msg)
+          .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
+          .base64();
     }
     return fileContentUtil.getContent(
-        rsrc.getRevision().getControl().getProject().getNameKey(),
-        rsrc.getRevision().getPatchSet().getRevision().get(),
+        rsrc.getRevision().getControl().getProjectControl().getProjectState(),
+        ObjectId.fromString(rsrc.getRevision().getPatchSet().getRevision().get()),
         path);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContentType.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContentType.java
deleted file mode 100644
index 2fa3126..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContentType.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2014 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.change;
-
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import java.io.IOException;
-
-@Singleton
-public class GetContentType implements RestReadView<FileResource> {
-  private final FileContentUtil fileContentUtil;
-
-  @Inject
-  GetContentType(FileContentUtil fileContentUtil) {
-    this.fileContentUtil = fileContentUtil;
-  }
-
-  @Override
-  public String apply(FileResource rsrc)
-      throws ResourceNotFoundException, IOException {
-    String path = rsrc.getPatchKey().get();
-    if (Patch.COMMIT_MSG.equals(path)) {
-      return "text/plain";
-    }
-    return fileContentUtil.getContentType(
-        rsrc.getRevision().getControl().getProject().getNameKey(),
-        rsrc.getRevision().getPatchSet().getRevision().get(),
-        path);
-  }
-}
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 04b1386..7c7bcdae 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,7 +24,6 @@
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.PatchScript;
 import com.google.gerrit.common.data.PatchScript.DisplayMethod;
-import com.google.gerrit.common.data.PatchScript.FileMode;
 import com.google.gerrit.extensions.common.ChangeType;
 import com.google.gerrit.extensions.common.DiffInfo;
 import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
@@ -194,7 +193,8 @@
           result.metaA = new FileMeta();
           result.metaA.name = MoreObjects.firstNonNull(ps.getOldName(),
               ps.getNewName());
-          setContentType(result.metaA, state, ps.getFileModeA(), ps.getMimeTypeA());
+          result.metaA.contentType = FileContentUtil.resolveContentType(
+              state, result.metaA.name, ps.getFileModeA(), ps.getMimeTypeA());
           result.metaA.lines = ps.getA().size();
           result.metaA.webLinks =
               getFileWebLinks(state.getProject(), revA, result.metaA.name);
@@ -203,7 +203,8 @@
         if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
           result.metaB = new FileMeta();
           result.metaB.name = ps.getNewName();
-          setContentType(result.metaB, state, ps.getFileModeB(), ps.getMimeTypeB());
+          result.metaB.contentType = FileContentUtil.resolveContentType(
+              state, result.metaB.name, ps.getFileModeB(), ps.getMimeTypeB());
           result.metaB.lines = ps.getB().size();
           result.metaB.webLinks =
               getFileWebLinks(state.getProject(), revB, result.metaB.name);
@@ -250,34 +251,6 @@
     return links.isEmpty() ? null : links.toList();
   }
 
-  private void setContentType(FileMeta meta, ProjectState project,
-      FileMode fileMode, String mimeType) {
-    switch (fileMode) {
-      case FILE:
-        if (Patch.COMMIT_MSG.equals(meta.name)) {
-          mimeType = "text/x-gerrit-commit-message";
-        } else if (project != null) {
-          for (ProjectState p : project.tree()) {
-            String t = p.getConfig().getMimeTypes().getMimeType(meta.name);
-            if (t != null) {
-              mimeType = t;
-              break;
-            }
-          }
-        }
-        meta.contentType = mimeType;
-        break;
-      case GITLINK:
-        meta.contentType = "x-git/gitlink";
-        break;
-      case SYMLINK:
-        meta.contentType = "x-git/symlink";
-        break;
-      default:
-        throw new IllegalStateException("file mode: " + fileMode);
-    }
-  }
-
   private static class Content {
     final List<ContentEntry> lines;
     final SparseFileContent fileA;
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 f89c981..4cf7692 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
@@ -103,7 +103,6 @@
     put(FILE_KIND, "reviewed").to(PutReviewed.class);
     delete(FILE_KIND, "reviewed").to(DeleteReviewed.class);
     get(FILE_KIND, "content").to(GetContent.class);
-    get(FILE_KIND, "type").to(GetContentType.class);
     get(FILE_KIND, "diff").to(GetDiff.class);
 
     child(CHANGE_KIND, "edit").to(ChangeEdits.class);
@@ -115,7 +114,6 @@
     put(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Put.class);
     delete(CHANGE_EDIT_KIND).to(ChangeEdits.DeleteContent.class);
     get(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Get.class);
-    get(CHANGE_EDIT_KIND, "type").to(ChangeEdits.GetType.class);
 
     install(new FactoryModule() {
       @Override
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 18b08b5..a01f049 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
@@ -37,6 +37,7 @@
 import com.google.gerrit.extensions.common.Comment.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.RestModifyView;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.extensions.restapi.Url;
@@ -130,8 +131,11 @@
 
   @Override
   public Output apply(RevisionResource revision, ReviewInput input)
-      throws AuthException, BadRequestException, UnprocessableEntityException,
-      OrmException, IOException {
+      throws AuthException, BadRequestException, ResourceConflictException,
+      UnprocessableEntityException, OrmException, IOException {
+    if (revision.getEdit().isPresent()) {
+      throw new ResourceConflictException("cannot post review on edit");
+    }
     if (input.onBehalfOf != null) {
       revision = onBehalfOf(revision, input);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
index f32b41c..dc5e445 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -32,6 +33,7 @@
 import com.google.gerrit.server.edit.ChangeEdit;
 import com.google.gerrit.server.edit.ChangeEditUtil;
 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.Singleton;
@@ -76,11 +78,10 @@
       }
       throw new ResourceNotFoundException(id);
     }
+
     List<RevisionResource> match = Lists.newArrayListWithExpectedSize(2);
     for (RevisionResource rsrc : find(change, id.get())) {
-      Change.Id changeId = rsrc.getChange().getId();
-      if (changeId.equals(change.getChange().getId())
-          && visible(change, rsrc.getPatchSet())) {
+      if (visible(change, rsrc.getPatchSet())) {
         match.add(rsrc);
       }
     }
@@ -103,19 +104,11 @@
 
   private List<RevisionResource> find(ChangeResource change, String id)
       throws OrmException {
-    ReviewDb db = dbProvider.get();
-
     if (id.equals("0")) {
       return loadEdit(change, null);
     } else if (id.length() < 6 && id.matches("^[1-9][0-9]{0,4}$")) {
       // Legacy patch set number syntax.
-      PatchSet ps = dbProvider.get().patchSets().get(new PatchSet.Id(
-          change.getChange().getId(),
-          Integer.parseInt(id)));
-      if (ps != null) {
-        return toResources(change, ps);
-      }
-      return Collections.emptyList();
+      return byLegacyPatchSetId(change, id);
     } else if (id.length() < 4 || id.length() > RevId.LEN) {
       // Require a minimum of 4 digits.
       // Impossibly long identifier will never match.
@@ -127,20 +120,15 @@
       // for all patch sets in the change.
       RevId revid = new RevId(id);
       if (revid.isComplete()) {
-        List<RevisionResource> list =
-            toResources(change, db.patchSets().byRevision(revid));
-        if (list.isEmpty()) {
-          return loadEdit(change, revid);
-        }
-        return list;
-      } else {
-        return toResources(
-            change, db.patchSets().byRevisionRange(revid, revid.max()));
+        List<RevisionResource> m = toResources(change, findExactMatch(revid));
+        return m.isEmpty() ? loadEdit(change, revid)  : m;
       }
+      return toResources(change, findByPrefix(revid));
     } else {
       // Chance of collision rises; look at all patch sets on the change.
       List<RevisionResource> out = Lists.newArrayList();
-      for (PatchSet ps : db.patchSets().byChange(change.getChange().getId())) {
+      for (PatchSet ps : dbProvider.get().patchSets()
+          .byChange(change.getChange().getId())) {
         if (ps.getRevision() != null && ps.getRevision().get().startsWith(id)) {
           out.add(new RevisionResource(change, ps));
         }
@@ -149,6 +137,25 @@
     }
   }
 
+  private List<RevisionResource> byLegacyPatchSetId(ChangeResource change,
+      String id) throws OrmException {
+    PatchSet ps = dbProvider.get().patchSets().get(new PatchSet.Id(
+        change.getChange().getId(),
+        Integer.parseInt(id)));
+    if (ps != null) {
+      return Collections.singletonList(new RevisionResource(change, ps));
+    }
+    return Collections.emptyList();
+  }
+
+  private ResultSet<PatchSet> findExactMatch(RevId revid) throws OrmException {
+    return dbProvider.get().patchSets().byRevision(revid);
+  }
+
+  private ResultSet<PatchSet> findByPrefix(RevId revid) throws OrmException {
+    return dbProvider.get().patchSets().byRevisionRange(revid, revid.max());
+  }
+
   private List<RevisionResource> loadEdit(ChangeResource change, RevId revid)
       throws OrmException {
     try {
@@ -162,25 +169,26 @@
               new RevisionResource(change, ps, edit));
         }
       }
+      return Collections.emptyList();
     } catch (AuthException | IOException e) {
       throw new OrmException(e);
     }
-    return Collections.emptyList();
   }
 
   private static List<RevisionResource> toResources(final ChangeResource change,
       Iterable<PatchSet> patchSets) {
+    final Change.Id changeId = change.getChange().getId();
     return FluentIterable.from(patchSets)
-        .transform(new Function<PatchSet, RevisionResource>() {
+        .filter(new Predicate<PatchSet>() {
+          @Override
+          public boolean apply(PatchSet in) {
+            return changeId.equals(in.getId().getParentKey());
+          }
+        }).transform(new Function<PatchSet, RevisionResource>() {
           @Override
           public RevisionResource apply(PatchSet in) {
             return new RevisionResource(change, in);
           }
         }).toList();
   }
-
-  private static List<RevisionResource> toResources(ChangeResource change,
-      PatchSet ps) {
-    return Collections.singletonList(new RevisionResource(change, ps));
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
index 008a217..738d309 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
@@ -15,16 +15,12 @@
 package com.google.gerrit.server.edit;
 
 import com.google.common.collect.Maps;
-import com.google.gerrit.extensions.common.ActionInfo;
 import com.google.gerrit.extensions.common.CommitInfo;
 import com.google.gerrit.extensions.common.EditInfo;
 import com.google.gerrit.extensions.common.FetchInfo;
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
-import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.server.CommonConverters;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.change.ChangeJson;
@@ -56,7 +52,6 @@
     EditInfo out = new EditInfo();
     out.commit = fillCommit(edit.getEditCommit());
     out.baseRevision = edit.getBasePatchSet().getRevision().get();
-    out.actions = fillActions(edit);
     if (downloadCommands) {
       out.fetch = fillFetchMap(edit);
     }
@@ -82,35 +77,6 @@
     return commit;
   }
 
-  private static Map<String, ActionInfo> fillActions(ChangeEdit edit) {
-    Map<String, ActionInfo> actions = Maps.newTreeMap();
-
-    UiAction.Description descr = new UiAction.Description();
-    PrivateInternals_UiActionDescription.setId(descr, "/");
-    PrivateInternals_UiActionDescription.setMethod(descr, "DELETE");
-    descr.setTitle("Delete edit");
-    actions.put(descr.getId(), new ActionInfo(descr));
-
-    // Only expose publish action when the edit is on top of current ps
-    PatchSet.Id current = edit.getChange().currentPatchSetId();
-    PatchSet basePs = edit.getBasePatchSet();
-    if (basePs.getId().equals(current)) {
-      descr = new UiAction.Description();
-      PrivateInternals_UiActionDescription.setId(descr, "publish");
-      PrivateInternals_UiActionDescription.setMethod(descr, "POST");
-      descr.setTitle("Publish edit");
-      actions.put(descr.getId(), new ActionInfo(descr));
-    } else {
-      descr = new UiAction.Description();
-      PrivateInternals_UiActionDescription.setId(descr, "rebase");
-      PrivateInternals_UiActionDescription.setMethod(descr, "POST");
-      descr.setTitle("Rebase edit");
-      actions.put(descr.getId(), new ActionInfo(descr));
-    }
-
-    return actions;
-  }
-
   private Map<String, FetchInfo> fillFetchMap(ChangeEdit edit) {
     Map<String, FetchInfo> r = Maps.newLinkedHashMap();
     for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
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 71a68b4..9127acd 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
@@ -18,7 +18,6 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -38,6 +37,7 @@
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Normalizes votes on labels according to project config and permissions.
@@ -85,16 +85,16 @@
     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 Objects.equals(unchanged, r.unchanged)
+            && Objects.equals(updated, r.updated)
+            && Objects.equals(deleted, r.deleted);
       }
       return false;
     }
 
     @Override
     public int hashCode() {
-      return Objects.hashCode(unchanged, updated, deleted);
+      return Objects.hash(unchanged, updated, deleted);
     }
 
     @Override
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 112b1a8..b859dc2 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
@@ -20,7 +20,6 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.eclipse.jgit.lib.RefDatabase.ALL;
 
-import com.google.common.base.Objects;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
@@ -97,6 +96,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -1058,8 +1058,8 @@
     try {
       ChangeMessage last = Iterables.getLast(cmUtil.byChange(db, notes));
       if (last != null) {
-        if (Objects.equal(last.getAuthor(), msg.getAuthor())
-            && Objects.equal(last.getMessage(), msg.getMessage())) {
+        if (Objects.equals(last.getAuthor(), msg.getAuthor())
+            && Objects.equals(last.getMessage(), msg.getMessage())) {
           long lastMs = last.getWrittenOn().getTime();
           long msgMs = msg.getWrittenOn().getTime();
           long sinceMs = msgMs - lastMs;
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 599a305..09846d6 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
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.git;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -47,6 +46,7 @@
 import org.eclipse.jgit.util.RawParseUtils;
 
 import java.io.IOException;
+import java.util.Objects;
 
 /**
  * Support for metadata stored within a version controlled branch.
@@ -275,7 +275,7 @@
 
       @Override
       public RevCommit createRef(String refName) throws IOException {
-        if (Objects.equal(src, revision)) {
+        if (Objects.equals(src, revision)) {
           return revision;
         }
         return updateRef(ObjectId.zeroId(), src, refName);
@@ -306,7 +306,7 @@
 
       @Override
       public RevCommit commitAt(ObjectId expected) throws IOException {
-        if (Objects.equal(src, expected)) {
+        if (Objects.equals(src, expected)) {
           return revision;
         }
         return updateRef(MoreObjects.firstNonNull(expected, ObjectId.zeroId()),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java
index 2eacd09..fb39462 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java
@@ -19,7 +19,6 @@
 import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ComparisonChain;
 import com.google.common.collect.Lists;
@@ -52,6 +51,7 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 
@@ -232,13 +232,13 @@
     }
 
     protected void checkUpdate(AbstractChangeUpdate update) {
-      checkState(Objects.equal(update.getPatchSetId(), psId),
+      checkState(Objects.equals(update.getPatchSetId(), psId),
           "cannot apply event for %s to update for %s",
           update.getPatchSetId(), psId);
       checkState(when.getTime() - update.getWhen().getTime() <= TS_WINDOW_MS,
           "event at %s outside update window starting at %s",
           when, update.getWhen());
-      checkState(Objects.equal(update.getUser().getAccountId(), who),
+      checkState(Objects.equals(update.getUser().getAccountId(), who),
           "cannot apply event by %s to update by %s",
           who, update.getUser().getAccountId());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
index 6681d94..98531ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
@@ -41,4 +41,8 @@
   public String getRef() {
     return branchInfo.ref;
   }
+
+  public String getRevision() {
+    return branchInfo.revision;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java
index 2543818..36186a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CommitResource.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.inject.TypeLiteral;
 
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -33,8 +32,8 @@
     this.commit = commit;
   }
 
-  public Project.NameKey getProject() {
-    return project.getNameKey();
+  public ProjectControl getProject() {
+    return project.getControl();
   }
 
   public RevCommit getCommit() {
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
index c7c6675..47942be 100644
--- 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
@@ -16,28 +16,29 @@
 
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.inject.TypeLiteral;
 
+import org.eclipse.jgit.lib.ObjectId;
+
 public class FileResource implements RestResource {
   public static final TypeLiteral<RestView<FileResource>> FILE_KIND =
       new TypeLiteral<RestView<FileResource>>() {};
 
-  private final Project.NameKey project;
-  private final String rev;
+  private final ProjectControl project;
+  private final ObjectId rev;
   private final String path;
 
-  public FileResource(Project.NameKey project, String rev, String path) {
+  public FileResource(ProjectControl project, ObjectId rev, String path) {
     this.project = project;
     this.rev = rev;
     this.path = path;
   }
 
-  public Project.NameKey getProject() {
+  public ProjectControl getProject() {
     return project;
   }
 
-  public String getRev() {
+  public ObjectId getRev() {
     return rev;
   }
 
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
index f0544fe..d0460d5 100644
--- 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
@@ -22,6 +22,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.lib.ObjectId;
+
 @Singleton
 public class FilesCollection implements
     ChildCollection<BranchResource, FileResource> {
@@ -39,7 +41,10 @@
 
   @Override
   public FileResource parse(BranchResource parent, IdString id) {
-    return new FileResource(parent.getNameKey(), parent.getRef(), id.get());
+    return new FileResource(
+        parent.getControl(),
+        ObjectId.fromString(parent.getRevision()),
+        id.get());
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
index f383230..8e0aab8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
@@ -40,8 +40,7 @@
   @Override
   public FileResource parse(CommitResource parent, IdString id)
       throws ResourceNotFoundException {
-    return new FileResource(parent.getProject(), parent.getCommit().getName(),
-        id.get());
+    return new FileResource(parent.getProject(), parent.getCommit(), id.get());
   }
 
   @Override
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
index 00f25bc..23e9e30 100644
--- 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
@@ -35,7 +35,9 @@
   @Override
   public BinaryResult apply(FileResource rsrc)
       throws ResourceNotFoundException, IOException {
-    return fileContentUtil.getContent(rsrc.getProject(), rsrc.getRev(),
+    return fileContentUtil.getContent(
+        rsrc.getProject().getProjectState(),
+        rsrc.getRev(),
         rsrc.getPath());
   }
 }
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 0e4ff98..de12e9b 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
@@ -16,7 +16,6 @@
 
 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;
 import com.google.gerrit.extensions.api.projects.ProjectInput.ConfigValue;
@@ -57,6 +56,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 
 @Singleton
 public class PutConfig implements RestModifyView<ProjectResource, Input> {
@@ -178,7 +178,7 @@
         ObjectId baseRev = projectConfig.getRevision();
         ObjectId commitRev = projectConfig.commit(md);
         // Only fire hook if project was actually changed.
-        if (!Objects.equal(baseRev, commitRev)) {
+        if (!Objects.equals(baseRev, commitRev)) {
           IdentifiedUser user = (IdentifiedUser) currentUser.get();
           hooks.doRefUpdatedHook(
             new Branch.NameKey(projectName, RefNames.REFS_CONFIG),
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 db67ce0..536bfa7 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
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.project;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -40,6 +39,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 
 import java.io.IOException;
+import java.util.Objects;
 
 @Singleton
 class PutDescription implements RestModifyView<ProjectResource, Input> {
@@ -97,7 +97,7 @@
         ObjectId baseRev = config.getRevision();
         ObjectId commitRev = config.commit(md);
         // Only fire hook if project was actually changed.
-        if (!Objects.equal(baseRev, commitRev)) {
+        if (!Objects.equals(baseRev, commitRev)) {
           hooks.doRefUpdatedHook(
             new Branch.NameKey(resource.getNameKey(), RefNames.REFS_CONFIG),
             baseRev, commitRev, user.getAccount());
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
index e64ff13..3ad0ea4 100644
--- 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
@@ -14,12 +14,12 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.common.base.Objects;
 import com.google.gerrit.extensions.common.SubmitType;
 
 import org.eclipse.jgit.lib.ObjectId;
 
 import java.io.Serializable;
+import java.util.Objects;
 
 public class ConflictKey implements Serializable {
   private static final long serialVersionUID = 2L;
@@ -73,6 +73,6 @@
 
   @Override
   public int hashCode() {
-    return Objects.hashCode(commit, otherCommit, submitType, contentMerge);
+    return Objects.hash(commit, otherCommit, submitType, contentMerge);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_102.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_102.java
index bcefe78..990cefe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_102.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_102.java
@@ -24,6 +24,8 @@
 
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.Set;
+import java.util.regex.Pattern;
 
 public class Schema_102 extends SchemaVersion {
   @Inject
@@ -37,6 +39,20 @@
     JdbcSchema schema = (JdbcSchema) db;
     SqlDialect dialect = schema.getDialect();
     try (Statement stmt = schema.getConnection().createStatement()) {
+      // Drop left over indexes that were missed to be removed in schema 84.
+      // See "Delete SQL index support" commit for more details:
+      // d4ae3a16d5e1464574bd04f429a63eb9c02b3b43
+      Pattern pattern =
+          Pattern.compile("^changes_(allOpen|allClosed|byBranchClosed)$",
+              Pattern.CASE_INSENSITIVE);
+      Set<String> listIndexes = dialect.listIndexes(
+          schema.getConnection(), "changes");
+      for (String index : listIndexes) {
+        if (pattern.matcher(index).matches()) {
+          stmt.executeUpdate("DROP INDEX " + index);
+        }
+      }
+
       stmt.executeUpdate("DROP INDEX changes_byProjectOpen");
       if (dialect instanceof DialectPostgreSQL) {
         stmt.executeUpdate("CREATE INDEX changes_byProjectOpen"
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java
index 0da7567..20c7010 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/securestore/SecureStoreData.java
@@ -15,11 +15,11 @@
 package com.google.gerrit.server.securestore;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
 
 import java.io.File;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.Objects;
 
 public class SecureStoreData {
   public final File pluginFile;
@@ -73,6 +73,6 @@
 
   @Override
   public int hashCode() {
-    return Objects.hashCode(storeName);
+    return Objects.hash(storeName);
   }
 }
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
index 0192355..b69ab64 100644
--- 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
@@ -16,11 +16,12 @@
 
 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;
 
+import java.util.Objects;
+
 /** A single vote on a label, consisting of a label name and a value. */
 public class LabelVote {
   public static LabelVote parse(String text) {
@@ -99,7 +100,7 @@
   public boolean equals(Object o) {
     if (o instanceof LabelVote) {
       LabelVote l = (LabelVote) o;
-      return Objects.equal(name, l.name)
+      return Objects.equals(name, l.name)
           && value == l.value;
     }
     return false;
diff --git a/lib/antlr/BUCK b/lib/antlr/BUCK
index 732b459..edf153c 100644
--- a/lib/antlr/BUCK
+++ b/lib/antlr/BUCK
@@ -1,11 +1,11 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '3.2'
+VERSION = '3.5.2'
 
 maven_jar(
   name = 'java_runtime',
   id = 'org.antlr:antlr-runtime:' + VERSION,
-  sha1 = '31c746001016c6226bd7356c9f87a6a084ce3715',
+  sha1 = 'cd9cd41361c155f3af0f653009dcecb08d8b4afd',
   license = 'antlr',
 )
 
@@ -18,8 +18,8 @@
 
 maven_jar(
   name = 'stringtemplate',
-  id = 'org.antlr:stringtemplate:' + VERSION,
-  sha1 = '6fe2e3bb57daebd1555494818909f9664376dd6c',
+  id = 'org.antlr:stringtemplate:4.0.2',
+  sha1 = 'e28e09e2d44d60506a7bcb004d6c23ff35c6ac08',
   license = 'antlr',
   attach_source = False,
   visibility = [],
@@ -28,7 +28,7 @@
 maven_jar(
   name = 'tool',
   id = 'org.antlr:antlr:' + VERSION,
-  sha1 = '6b0acabea7bb3da058200a77178057e47e25cb69',
+  sha1 = 'c4a65c950bfc3e7d04309c515b2177c00baf7764',
   license = 'antlr',
   deps = [
     ':java_runtime',