Merge "dev-release: update the instructions for sticky announcement"
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index 286d59b..0fa494d 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -17,7 +17,7 @@
 org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
 org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
 org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
 org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
 org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
 org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 1a0c0db..cd9bb4d 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -768,8 +768,7 @@
 [[category_submit]]
 === Submit
 
-This category permits users to push the `Submit Patch Set n` button
-on the web UI.
+This category permits users to submit changes.
 
 Submitting a change causes it to be merged into the destination
 branch as soon as possible, making it a permanent part of the
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index bfab875..2fe47a3 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -134,7 +134,12 @@
   ./tools/version.py 2.5
 ----
 
-Commit the change and create the release tag on the new commit:
+Also check and update the referenced `archetypeVersion` and the
+`archetypeRepository` in the `Documentation/dev-plugins.txt` file.
+If the referenced `archetypeVersion` will be available in the Maven central,
+delete the line with the `archetypeRepository`.
+
+Commit the changes and create the release tag on the new commit:
 
 ----
   git tag -a v2.5
@@ -313,13 +318,13 @@
 For an `RC`:
 +
 ----
-  git push gerrit-review refs/tags/v2.5-rc0:refs/tags/v2.5-rc0
+  git push gerrit-review tag v2.5-rc0
 ----
 +
 For a final `stable` release:
 +
 ----
-  git push gerrit-review refs/tags/v2.5:refs/tags/v2.5
+  git push gerrit-review tag v2.5
 ----
 
 
diff --git a/Documentation/images/user-review-ui-change-screen-edit-commit-message.png b/Documentation/images/user-review-ui-change-screen-edit-commit-message.png
deleted file mode 100644
index 615e9a7..0000000
--- a/Documentation/images/user-review-ui-change-screen-edit-commit-message.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 9966c8e..b5c1557 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1550,6 +1550,8 @@
 Whether to show the change sizes as colored bars in the change table.
 |`legacycid_in_change_table`      |not set if `false`|
 Whether to show change number in the change table.
+|`mute_common_path_prefixes` |not set if `false`|
+Whether to mute common path prefixes in file names in the file table.
 |`review_category_strategy`   ||
 The strategy used to displayed info in the review category column.
 Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
@@ -1591,6 +1593,8 @@
 Whether to show the change sizes as colored bars in the change table.
 |`legacycid_in_change_table`      |optional|
 Whether to show change number in the change table.
+|`mute_common_path_prefixes` |optional|
+Whether to mute common path prefixes in file names in the file table.
 |`review_category_strategy`   |optional|
 The strategy used to displayed info in the review category column.
 Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 576cec6..bd72353 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1445,6 +1445,10 @@
   RnJvbSA3ZGFkY2MxNTNmZGVhMTdhYTg0ZmYzMmE2ZTI0NWRiYjY...
 ----
 
+Alternatively, if the only value of the Accept request header is
+`application/json` the content is returned as JSON string and
+`X-FYI-Content-Encoding` is set to `json`.
+
 [[get-edit-meta-data]]
 === Retrieve meta data of a file from Change Edit
 --
@@ -1512,6 +1516,18 @@
 M2JhNjcxZTk0OTBmNzUxNDU5ZGUzCg==
 ----
 
+Alternatively, if the only value of the Accept request header is
+`application/json` the commit message is returned as JSON string:
+
+.Response
+----
+  HTTP/1.1 200 OK
+
+)]}'
+"Subject of the commit message\n\nThis is the body of the commit message.\n\nChange-Id: Iaf1ba916bf843c175673d675bf7f52862f452db9\n"
+----
+
+
 [[publish-edit]]
 === Publish Change Edit
 --
@@ -2861,6 +2877,10 @@
   Ly8gQ29weXJpZ2h0IChDKSAyMDEwIFRoZSBBbmRyb2lkIE9wZW4gU291cmNlIFByb2plY...
 ----
 
+Alternatively, if the only value of the Accept request header is
+`application/json` the content is returned as JSON string and
+`X-FYI-Content-Encoding` is set to `json`.
+
 [[get-diff]]
 === Get Diff
 --
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index c6215c9..ef2cdd9 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1121,6 +1121,38 @@
   HTTP/1.1 204 No Content
 ----
 
+[[delete-branches]]
+=== Delete Branches
+--
+'POST /projects/link:#project-name[\{project-name\}]/branches:delete'
+--
+
+Delete one or more branches.
+
+The branches to be deleted must be provided in the request body as a
+link:#delete-branches-input[DeleteBranchesInput] entity.
+
+.Request
+----
+  POST /projects/MyProject/branches:delete HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "branches": [
+      "stable-1.0",
+      "stable-2.0"
+    ]
+  }
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+If some branches could not be deleted, the response is "`409 Conflict`" and the
+error message is contained in the response body.
+
 [[get-content]]
 === Get Content
 --
@@ -2060,6 +2092,18 @@
 Tokens such as `${project}` are not resolved.
 |===========================
 
+[[delete-branches-input]]
+=== DeleteBranchesInput
+The `DeleteBranchesInput` entity contains information about branches that should
+be deleted.
+
+[options="header",width="50%",cols="1,6"]
+|==========================
+|Field Name   |Description
+|`branches`   |A list of branch names that identify the branches that should be
+deleted.
+|==========================
+
 [[gc-input]]
 === GCInput
 The `GCInput` entity contains information to run the Git garbage
@@ -2172,7 +2216,8 @@
 |Field Name                  ||Description
 |`name`                      |optional|
 The name of the project (not encoded). +
-If set, must match the project name in the URL.
+If set, must match the project name in the URL. +
+If name ends with `.git` the suffix will be automatically removed.
 |`parent`                    |optional|
 The name of the parent project. +
 If not set, the `All-Projects` project will be the parent project.
diff --git a/Documentation/user-inline-edit.txt b/Documentation/user-inline-edit.txt
index 9be89a2..4c0c5a8 100644
--- a/Documentation/user-inline-edit.txt
+++ b/Documentation/user-inline-edit.txt
@@ -5,12 +5,16 @@
 
 
 [[create-change]]
-== Creating a New Empty Change
+== Creating a New Change
 
 A new change can be created directly in the browser, meaning it is not necessary
 to clone the whole repository to make trivial changes.
 
-There are two different ways to create an empty change:
+The new change is created as a draft change, unless
+link:config-gerrit.html#change.allowDrafts[change.allowDrafts] is set to false,
+in which case the change is created as a normal new change.
+
+There are two different ways to create a new change:
 
 By clicking on the 'Create Change' button in the project screen:
 
@@ -22,8 +26,8 @@
 
 image::images/inline-edit-create-change-project-screen-dialog.png[width=800, link="images/inline-edit-create-change-project-screen-dialog.png"]
 
-By clicking the 'Follow-Up' button on the change screen, to create an empty
-change based on the selected change.
+By clicking the 'Follow-Up' button on the change screen, to create a new change
+based on the selected change.
 
 [[create-change-from-change-screen]]
 
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index bb38f6a..ab1070a 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -22,16 +22,6 @@
 
 image::images/user-review-ui-change-screen-commit-message.png[width=800, link="images/user-review-ui-change-screen-commit-message.png"]
 
-[[edit-commit-message]]
-The commit message can be edited directly in the Web UI by clicking on
-the `Edit Message` button in the change header. This opens a drop-down
-editor box in which the commit message can be edited. Saving
-modifications of the commit message automatically creates a change edit
-that must be published to become a new patch set. The commit message may
-only be edited on the current patch set.
-
-image::images/user-review-ui-change-screen-edit-commit-message.png[width=800, link="images/user-review-ui-change-screen-edit-commit-message.png"]
-
 [[permalink]]
 The numeric change ID is a link to the change and clicking on it
 refreshes the change screen. By copying the link location you can get
@@ -609,10 +599,6 @@
 each label on which the user is allowed to vote. Voting on non-current
 patch sets is not possible.
 
-Typing "LGTM" (acronym for 'Looks Good To Me') in the summary comment
-text box automatically selects the highest possible score for the
-'Code-Review' label.
-
 The inline draft comments that will be published are displayed in a
 separate section so that they can be reviewed before publishing. There
 are links to navigate to the inline comments which can be used if a
diff --git a/ReleaseNotes/ReleaseNotes-2.11.txt b/ReleaseNotes/ReleaseNotes-2.11.txt
index b8e51ea..3a7a56e 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.txt
@@ -17,9 +17,9 @@
   java -jar gerrit.war reindex --recheck-mergeable -d site_path
 ----
 
-*WARNING:* Upgrading to 2.11.x requires the server be first upgraded to 2.1.7 (or
-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:* Upgrading to 2.11.x requires the server be first upgraded to 2.8 (or
+2.9) and then to 2.11.x. If you are upgrading from 2.8.x or later, you may ignore
+this warning and upgrade directly to 2.11.x.
 
 *WARNING:* The 'Generate HTTP Password' capability has been
 link:#remove-generate-http-password-capability[removed].
@@ -34,6 +34,9 @@
 account and ask the site administrator to
 link:https://code.google.com/p/gerrit/wiki/SqlMergeUserAccounts[merge it].
 
+*WARNING:* The
+link:https://gerrit-review.googlesource.com/Documentation/2.10/rest-api-changes.html#message[
+Edit Commit Message] REST API endpoint is removed
 
 Release Highlights
 ------------------
@@ -48,37 +51,6 @@
 * The deprecated '/query' URL is removed and will now return `Not Found`.
 
 
-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.
-+
-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
 ------------
 
@@ -100,26 +72,23 @@
 * 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).
+* File content can be edited in a full screen CodeMirror editor with support for
+themes and syntax highlighting.
 
 * 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.
+* The file table in the change screen supports seamless navigation to the
+CodeMirror editor.
 
 * Edit mode can be started from the side-by-side diff screen with seamless
-navigation to CodeMirror.
+navigation to the CodeMirror editor.
 
-* 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.
+* The commit message must now be changed in the context of a change edit. The
+'Edit Message' button is removed from the change screen.
 
 * 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
 ^^^^^^^^^^^^^
 
@@ -171,8 +140,8 @@
 Changes
 ^^^^^^^
 
-* The https://gerrit-review.googlesource.com/Documentation/2.11/rest-api-changes.html#message[
-Edit Commit Message] endpoint is deprecated in favor of the new
+* The link:https://gerrit-review.googlesource.com/Documentation/2.10/rest-api-changes.html#message[
+Edit Commit Message] endpoint is removed in favor of the new
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#put-change-edit-message[
 Change commit message in Change Edit] and
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#publish-edit[
@@ -500,12 +469,6 @@
 If a change/revision is a draft the natural next step is to publish (or delete)
 it, hence these buttons should be displayed in a more prominent place.
 
-** Move 'Submit' button into header.
-+
-If a change is ready to submit the natural next step is to submit it, hence the
-'Submit' button should be displayed in a more prominent place. This is consistent
-with displaying other buttons in the header.
-
 ** Highlight the 'Publish' button in blue.
 +
 If a change is a draft the natural next step is to publish it, hence
@@ -640,11 +603,13 @@
 
 * Update GWT to 2.7.
 
+* Update gwtjsonrpc to 1.7-2-g272ca32.
+
 * Update gwtorm to 1.14-14-gf54f1f1.
 
 * Update Jetty to 9.2.6.
 
-* Update JGit to 3.6.0.201412230720-r.
+* Update JGit to 3.6.2.201501210735-r.40-g23ad3a3.
 
 * Update Lucene to 4.10.2.
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index b6b4d8b..84c2674 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -39,6 +39,7 @@
 import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
@@ -116,6 +117,9 @@
   @Inject
   protected Provider<InternalChangeQuery> queryProvider;
 
+  @Inject
+  protected @GerritServerConfig Config cfg;
+
   protected Git git;
   protected GerritServer server;
   protected TestAccount admin;
@@ -159,12 +163,26 @@
     }
   }
 
-  protected static Config wholeTopicEnabledConfig() {
+  protected static Config submitWholeTopicEnabledConfig() {
     Config cfg = new Config();
     cfg.setBoolean("change", null, "submitWholeTopic", true);
     return cfg;
   }
 
+  protected static Config allowDraftsDisabledConfig() {
+    Config cfg = new Config();
+    cfg.setBoolean("change", null, "allowDrafts", false);
+    return cfg;
+  }
+
+  protected boolean isAllowDrafts() {
+    return cfg.getBoolean("change", "allowDrafts", true);
+  }
+
+  protected boolean isSubmitWholeTopicEnabled() {
+    return cfg.getBoolean("change", null, "submitWholeTopic", false);
+  }
+
   private void beforeTest(Config cfg, boolean memory, boolean enableHttpd) throws Exception {
     server = startServer(cfg, memory, enableHttpd);
     server.getTestInjector().injectMembers(this);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
index 584186c..bf6f928 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -16,9 +16,11 @@
 
 import com.google.common.base.Charsets;
 import com.google.common.base.Preconditions;
+import com.google.common.net.HttpHeaders;
 import com.google.gerrit.extensions.restapi.RawInput;
 import com.google.gerrit.server.OutputFormat;
 
+import org.apache.http.Header;
 import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
@@ -41,7 +43,20 @@
 
   @Override
   public RestResponse get(String endPoint) throws IOException {
+    return getWithHeader(endPoint, null);
+  }
+
+  public RestResponse getJsonAccept(String endPoint) throws IOException {
+    return getWithHeader(endPoint,
+        new BasicHeader(HttpHeaders.ACCEPT, "application/json"));
+  }
+
+  private RestResponse getWithHeader(String endPoint, Header header)
+      throws IOException {
     HttpGet get = new HttpGet(url + "/a" + endPoint);
+    if (header != null) {
+      get.addHeader(header);
+    }
     return new RestResponse(getClient().execute(get));
   }
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
index d2e70c3..7de4712 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -44,6 +44,17 @@
             .name);
   }
 
+  @Test
+  public void createProjectFooWithGitSuffix() throws Exception {
+    String name = "foo";
+    assertThat(name).isEqualTo(
+        gApi.projects()
+            .name(name + ".git")
+            .create()
+            .get()
+            .name);
+  }
+
   @Test(expected = RestApiException.class)
   public void createProjectFooBar() throws Exception {
     ProjectInput in = new ProjectInput();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index ff2944f..a177d00 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.common.DiffInfo;
 import com.google.gerrit.extensions.common.MergeableInfo;
@@ -55,6 +56,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -157,6 +159,21 @@
         .cherryPick(in);
     assertThat((Iterable<?>)orig.get().messages).hasSize(2);
 
+    String cherryPickedRevision = cherry.get().currentRevision;
+    String expectedMessage = String.format(
+        "Patch Set 1: Cherry Picked\n\n" +
+        "This patchset was cherry picked to branch %s as commit %s",
+        in.destination, cherryPickedRevision);
+
+    Iterator<ChangeMessageInfo> origIt = orig.get().messages.iterator();
+    origIt.next();
+    assertThat(origIt.next().message).isEqualTo(expectedMessage);
+
+    assertThat((Iterable<?>)cherry.get().messages).hasSize(1);
+    Iterator<ChangeMessageInfo> cherryIt = cherry.get().messages.iterator();
+    expectedMessage = "Patch Set 1: Cherry Picked from branch master.";
+    assertThat(cherryIt.next().message).isEqualTo(expectedMessage);
+
     assertThat(cherry.get().subject).contains(in.message);
     assertThat(cherry.get().topic).isEqualTo("someTopic");
     cherry.current().review(ReviewInput.approve());
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 2640645..371a472 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
@@ -53,10 +53,10 @@
 import com.google.gerrit.server.edit.UnchangedCommitMessageException;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gson.stream.JsonReader;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 
-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;
@@ -82,7 +82,7 @@
   private final static String FILE_NAME3 = "foo3";
   private final static byte[] CONTENT_OLD = "bar".getBytes(UTF_8);
   private final static byte[] CONTENT_NEW = "baz".getBytes(UTF_8);
-  private final static byte[] CONTENT_NEW2 = "qux".getBytes(UTF_8);
+  private final static byte[] CONTENT_NEW2 = "quxÄÜÖßµ".getBytes(UTF_8);
 
   @Inject
   private SchemaFactory<ReviewDb> reviewDbProvider;
@@ -301,16 +301,14 @@
     assertThat(adminSession.get(urlEditMessage()).getStatusCode())
         .isEqualTo(SC_NOT_FOUND);
     EditMessage.Input in = new EditMessage.Input();
-    in.message = String.format("New commit message\n\nChange-Id: %s",
+    in.message = String.format("New commit message\n\n" +
+        CONTENT_NEW2 + "\n\nChange-Id: %s",
         change.getKey());
     assertThat(adminSession.put(urlEditMessage(), in).getStatusCode())
         .isEqualTo(SC_NO_CONTENT);
-    RestResponse r = adminSession.get(urlEditMessage());
-    assertThat(adminSession.get(urlEditMessage()).getStatusCode())
-        .isEqualTo(SC_OK);
-    String content = r.getEntityContent();
-    assertThat(StringUtils.newStringUtf8(Base64.decodeBase64(content)))
-        .isEqualTo(in.message);
+    RestResponse r = adminSession.getJsonAccept(urlEditMessage());
+    assertThat(r.getStatusCode()).isEqualTo(SC_OK);
+    assertThat(readContentFromJson(r)).isEqualTo(in.message);
     Optional<ChangeEdit> edit = editUtil.byChange(change);
     assertThat(edit.get().getEditCommit().getFullMessage())
         .isEqualTo(in.message);
@@ -538,11 +536,10 @@
     assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2)))
         .isEqualTo(RefUpdate.Result.FORCED);
     edit = editUtil.byChange(change);
-    RestResponse r = adminSession.get(urlEditFile());
+    RestResponse r = adminSession.getJsonAccept(urlEditFile());
     assertThat(r.getStatusCode()).isEqualTo(SC_OK);
-    String content = r.getEntityContent();
-    assertThat(StringUtils.newStringUtf8(Base64.decodeBase64(content)))
-        .isEqualTo(StringUtils.newStringUtf8(CONTENT_NEW2));
+    assertThat(readContentFromJson(r)).isEqualTo(
+        StringUtils.newStringUtf8(CONTENT_NEW2));
   }
 
   @Test
@@ -718,4 +715,10 @@
     assertThat(r.getStatusCode()).isEqualTo(SC_OK);
     return newGson().fromJson(r.getReader(), EditInfo.class);
   }
+
+  private String readContentFromJson(RestResponse r) throws IOException {
+    JsonReader jsonReader = new JsonReader(r.getReader());
+    jsonReader.setLenient(true);
+    return newGson().fromJson(jsonReader, String.class);
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 94f965a..41918e3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -17,6 +17,7 @@
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assert_;
+import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.GitUtil.cloneProject;
 import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
 import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
@@ -82,7 +83,7 @@
 public abstract class AbstractSubmit extends AbstractDaemonTest {
   @ConfigSuite.Config
   public static Config submitWholeTopicEnabled() {
-    return wholeTopicEnabledConfig();
+    return submitWholeTopicEnabledConfig();
   }
 
   private Map<String, String> mergeResults;
@@ -134,6 +135,21 @@
     assertThat(getRemoteHead().getId()).isEqualTo(change.getCommitId());
   }
 
+  @Test
+  public void submitWholeTopic() throws Exception {
+    assume().that(isSubmitWholeTopicEnabled()).isTrue();
+    Git git = createProject();
+    PushOneCommit.Result change1 =
+        createChange(git, "Change 1", "a.txt", "content", "test-topic");
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "b.txt", "content", "test-topic");
+    approve(change1.getChangeId());
+    approve(change2.getChangeId());
+    submit(change2.getChangeId());
+    change1.assertChange(Change.Status.MERGED, "test-topic", admin);
+    change2.assertChange(Change.Status.MERGED, "test-topic", admin);
+  }
+
   protected Git createProject() throws JSchException, IOException,
       GitAPIException {
     return createProject(true);
@@ -183,6 +199,14 @@
     return push.to(git, "refs/for/master");
   }
 
+  protected PushOneCommit.Result createChange(Git git, String subject,
+      String fileName, String content, String topic)
+          throws GitAPIException, IOException {
+    PushOneCommit push =
+        pushFactory.create(db, admin.getIdent(), subject, fileName, content);
+    return push.to(git, "refs/for/master/" + topic);
+  }
+
   protected void submit(String changeId) throws IOException {
     submit(changeId, HttpStatus.SC_OK);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index 18b33bb..311161a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -15,17 +15,24 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.testutil.ConfigSuite;
 
 import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lib.Config;
 import org.junit.Test;
 
 public class CreateChangeIT extends AbstractDaemonTest {
+  @ConfigSuite.Config
+  public static Config allowDraftsDisabled() {
+    return allowDraftsDisabledConfig();
+  }
 
   @Test
   public void createEmptyChange_MissingBranch() throws Exception {
@@ -61,9 +68,19 @@
 
   @Test
   public void createDraftChange() throws Exception {
+    assume().that(isAllowDrafts()).isTrue();
     assertChange(newChangeInfo(ChangeStatus.DRAFT));
   }
 
+  @Test
+  public void createDraftChangeNotAllowed() throws Exception {
+    assume().that(isAllowDrafts()).isFalse();
+    ChangeInfo ci = newChangeInfo(ChangeStatus.DRAFT);
+    RestResponse r = adminSession.post("/changes/", ci);
+    assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_METHOD_NOT_ALLOWED);
+    assertThat(r.getEntityContent()).contains("draft workflow is disabled");
+  }
+
   private ChangeInfo newChangeInfo(ChangeStatus status) {
     ChangeInfo in = new ChangeInfo();
     in.project = project.get();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java
similarity index 61%
rename from gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java
rename to gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java
index 564bdf6..a3809a9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DraftChangeIT.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -24,66 +25,89 @@
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.testutil.ConfigSuite;
 import com.google.gwtorm.server.OrmException;
 
 import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lib.Config;
 import org.junit.Test;
 
 import java.io.IOException;
 
-public class DeleteDraftChangeIT extends AbstractDaemonTest {
+public class DraftChangeIT extends AbstractDaemonTest {
+  @ConfigSuite.Config
+  public static Config allowDraftsDisabled() {
+    return allowDraftsDisabledConfig();
+  }
 
   @Test
   public void deleteChange() throws Exception {
-    String changeId = createChange().getChangeId();
+    PushOneCommit.Result result = createChange();
+    result.assertOkStatus();
+    String changeId = result.getChangeId();
     String triplet = "p~master~" + changeId;
     ChangeInfo c = get(triplet);
     assertThat(c.id).isEqualTo(triplet);
     assertThat(c.status).isEqualTo(ChangeStatus.NEW);
-    RestResponse r = deleteChange(changeId, adminSession);
-    assertThat(r.getEntityContent()).isEqualTo("Change is not a draft");
-    assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
+    RestResponse response = deleteChange(changeId, adminSession);
+    assertThat(response.getEntityContent()).isEqualTo("Change is not a draft");
+    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
   }
 
   @Test
   public void deleteDraftChange() throws Exception {
-    String changeId = createDraftChange();
+    assume().that(isAllowDrafts()).isTrue();
+    PushOneCommit.Result result = createDraftChange();
+    result.assertOkStatus();
+    String changeId = result.getChangeId();
     String triplet = "p~master~" + changeId;
     ChangeInfo c = get(triplet);
     assertThat(c.id).isEqualTo(triplet);
     assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
-    RestResponse r = deleteChange(changeId, adminSession);
-    assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+    RestResponse response = deleteChange(changeId, adminSession);
+    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
   }
 
   @Test
   public void publishDraftChange() throws Exception {
-    String changeId = createDraftChange();
+    assume().that(isAllowDrafts()).isTrue();
+    PushOneCommit.Result result = createDraftChange();
+    result.assertOkStatus();
+    String changeId = result.getChangeId();
     String triplet = "p~master~" + changeId;
     ChangeInfo c = get(triplet);
     assertThat(c.id).isEqualTo(triplet);
     assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
-    RestResponse r = publishChange(changeId);
-    assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+    RestResponse response = publishChange(changeId);
+    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
     c = get(triplet);
     assertThat(c.status).isEqualTo(ChangeStatus.NEW);
   }
 
   @Test
   public void publishDraftPatchSet() throws Exception {
-    String changeId = createDraftChange();
+    assume().that(isAllowDrafts()).isTrue();
+    PushOneCommit.Result result = createDraftChange();
+    result.assertOkStatus();
+    String changeId = result.getChangeId();
     String triplet = "p~master~" + changeId;
     ChangeInfo c = get(triplet);
     assertThat(c.id).isEqualTo(triplet);
     assertThat(c.status).isEqualTo(ChangeStatus.DRAFT);
-    RestResponse r = publishPatchSet(changeId);
-    assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
+    RestResponse response = publishPatchSet(changeId);
+    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SC_NO_CONTENT);
     assertThat(get(triplet).status).isEqualTo(ChangeStatus.NEW);
   }
 
-  private String createDraftChange() throws Exception {
-    PushOneCommit push = pushFactory.create(db, admin.getIdent());
-    return push.to(git, "refs/drafts/master").getChangeId();
+  @Test
+  public void createDraftChangeWhenDraftsNotAllowed() throws Exception {
+    assume().that(isAllowDrafts()).isFalse();
+    PushOneCommit.Result r = createDraftChange();
+    r.assertErrorStatus("draft workflow is disabled");
+  }
+
+  private PushOneCommit.Result createDraftChange() throws Exception {
+    return pushTo("refs/drafts/master");
   }
 
   private static RestResponse deleteChange(String changeId,
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
index 9492c437..efd16a4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -17,8 +17,12 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.GitUtil.checkout;
 
+import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.extensions.common.ChangeInfo;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -184,4 +188,57 @@
 
     assertThat(log.get(3).getId()).isEqualTo(initialHead.getId());
   }
+
+  @Test
+  public void submitDependentNonConflictingChangesOutOfOrder() throws Exception {
+    Git git = createProject();
+    RevCommit initialHead = getRemoteHead();
+
+    checkout(git, initialHead.getId().getName());
+    PushOneCommit.Result change2 = createChange(git, "Change 2", "b", "b");
+    PushOneCommit.Result change3 = createChange(git, "Change 3", "c", "c");
+    assertThat(change3.getCommit().getParent(0)).isEqualTo(change2.getCommit());
+
+    // Submit succeeds; change3 is successfully cherry-picked onto head.
+    submit(change3.getChangeId());
+    // Submit succeeds; change2 is successfully cherry-picked onto head
+    // (which was change3's cherry-pick).
+    submit(change2.getChangeId());
+
+    // change2 is the new tip.
+    List<RevCommit> log = getRemoteLog();
+    assertThat(log.get(0).getShortMessage()).isEqualTo(
+        change2.getCommit().getShortMessage());
+    assertThat(log.get(0).getParent(0)).isEqualTo(log.get(1));
+
+    assertThat(log.get(1).getShortMessage()).isEqualTo(
+        change3.getCommit().getShortMessage());
+    assertThat(log.get(1).getParent(0)).isEqualTo(log.get(2));
+
+    assertThat(log.get(2).getId()).isEqualTo(initialHead.getId());
+  }
+
+  @Test
+  public void submitDependentConflictingChangesOutOfOrder() throws Exception {
+    Git git = createProject();
+    RevCommit initialHead = getRemoteHead();
+
+    checkout(git, initialHead.getId().getName());
+    PushOneCommit.Result change2 = createChange(git, "Change 2", "b", "b1");
+    PushOneCommit.Result change3 = createChange(git, "Change 3", "b", "b2");
+    assertThat(change3.getCommit().getParent(0)).isEqualTo(change2.getCommit());
+
+    // Submit fails; change3 contains the delta "b1" -> "b2", which cannot be
+    // applied against tip.
+    submitWithConflict(change3.getChangeId());
+
+    ChangeInfo info3 = get(change3.getChangeId(), ListChangesOption.MESSAGES);
+    assertThat(info3.status).isEqualTo(ChangeStatus.NEW);
+    assertThat(Iterables.getLast(info3.messages).message.toLowerCase())
+        .contains("path conflict");
+
+    // Tip has not changed.
+    List<RevCommit> log = getRemoteLog();
+    assertThat(log.get(0)).isEqualTo(initialHead.getId());
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index a87e972..998c5ea 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -58,6 +58,17 @@
   }
 
   @Test
+  public void testCreateProjectApiWithGitSuffix() throws Exception {
+    final String newProjectName = "newProject";
+    ProjectInfo p = gApi.projects().name(newProjectName + ".git").create().get();
+    assertThat(p.name).isEqualTo(newProjectName);
+    ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+    assertThat(projectState).isNotNull();
+    assertProjectInfo(projectState.getProject(), p);
+    assertHead(newProjectName, "refs/heads/master");
+  }
+
+  @Test
   public void testCreateProject() throws Exception {
     final String newProjectName = "newProject";
     RestResponse r = adminSession.put("/projects/" + newProjectName);
@@ -71,6 +82,19 @@
   }
 
   @Test
+  public void testCreateProjectWithGitSuffix() throws Exception {
+    final String newProjectName = "newProject";
+    RestResponse r = adminSession.put("/projects/" + newProjectName + ".git");
+    assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CREATED);
+    ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
+    assertThat(p.name).isEqualTo(newProjectName);
+    ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+    assertThat(projectState).isNotNull();
+    assertProjectInfo(projectState.getProject(), p);
+    assertHead(newProjectName, "refs/heads/master");
+  }
+
+  @Test
   public void testCreateProjectWithNameMismatch_BadRequest() throws Exception {
     ProjectInput in = new ProjectInput();
     in.name = "otherName";
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
new file mode 100644
index 0000000..761e282
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetProjectIT.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.common.ProjectInfo;
+
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+
+public class GetProjectIT extends AbstractDaemonTest {
+
+  @Test
+  public void getProject() throws Exception {
+    String name = project.get();
+    RestResponse r = adminSession.get("/projects/" + name);
+    assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+    ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
+    assertThat(p.name).isEqualTo(name);
+  }
+
+  @Test
+  public void getProjectWithGitSuffix() throws Exception {
+    String name = project.get();
+    RestResponse r = adminSession.get("/projects/" + name + ".git");
+    assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+    ProjectInfo p = newGson().fromJson(r.getReader(), ProjectInfo.class);
+    assertThat(p.name).isEqualTo(name);
+  }
+
+  @Test
+  public void getProjectNotExisting() throws Exception {
+    RestResponse r = adminSession.get("/projects/does-not-exist");
+    assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_NOT_FOUND);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
index dfce940..fbd3c29 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.client.Comment;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.server.notedb.NotesMigration;
@@ -44,83 +45,95 @@
     return NotesMigration.allEnabledConfig();
   }
 
+  private final Integer lines[] = {0, 1};
+
   @Test
   public void createDraft() throws Exception {
-    PushOneCommit.Result r = createChange();
-    String changeId = r.getChangeId();
-    String revId = r.getCommit().getName();
-    ReviewInput.CommentInput comment = newCommentInfo(
-        "file1", Side.REVISION, 1, "comment 1");
-    addDraft(changeId, revId, comment);
-    Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
-    assertThat(result).hasSize(1);
-    CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
-    assertCommentInfo(comment, actual);
+    for (Integer line : lines) {
+      PushOneCommit.Result r = createChange();
+      String changeId = r.getChangeId();
+      String revId = r.getCommit().getName();
+      ReviewInput.CommentInput comment = newCommentInfo(
+          "file1", Side.REVISION, line, "comment 1");
+      addDraft(changeId, revId, comment);
+      Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
+      assertThat(result).hasSize(1);
+      CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+      assertCommentInfo(comment, actual);
+    }
   }
 
   @Test
   public void postComment() throws Exception {
-    String file = "file";
-    String contents = "contents";
-    PushOneCommit push = pushFactory.create(db, admin.getIdent(),
-        "first subject", file, contents);
-    PushOneCommit.Result r = push.to(git, "refs/for/master");
-    String changeId = r.getChangeId();
-    String revId = r.getCommit().getName();
-    ReviewInput input = new ReviewInput();
-    ReviewInput.CommentInput comment = newCommentInfo(
-        file, Side.REVISION, 1, "comment 1");
-    input.comments = new HashMap<>();
-    input.comments.put(comment.path, Lists.newArrayList(comment));
-    revision(r).review(input);
-    Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
-    assertThat(result).isNotEmpty();
-    CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
-    assertCommentInfo(comment, actual);
+    for (Integer line : lines) {
+      String file = "file";
+      String contents = "contents " + line;
+      PushOneCommit push = pushFactory.create(db, admin.getIdent(),
+          "first subject", file, contents);
+      PushOneCommit.Result r = push.to(git, "refs/for/master");
+      String changeId = r.getChangeId();
+      String revId = r.getCommit().getName();
+      ReviewInput input = new ReviewInput();
+      ReviewInput.CommentInput comment = newCommentInfo(
+          file, Side.REVISION, line, "comment 1");
+      input.comments = new HashMap<>();
+      input.comments.put(comment.path, Lists.newArrayList(comment));
+      revision(r).review(input);
+      Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
+      assertThat(result).isNotEmpty();
+      CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+      assertCommentInfo(comment, actual);
+    }
   }
 
   @Test
   public void putDraft() throws Exception {
-    PushOneCommit.Result r = createChange();
-    String changeId = r.getChangeId();
-    String revId = r.getCommit().getName();
-    ReviewInput.CommentInput comment = newCommentInfo(
-        "file1", Side.REVISION, 1, "comment 1");
-    addDraft(changeId, revId, comment);
-    Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
-    CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
-    assertCommentInfo(comment, actual);
-    String uuid = actual.id;
-    comment.message = "updated comment 1";
-    updateDraft(changeId, revId, comment, uuid);
-    result = getDraftComments(changeId, revId);
-    actual = Iterables.getOnlyElement(result.get(comment.path));
-    assertCommentInfo(comment, actual);
+    for (Integer line : lines) {
+      PushOneCommit.Result r = createChange();
+      String changeId = r.getChangeId();
+      String revId = r.getCommit().getName();
+      ReviewInput.CommentInput comment = newCommentInfo(
+          "file1", Side.REVISION, line, "comment 1");
+      addDraft(changeId, revId, comment);
+      Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
+      CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
+      assertCommentInfo(comment, actual);
+      String uuid = actual.id;
+      comment.message = "updated comment 1";
+      updateDraft(changeId, revId, comment, uuid);
+      result = getDraftComments(changeId, revId);
+      actual = Iterables.getOnlyElement(result.get(comment.path));
+      assertCommentInfo(comment, actual);
+    }
   }
 
   @Test
   public void getDraft() throws Exception {
-    PushOneCommit.Result r = createChange();
-    String changeId = r.getChangeId();
-    String revId = r.getCommit().getName();
-    ReviewInput.CommentInput comment = newCommentInfo(
-        "file1", Side.REVISION, 1, "comment 1");
-    CommentInfo returned = addDraft(changeId, revId, comment);
-    CommentInfo actual = getDraftComment(changeId, revId, returned.id);
-    assertCommentInfo(comment, actual);
+    for (Integer line : lines) {
+      PushOneCommit.Result r = createChange();
+      String changeId = r.getChangeId();
+      String revId = r.getCommit().getName();
+      ReviewInput.CommentInput comment = newCommentInfo(
+          "file1", Side.REVISION, line, "comment 1");
+      CommentInfo returned = addDraft(changeId, revId, comment);
+      CommentInfo actual = getDraftComment(changeId, revId, returned.id);
+      assertCommentInfo(comment, actual);
+    }
   }
 
   @Test
   public void deleteDraft() throws Exception {
-    PushOneCommit.Result r = createChange();
-    String changeId = r.getChangeId();
-    String revId = r.getCommit().getName();
-    ReviewInput.CommentInput comment = newCommentInfo(
-        "file1", Side.REVISION, 1, "comment 1");
-    CommentInfo returned = addDraft(changeId, revId, comment);
-    deleteDraft(changeId, revId, returned.id);
-    Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId);
-    assertThat(drafts).isEmpty();
+    for (Integer line : lines) {
+      PushOneCommit.Result r = createChange();
+      String changeId = r.getChangeId();
+      String revId = r.getCommit().getName();
+      ReviewInput.CommentInput comment = newCommentInfo(
+          "file1", Side.REVISION, line, "comment 1");
+      CommentInfo returned = addDraft(changeId, revId, comment);
+      deleteDraft(changeId, revId, returned.id);
+      Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId);
+      assertThat(drafts).isEmpty();
+    }
   }
 
   private CommentInfo addDraft(String changeId, String revId,
@@ -176,18 +189,40 @@
     assertThat(actual.line).isEqualTo(expected.line);
     assertThat(actual.message).isEqualTo(expected.message);
     assertThat(actual.inReplyTo).isEqualTo(expected.inReplyTo);
+    assertCommentRange(expected.range, actual.range);
     if (actual.side == null) {
       assertThat(Side.REVISION).isEqualTo(expected.side);
     }
   }
 
+  private static void assertCommentRange(Comment.Range expected,
+      Comment.Range actual) {
+    if (expected == null) {
+      assertThat(actual).isNull();
+    } else {
+      assertThat(actual).isNotNull();
+      assertThat(actual.startLine).isEqualTo(expected.startLine);
+      assertThat(actual.startCharacter).isEqualTo(expected.startCharacter);
+      assertThat(actual.endLine).isEqualTo(expected.endLine);
+      assertThat(actual.endCharacter).isEqualTo(expected.endCharacter);
+    }
+  }
+
   private ReviewInput.CommentInput newCommentInfo(String path,
       Side side, int line, String message) {
     ReviewInput.CommentInput input = new ReviewInput.CommentInput();
     input.path = path;
     input.side = side;
-    input.line = line;
+    input.line = line != 0 ? line : null;
     input.message = message;
+    if (line != 0) {
+      Comment.Range range = new Comment.Range();
+      range.startLine = 1;
+      range.startCharacter = 1;
+      range.endLine = 1;
+      range.endCharacter = 5;
+      input.range = range;
+    }
     return input;
   }
 }
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 b93ad3e..d911390 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,6 +56,7 @@
   protected int largeChangeSize;
   protected String replyLabel;
   protected String replyTitle;
+  protected boolean allowDraftChanges;
 
   public String getLoginUrl() {
     return loginUrl;
@@ -307,4 +308,12 @@
   public void setReplyLabel(String r) {
     replyLabel = r;
   }
+
+  public boolean isAllowDraftChanges() {
+    return allowDraftChanges;
+  }
+
+  public void setAllowDraftChanges(boolean b) {
+    allowDraftChanges = b;
+  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
index e264b31..18f356b 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/BinaryResult.java
@@ -14,11 +14,17 @@
 
 package com.google.gerrit.extensions.restapi;
 
+import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
 
 /**
  * Wrapper around a non-JSON result from a {@link RestView}.
@@ -34,13 +40,7 @@
 
   /** Produce a UTF-8 encoded result from a string. */
   public static BinaryResult create(String data) {
-    try {
-      return create(data.getBytes("UTF-8"))
-        .setContentType("text/plain")
-        .setCharacterEncoding("UTF-8");
-    } catch (UnsupportedEncodingException e) {
-      throw new RuntimeException("JVM does not support UTF-8", e);
-    }
+    return new StringResult(data);
   }
 
   /** Produce an {@code application/octet-stream} result from a byte array. */
@@ -144,6 +144,28 @@
    */
   public abstract void writeTo(OutputStream os) throws IOException;
 
+  /**
+   * Return a copy of the result as a String.
+   * <p>
+   * The default version of this method copies the result into a temporary byte
+   * array and then tries to decode it using the configured encoding.
+   *
+   * @return string version of the result.
+   * @throws IOException if the data cannot be produced or could not be
+   *         decoded to a String.
+   */
+  public String asString() throws IOException {
+    long len = getContentLength();
+    ByteArrayOutputStream buf;
+    if (0 < len) {
+      buf = new ByteArrayOutputStream((int) len);
+    } else {
+      buf = new ByteArrayOutputStream();
+    }
+    writeTo(buf);
+    return decode(buf.toByteArray(), getCharacterEncoding());
+  }
+
   /** Close the result and release any resources it holds. */
   @Override
   public void close() throws IOException {
@@ -161,6 +183,25 @@
         getContentType());
   }
 
+  private static String decode(byte[] data, String enc) {
+    try {
+      Charset cs = enc != null
+          ? Charset.forName(enc)
+          : StandardCharsets.UTF_8;
+      return cs.newDecoder()
+        .onMalformedInput(CodingErrorAction.REPORT)
+        .onUnmappableCharacter(CodingErrorAction.REPORT)
+        .decode(ByteBuffer.wrap(data))
+        .toString();
+    } catch (UnsupportedCharsetException | CharacterCodingException e) {
+      // Fallback to ISO-8850-1 style encoding.
+      StringBuilder r = new StringBuilder(data.length);
+      for (byte b : data)
+          r.append((char) (b & 0xff));
+      return r.toString();
+    }
+  }
+
   private static class Array extends BinaryResult {
     private final byte[] data;
 
@@ -173,6 +214,27 @@
     public void writeTo(OutputStream os) throws IOException {
       os.write(data);
     }
+
+    @Override
+    public String asString() {
+      return decode(data, getCharacterEncoding());
+    }
+  }
+
+  private static class StringResult extends Array {
+    private final String str;
+
+    StringResult(String str) {
+      super(str.getBytes(StandardCharsets.UTF_8));
+      setContentType("text/plain");
+      setCharacterEncoding("UTF-8");
+      this.str = str;
+    }
+
+    @Override
+    public String asString() {
+      return str;
+    }
   }
 
   private static class Stream extends BinaryResult {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index abcfa0c..4c3cc29 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -42,6 +42,7 @@
   String showRelativeDateInChangeTable();
   String showSizeBarInChangeTable();
   String showLegacycidInChangeTable();
+  String muteCommonPathPrefixes();
   String myMenu();
   String myMenuInfo();
   String myMenuName();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index 8f151a9..5596ace 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -23,6 +23,7 @@
 showRelativeDateInChangeTable = Show Relative Dates In Changes Table
 showSizeBarInChangeTable = Show Change Sizes As Colored Bars In Changes Table
 showLegacycidInChangeTable = Show Change Number In Changes Table
+muteCommonPathPrefixes = Mute Common Path Prefixes In File List
 myMenu = My Menu
 myMenuInfo = \
   Menu items for the 'My' top level menu. \
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 af3129b..5cee767 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
@@ -50,6 +50,7 @@
   private CheckBox relativeDateInChangeTable;
   private CheckBox sizeBarInChangeTable;
   private CheckBox legacycidInChangeTable;
+  private CheckBox muteCommonPathPrefixes;
   private ListBox maximumPageSize;
   private ListBox dateFormat;
   private ListBox timeFormat;
@@ -132,6 +133,7 @@
     relativeDateInChangeTable = new CheckBox(Util.C.showRelativeDateInChangeTable());
     sizeBarInChangeTable = new CheckBox(Util.C.showSizeBarInChangeTable());
     legacycidInChangeTable = new CheckBox(Util.C.showLegacycidInChangeTable());
+    muteCommonPathPrefixes = new CheckBox(Util.C.muteCommonPathPrefixes());
 
     final Grid formGrid = new Grid(11, 2);
 
@@ -173,7 +175,7 @@
     row++;
 
     formGrid.setText(row, labelIdx, "");
-    formGrid.setWidget(row, fieldIdx, legacycidInChangeTable);
+    formGrid.setWidget(row, fieldIdx, muteCommonPathPrefixes);
     row++;
 
     formGrid.setText(row, labelIdx, Util.C.diffViewLabel());
@@ -205,6 +207,7 @@
     e.listenTo(relativeDateInChangeTable);
     e.listenTo(sizeBarInChangeTable);
     e.listenTo(legacycidInChangeTable);
+    e.listenTo(muteCommonPathPrefixes);
     e.listenTo(diffView);
   }
 
@@ -230,6 +233,7 @@
     relativeDateInChangeTable.setEnabled(on);
     sizeBarInChangeTable.setEnabled(on);
     legacycidInChangeTable.setEnabled(on);
+    muteCommonPathPrefixes.setEnabled(on);
     reviewCategoryStrategy.setEnabled(on);
     diffView.setEnabled(on);
   }
@@ -246,6 +250,7 @@
     relativeDateInChangeTable.setValue(p.relativeDateInChangeTable());
     sizeBarInChangeTable.setValue(p.sizeBarInChangeTable());
     legacycidInChangeTable.setValue(p.legacycidInChangeTable());
+    muteCommonPathPrefixes.setValue(p.muteCommonPathPrefixes());
     setListBox(reviewCategoryStrategy,
         AccountGeneralPreferences.ReviewCategoryStrategy.NONE,
         p.reviewCategoryStrategy());
@@ -329,6 +334,7 @@
     p.setRelativeDateInChangeTable(relativeDateInChangeTable.getValue());
     p.setSizeBarInChangeTable(sizeBarInChangeTable.getValue());
     p.setLegacycidInChangeTable(legacycidInChangeTable.getValue());
+    p.setMuteCommonPathPrefixes(muteCommonPathPrefixes.getValue());
     p.setReviewCategoryStrategy(getListBox(reviewCategoryStrategy,
         ReviewCategoryStrategy.NONE,
         ReviewCategoryStrategy.values()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
index aec679c..e9bd5f1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/Preferences.java
@@ -44,6 +44,7 @@
     p.relativeDateInChangeTable(in.isRelativeDateInChangeTable());
     p.sizeBarInChangeTable(in.isSizeBarInChangeTable());
     p.legacycidInChangeTable(in.isLegacycidInChangeTable());
+    p.muteCommonPathPrefixes(in.isMuteCommonPathPrefixes());
     p.reviewCategoryStrategy(in.getReviewCategoryStrategy());
     p.diffView(in.getDiffView());
     p.setMyMenus(myMenus);
@@ -102,6 +103,9 @@
   public final native boolean legacycidInChangeTable()
   /*-{ return this.legacycid_in_change_table || false }-*/;
 
+  public final native boolean muteCommonPathPrefixes()
+  /*-{ return this.mute_common_path_prefixes || false }-*/;
+
   public final ReviewCategoryStrategy reviewCategoryStrategy() {
     String s = reviewCategeoryStrategyRaw();
     return s != null ? ReviewCategoryStrategy.valueOf(s) : ReviewCategoryStrategy.NONE;
@@ -164,6 +168,9 @@
   public final native void legacycidInChangeTable(boolean s)
   /*-{ this.legacycid_in_change_table = s }-*/;
 
+  public final native void muteCommonPathPrefixes(boolean s)
+  /*-{ this.mute_common_path_prefixes = s }-*/;
+
   public final void reviewCategoryStrategy(ReviewCategoryStrategy s) {
     reviewCategoryStrategyRaw(s != null ? s.toString() : null);
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
index 30e2e3d..1ffd6f0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateChangeAction.java
@@ -21,10 +21,12 @@
 import com.google.gerrit.client.ui.CreateChangeDialog;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwt.event.logical.shared.CloseEvent;
 import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.PopupPanel;
 
 class CreateChangeAction {
-  static void call(Button b, final String project) {
+  static void call(final Button b, final String project) {
     // TODO Replace CreateChangeDialog with a nicer looking display.
     b.setEnabled(false);
     new CreateChangeDialog(new Project.NameKey(project)) {
@@ -35,7 +37,7 @@
 
       @Override
       public void onSend() {
-        ChangeApi.createDraftChange(project, getDestinationBranch(),
+        ChangeApi.createChange(project, getDestinationBranch(),
           message.getText(), null,
           new GerritCallback<ChangeInfo>() {
             @Override
@@ -52,6 +54,13 @@
             }
         });
       }
+
+      @Override
+      public void onClose(CloseEvent<PopupPanel> event) {
+        super.onClose(event);
+        b.setEnabled(true);
+      }
+
     }.center();
   }
 }
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 aa50f02..5dab3ac 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
@@ -630,7 +630,8 @@
 
       if (Patch.COMMIT_MSG.equals(path)) {
         sb.append(Util.C.commitMessage());
-      } else {
+      } else if (!hasUser || Gerrit.getUserAccount().getGeneralPreferences()
+          .isMuteCommonPathPrefixes()) {
         int commonPrefixLen = commonPrefix(path);
         if (commonPrefixLen > 0) {
           sb.openSpan().setStyleName(R.css().commonPrefix())
@@ -639,6 +640,8 @@
         }
         sb.append(path.substring(commonPrefixLen));
         lastPath = path;
+      } else {
+        sb.append(path);
       }
 
       sb.closeAnchor();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java
index 1f5c516..5a7df72 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FollowUpAction.java
@@ -35,7 +35,7 @@
 
   @Override
   void send(String message) {
-    ChangeApi.createDraftChange(project, branch, message, base,
+    ChangeApi.createChange(project, branch, message, base,
         new GerritCallback<ChangeInfo>() {
           @Override
           public void onSuccess(ChangeInfo result) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
index 5eb936c..fd9f4bc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -76,8 +76,6 @@
 import java.util.TreeSet;
 
 class ReplyBox extends Composite {
-  private static final String CODE_REVIEW = "Code-Review";
-
   interface Binder extends UiBinder<HTMLPanel, ReplyBox> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
@@ -92,7 +90,6 @@
   private final String revision;
   private ReviewInput in = ReviewInput.create();
   private int labelHelpColumn;
-  private Runnable lgtm;
 
   @UiField Styles style;
   @UiField TextArea message;
@@ -173,20 +170,6 @@
       }}, 0);
   }
 
-  @UiHandler("message")
-  void onMessageKey(KeyPressEvent event) {
-    if (lgtm != null
-        && event.getCharCode() == 'M'
-        && message.getValue().equals("LGT")) {
-      Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-        @Override
-        public void execute() {
-          lgtm.run();
-        }
-      });
-    }
-  }
-
   @UiHandler("post")
   void onPost(@SuppressWarnings("unused") ClickEvent e) {
     postReview();
@@ -358,15 +341,6 @@
         labelsTable.setWidget(row, 1 + i, b);
       }
     }
-
-    if (CODE_REVIEW.equalsIgnoreCase(id) && !group.buttons.isEmpty()) {
-      lgtm = new Runnable() {
-        @Override
-        public void run() {
-          group.selectMax();
-        }
-      };
-    }
   }
 
   private void renderCheckBox(int row, LabelAndValues lv) {
@@ -390,15 +364,6 @@
     });
     b.setStyleName(style.label_name());
     labelsTable.setWidget(row, 0, b);
-
-    if (CODE_REVIEW.equalsIgnoreCase(id)) {
-      lgtm = new Runnable() {
-        @Override
-        public void run() {
-          b.setValue(true, true);
-        }
-      };
-    }
   }
 
   private static boolean isCheckBox(Set<Short> values) {
@@ -467,16 +432,6 @@
       selected = b;
       labelsTable.setText(row, labelHelpColumn, b.text);
     }
-
-    void selectMax() {
-      for (int i = 0; i < buttons.size() - 1; i++) {
-        buttons.get(i).setValue(false, false);
-      }
-
-      LabelRadioButton max = buttons.get(buttons.size() - 1);
-      max.setValue(true, true);
-      max.select();
-    }
   }
 
   private class LabelRadioButton extends RadioButton implements
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index 0078ed3..2036942 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client.changes;
 
+import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.changes.ChangeInfo.CommitInfo;
 import com.google.gerrit.client.changes.ChangeInfo.EditInfo;
 import com.google.gerrit.client.changes.ChangeInfo.IncludedInInfo;
@@ -37,15 +38,24 @@
     call(id, "abandon").post(input, cb);
   }
 
-  /** Create a draft change. */
-  public static void createDraftChange(String project, String branch,
+  /** Create a new change.
+   *
+   * The new change is created as DRAFT unless the draft workflow is disabled
+   * by `change.allowDrafts = false` in the configuration, in which case the
+   * new change is created as NEW.
+   *
+   */
+  public static void createChange(String project, String branch,
       String subject, String base, AsyncCallback<ChangeInfo> cb) {
     CreateChangeInput input = CreateChangeInput.create();
     input.project(emptyToNull(project));
     input.branch(emptyToNull(branch));
     input.subject(emptyToNull(subject));
     input.base_change(emptyToNull(base));
-    input.status(Change.Status.DRAFT.toString());
+
+    if (Gerrit.getConfig().isAllowDraftChanges()) {
+      input.status(Change.Status.DRAFT.toString());
+    }
 
     new RestApi("/changes/").post(input, cb);
   }
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 7cc080a..514a3be 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
@@ -52,16 +52,19 @@
   private final Set<DraftBox> unsavedDrafts;
   private boolean attached;
   private boolean expandAll;
+  private boolean open;
 
   CommentManager(SideBySide host,
       PatchSet.Id base, PatchSet.Id revision,
       String path,
-      CommentLinkProcessor clp) {
+      CommentLinkProcessor clp,
+      boolean open) {
     this.host = host;
     this.base = base;
     this.revision = revision;
     this.path = path;
     this.commentLinkProcessor = clp;
+    this.open = open;
 
     published = new HashMap<>();
     sideA = new TreeMap<>();
@@ -157,7 +160,8 @@
             group,
             commentLinkProcessor,
             getPatchSetIdFromSide(side),
-            info);
+            info,
+            open);
         group.add(box);
         box.setAnnotation(host.diffTable.scrollbar.comment(
             host.getCmFromSide(side),
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java
index dc52aa3..c4459b6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/FileInfo.java
@@ -43,6 +43,20 @@
         } else if (Patch.COMMIT_MSG.equals(b.path())) {
           return 1;
         }
+        // Look at file suffixes to check if it makes sense to use a different order
+        int s1 = a.path().lastIndexOf('.');
+        int s2 = b.path().lastIndexOf('.');
+        if (s1 > 0 && s2 > 0 &&
+            a.path().substring(0, s1).equals(b.path().substring(0, s2))) {
+            String suffixA = a.path().substring(s1);
+            String suffixB = b.path().substring(s2);
+            // C++ and C: give priority to header files (.h/.hpp/...)
+            if (suffixA.indexOf(".h") == 0) {
+                return -1;
+            } else if (suffixB.indexOf(".h") == 0) {
+                return 1;
+            }
+        }
         return a.path().compareTo(b.path());
       }
     });
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
index 5e79683..7d74c2b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.java
@@ -70,7 +70,8 @@
       CommentGroup group,
       CommentLinkProcessor clp,
       PatchSet.Id psId,
-      CommentInfo info) {
+      CommentInfo info,
+      boolean open) {
     super(group, info.range());
 
     this.psId = psId;
@@ -99,6 +100,8 @@
       message.setInnerSafeHtml(clp.apply(
           new SafeHtmlBuilder().append(msg).wikify()));
     }
+
+    fix.setVisible(open);
   }
 
   @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
index 4495102..bcc34e2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PublishedBox.ui.xml
@@ -66,7 +66,7 @@
           <ui:attribute name='title'/>
           <div><ui:msg>Done</ui:msg></div>
         </g:Button>
-        <g:Button ui:field='fix' styleName=''
+        <g:Button ui:field='fix' styleName='' visible='false'
             title='Fix this comment in the inline editor'>
           <ui:attribute name='title'/>
           <div><ui:msg>Fix</ui:msg></div>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
index 34e92e9..f405cb6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
@@ -116,6 +116,7 @@
   private DisplaySide startSide;
   private int startLine;
   private DiffPreferences prefs;
+  private Change.Status changeStatus;
 
   private CodeMirror cmA;
   private CodeMirror cmB;
@@ -234,6 +235,7 @@
     call.get(group2.add(new AsyncCallback<ChangeInfo>() {
       @Override
       public void onSuccess(ChangeInfo info) {
+        changeStatus = info.status();
         info.revisions().copyKeysIntoChildren("name");
         if (edit != null) {
           edit.set_name(edit.commit().commit());
@@ -244,7 +246,7 @@
         JsArray<RevisionInfo> list = info.revisions().values();
         RevisionInfo.sortRevisionInfoByNumber(list);
         diffTable.set(prefs, list, diff, edit != null, currentPatchSet,
-            info.status().isOpen());
+            changeStatus.isOpen());
         header.setChangeInfo(info);
       }
 
@@ -260,7 +262,8 @@
             commentManager = new CommentManager(
                 SideBySide.this,
                 base, revision, path,
-                result.getCommentLinkProcessor());
+                result.getCommentLinkProcessor(),
+                changeStatus.isOpen());
             setTheme(result.getTheme());
             display(comments);
           }
@@ -357,11 +360,9 @@
   }
 
   private void registerCmEvents(final CodeMirror cm) {
-    cm.on("beforeSelectionChange", onSelectionChange(cm));
     cm.on("cursorActivity", updateActiveLine(cm));
-    cm.on("gutterClick", onGutterClick(cm));
     cm.on("focus", updateActiveLine(cm));
-    cm.addKeyMap(KeyMap.create()
+    KeyMap keyMap = KeyMap.create()
         .on("A", upToChange(true))
         .on("U", upToChange(false))
         .on("[", header.navigate(Direction.PREV))
@@ -369,7 +370,6 @@
         .on("R", header.toggleReviewed())
         .on("O", commentManager.toggleOpenBox(cm))
         .on("Enter", commentManager.toggleOpenBox(cm))
-        .on("C", commentManager.insertNewDraft(cm))
         .on("N", maybeNextVimSearch(cm))
         .on("M", modifyInEditScreen(cm))
         .on("P", chunkManager.diffChunkNav(cm, Direction.PREV))
@@ -428,7 +428,13 @@
           public void run() {
             cm.execCommand("selectAll");
           }
-        }));
+        });
+    if (revision.get() != 0) {
+      cm.on("beforeSelectionChange", onSelectionChange(cm));
+      cm.on("gutterClick", onGutterClick(cm));
+      keyMap.on("C", commentManager.insertNewDraft(cm));
+    }
+    cm.addKeyMap(keyMap);
     if (prefs.renderEntireFile()) {
       cm.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
index 218a6c3..178a583 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchTable.java
@@ -261,8 +261,9 @@
 
   private static boolean isUnifiedPatchLink(final Patch patch) {
     return (patch.getPatchType().equals(PatchType.BINARY)
-        || Gerrit.getUserAccount().getGeneralPreferences().getDiffView()
-        .equals(DiffView.UNIFIED_DIFF));
+        || (Gerrit.isSignedIn()
+            && Gerrit.getUserAccount().getGeneralPreferences().getDiffView()
+            .equals(DiffView.UNIFIED_DIFF)));
   }
 
   private static String getFileNameOnly(Patch patch) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index ee8ea69..f4f7087 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -15,7 +15,6 @@
 
 import com.google.gerrit.client.VoidResult;
 import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterValue;
-import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.NativeString;
 import com.google.gerrit.client.rpc.RestApi;
@@ -60,21 +59,20 @@
   }
 
   /**
-   * Delete branches. For each branch to be deleted a separate DELETE request is
-   * fired to the server. The {@code onSuccess} method of the provided callback
-   * is invoked once after all requests succeeded. If any request fails the
-   * callbacks' {@code onFailure} method is invoked. In a failure case it can be
-   * that still some of the branches were successfully deleted.
+   * Delete branches. One call is fired to the server to delete all the
+   * branches.
    */
   public static void deleteBranches(Project.NameKey name,
       Set<String> refs, AsyncCallback<VoidResult> cb) {
-    CallbackGroup group = new CallbackGroup();
-    for (String ref : refs) {
-      project(name).view("branches").id(ref)
-          .delete(group.add(cb));
-      cb = CallbackGroup.emptyCallback();
+    if (refs.size() == 1) {
+      project(name).view("branches").id(refs.iterator().next()).delete(cb);
+    } else {
+      DeleteBranchesInput d = DeleteBranchesInput.create();
+      for (String ref : refs) {
+        d.add_branch(ref);
+      }
+      project(name).view("branches:delete").post(d, cb);
     }
-    group.done();
   }
 
   public static void getConfig(Project.NameKey name,
@@ -292,4 +290,18 @@
 
     final native void setRef(String r) /*-{ if(r)this.ref=r; }-*/;
   }
+
+  private static class DeleteBranchesInput extends JavaScriptObject {
+    static DeleteBranchesInput create() {
+      DeleteBranchesInput d = createObject().cast();
+      d.init();
+      return d;
+    }
+
+    protected DeleteBranchesInput() {
+    }
+
+    final native void init() /*-{ this.branches = []; }-*/;
+    final native void add_branch(String b) /*-{ this.branches.push(b); }-*/;
+  }
 }
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 e87853b..e48477f 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
@@ -45,6 +45,7 @@
   private static final String JSON_TYPE = "application/json";
   private static final String JSON_UTF8 = JSON_TYPE + "; charset=utf-8";
   private static final String TEXT_TYPE = "text/plain";
+  private static final String TEXT_UTF8 = TEXT_TYPE + "; charset=utf-8";
 
   /**
    * Expected JSON content body prefix that prevents XSSI.
@@ -129,9 +130,14 @@
         final String type;
         if (isJsonBody(res)) {
           try {
-            // javac generics bug
-            data = RestApi.<T> cast(parseJson(res));
-            type = JSON_TYPE;
+            JSONValue val = parseJson(res);
+            if (isJsonEncoded(res) && val.isString() != null) {
+              data = NativeString.wrap(val.isString().stringValue()).cast();
+              type = simpleType(res.getHeader("X-FYI-Content-Type"));
+            } else {
+              data = RestApi.<T> cast(val);
+              type = JSON_TYPE;
+            }
           } catch (JSONException e) {
             if (!background) {
               RpcStatus.INSTANCE.onRpcComplete();
@@ -140,9 +146,6 @@
                 "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;
@@ -371,7 +374,7 @@
 
   public <T extends JavaScriptObject> void post(String content,
       HttpCallback<T> cb) {
-    sendRaw(POST, content, cb);
+    sendText(POST, content, cb);
   }
 
   public <T extends JavaScriptObject> void put(AsyncCallback<T> cb) {
@@ -389,7 +392,7 @@
 
   public <T extends JavaScriptObject> void put(String content,
       HttpCallback<T> cb) {
-    sendRaw(PUT, content, cb);
+    sendText(PUT, content, cb);
   }
 
   public <T extends JavaScriptObject> void put(
@@ -423,10 +426,7 @@
   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,
+  private <T extends JavaScriptObject> void sendText(Method method, String body,
       HttpCallback<T> cb) {
     HttpImpl<T> httpCallback = new HttpImpl<>(background, cb);
     try {
@@ -434,7 +434,7 @@
         RpcStatus.INSTANCE.onRpcStart();
       }
       RequestBuilder req = request(method);
-      req.setHeader("Content-Type", TEXT_TYPE);
+      req.setHeader("Content-Type", TEXT_UTF8);
       req.sendRequest(body, httpCallback);
     } catch (RequestException e) {
       httpCallback.onError(null, e);
@@ -461,9 +461,8 @@
     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 isJsonEncoded(Response res) {
+    return "json".equals(res.getHeader("X-FYI-Content-Encoding"));
   }
 
   private static boolean isContentType(Response res, String want) {
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
index 2dc034b..d6b194b 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java
@@ -51,6 +51,7 @@
       Modes.I.gas(),
       Modes.I.gerrit_commit(),
       Modes.I.gfm(),
+      Modes.I.go(),
       Modes.I.groovy(),
       Modes.I.haskell(),
       Modes.I.htmlmixed(),
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
index 2bd5b00..e511be5 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
@@ -36,6 +36,7 @@
   @Source("gas.js") @DoNotEmbed DataResource gas();
   @Source("gerrit/commit.js") @DoNotEmbed DataResource gerrit_commit();
   @Source("gfm.js") @DoNotEmbed DataResource gfm();
+  @Source("go.js") @DoNotEmbed DataResource go();
   @Source("groovy.js") @DoNotEmbed DataResource groovy();
   @Source("haskell.js") @DoNotEmbed DataResource haskell();
   @Source("htmlmixed.js") @DoNotEmbed DataResource htmlmixed();
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 eca6e22..2b283ca 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
@@ -177,6 +177,8 @@
     config.setReplyTitle(replyTitle);
     config.setReplyLabel(replyLabel);
 
+    config.setAllowDraftChanges(cfg.getBoolean("change", "allowDrafts", true));
+
     return config;
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 0b66e58..e916e2c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -76,8 +76,8 @@
     }
     install(new RunAsFilter.Module());
 
+    installAuthModule();
     if (options.enableMasterFeatures()) {
-      installAuthModule();
       install(new UrlModule(options));
       install(new UiRpcModule());
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 5055d47..dd36495 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -91,6 +91,7 @@
 import com.google.gson.JsonPrimitive;
 import com.google.gson.stream.JsonReader;
 import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
 import com.google.gson.stream.MalformedJsonException;
 import com.google.gwtexpui.server.CacheHeaders;
 import com.google.inject.Inject;
@@ -105,6 +106,7 @@
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.EOFException;
+import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -718,6 +720,7 @@
     }
   }
 
+  @SuppressWarnings("resource")
   static void replyBinaryResult(
       @Nullable HttpServletRequest req,
       HttpServletResponse res,
@@ -730,7 +733,11 @@
             "attachment; filename=\"" + bin.getAttachmentName() + "\"");
       }
       if (bin.isBase64()) {
-        bin = stackBase64(res, bin);
+        if (req != null && JSON_TYPE.equals(req.getHeader(HttpHeaders.ACCEPT))) {
+          bin = stackJsonString(res, bin);
+        } else {
+          bin = stackBase64(res, bin);
+        }
       }
       if (bin.canGzip() && acceptsGzip(req)) {
         bin = stackGzip(res, bin);
@@ -757,6 +764,24 @@
     }
   }
 
+  private static BinaryResult stackJsonString(HttpServletResponse res,
+      final BinaryResult src) throws IOException {
+    TemporaryBuffer.Heap buf = heap(Integer.MAX_VALUE);
+    buf.write(JSON_MAGIC);
+    try(Writer w = new BufferedWriter(new OutputStreamWriter(buf, UTF_8));
+        JsonWriter json = new JsonWriter(w)) {
+      json.setLenient(true);
+      json.setHtmlSafe(true);
+      json.value(src.asString());
+      w.write('\n');
+    }
+    res.setHeader("X-FYI-Content-Encoding", "json");
+    res.setHeader("X-FYI-Content-Type", src.getContentType());
+    return asBinaryResult(buf)
+      .setContentType(JSON_TYPE)
+      .setCharacterEncoding(UTF_8.name());
+  }
+
   private static BinaryResult stackBase64(HttpServletResponse res,
       final BinaryResult src) throws IOException {
     BinaryResult b64;
@@ -767,10 +792,16 @@
       b64 = new BinaryResult() {
         @Override
         public void writeTo(OutputStream out) throws IOException {
-          OutputStream e = BaseEncoding.base64().encodingStream(
-              new OutputStreamWriter(out, ISO_8859_1));
-          src.writeTo(e);
-          e.flush();
+          try (OutputStreamWriter w = new OutputStreamWriter(
+                new FilterOutputStream(out) {
+                  @Override
+                  public void close() {
+                    // Do not close out, but only w and e.
+                  }
+                }, ISO_8859_1);
+              OutputStream e = BaseEncoding.base64().encodingStream(w)) {
+            src.writeTo(e);
+          }
         }
       };
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java
index 2c34711..3328a54 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/WarDistribution.java
@@ -49,8 +49,9 @@
             String pluginJarName = new File(ze.getName()).getName();
             String pluginName = pluginJarName.substring(0,
                 pluginJarName.length() - JAR.length());
-            final InputStream in = zf.getInputStream(ze);
-            processor.process(pluginName, in);
+            try (InputStream in = zf.getInputStream(ze)) {
+              processor.process(pluginName, in);
+            }
           }
         }
       }
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
index a5a8ed5..7e6f943 100755
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
@@ -265,7 +265,9 @@
   exit 1
 fi
 
-JSTACK=${JAVA:0:${#JAVA}-5}/jstack
+if test -z "$JSTACK"; then
+  JSTACK="$JAVA_HOME/bin/jstack"
+fi
 
 #####################################################
 # Add Gerrit properties to Java VM options.
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
index a0cc2bf..31494ba 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
@@ -157,6 +157,9 @@
   @Column(id = 18, length = 20, notNull = false)
   protected String reviewCategoryStrategy;
 
+  @Column(id = 19)
+  protected boolean muteCommonPathPrefixes;
+
   public AccountGeneralPreferences() {
   }
 
@@ -295,6 +298,15 @@
     this.legacycidInChangeTable = legacycidInChangeTable;
   }
 
+  public boolean isMuteCommonPathPrefixes() {
+    return muteCommonPathPrefixes;
+  }
+
+  public void setMuteCommonPathPrefixes(
+      boolean muteCommonPathPrefixes) {
+    this.muteCommonPathPrefixes = muteCommonPathPrefixes;
+  }
+
   public void resetToDefaults() {
     maximumPageSize = DEFAULT_PAGESIZE;
     showSiteHeader = true;
@@ -309,5 +321,6 @@
     diffView = null;
     sizeBarInChangeTable = true;
     legacycidInChangeTable = false;
+    muteCommonPathPrefixes = true;
   }
 }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index 4c0bb69..fe2cd96 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -598,4 +598,14 @@
   public void setTopic(String topic) {
     this.topic = topic;
   }
+
+  @Override
+  public String toString() {
+    return new StringBuilder(getClass().getSimpleName())
+        .append('{').append(changeId)
+        .append(" (").append(changeKey).append("), ")
+        .append("dest=").append(dest).append(", ")
+        .append("status=").append(status).append('}')
+        .toString();
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
index 8983013..e45e7cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
@@ -109,6 +109,7 @@
     Boolean relativeDateInChangeTable;
     Boolean sizeBarInChangeTable;
     Boolean legacycidInChangeTable;
+    Boolean muteCommonPathPrefixes;
     ReviewCategoryStrategy reviewCategoryStrategy;
     DiffView diffView;
     List<TopMenu.MenuItem> my;
@@ -127,6 +128,7 @@
         relativeDateInChangeTable = p.isRelativeDateInChangeTable() ? true : null;
         sizeBarInChangeTable = p.isSizeBarInChangeTable() ? true : null;
         legacycidInChangeTable = p.isLegacycidInChangeTable() ? true : null;
+        muteCommonPathPrefixes = p.isMuteCommonPathPrefixes() ? true : null;
         reviewCategoryStrategy = p.getReviewCategoryStrategy();
         diffView = p.getDiffView();
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index 08450f3..d75c5a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -63,6 +63,7 @@
     public Boolean relativeDateInChangeTable;
     public Boolean sizeBarInChangeTable;
     public Boolean legacycidInChangeTable;
+    public Boolean muteCommonPathPrefixes;
     public ReviewCategoryStrategy reviewCategoryStrategy;
     public DiffView diffView;
     public List<TopMenu.MenuItem> my;
@@ -150,6 +151,9 @@
       if (i.legacycidInChangeTable != null) {
         p.setLegacycidInChangeTable(i.legacycidInChangeTable);
       }
+      if (i.muteCommonPathPrefixes != null) {
+        p.setMuteCommonPathPrefixes(i.muteCommonPathPrefixes);
+      }
       if (i.reviewCategoryStrategy != null) {
         p.setReviewCategoryStrategy(i.reviewCategoryStrategy);
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 0698203..730a86f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -279,7 +279,8 @@
         try {
           final Name compositeGroupName = new CompositeName().add(groupDN);
           final Attribute in =
-              ctx.getAttributes(compositeGroupName).get(schema.accountMemberField);
+              ctx.getAttributes(compositeGroupName, schema.accountMemberFieldArray)
+                .get(schema.accountMemberField);
           if (in != null) {
             final NamingEnumeration<?> groups = in.getAll();
             try {
@@ -308,6 +309,7 @@
     final ParameterizedString accountEmailAddress;
     final ParameterizedString accountSshUserName;
     final String accountMemberField;
+    final String[] accountMemberFieldArray;
     final List<LdapQuery> accountQueryList;
 
     final List<String> groupBases;
@@ -372,7 +374,10 @@
       accountMemberField =
           LdapRealm.optdef(config, "accountMemberField", type.accountMemberField());
       if (accountMemberField != null) {
+        accountMemberFieldArray = new String[] {accountMemberField};
         accountAtts.add(accountMemberField);
+      } else {
+        accountMemberFieldArray = null;
       }
 
       final SearchScope accountScope = LdapRealm.scope(config, "accountScope");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index 262e7bd..9606397 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.GerritPersonIdent;
@@ -34,6 +35,7 @@
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -76,7 +78,9 @@
   private final CommitValidators.Factory commitValidatorsFactory;
   private final ChangeInserter.Factory changeInserterFactory;
   private final PatchSetInserter.Factory patchSetInserterFactory;
-  final MergeUtil.Factory mergeUtilFactory;
+  private final MergeUtil.Factory mergeUtilFactory;
+  private final ChangeMessagesUtil changeMessagesUtil;
+  private final ChangeUpdate.Factory updateFactory;
 
   @Inject
   CherryPickChange(Provider<ReviewDb> db,
@@ -87,7 +91,9 @@
       CommitValidators.Factory commitValidatorsFactory,
       ChangeInserter.Factory changeInserterFactory,
       PatchSetInserter.Factory patchSetInserterFactory,
-      MergeUtil.Factory mergeUtilFactory) {
+      MergeUtil.Factory mergeUtilFactory,
+      ChangeMessagesUtil changeMessagesUtil,
+      ChangeUpdate.Factory updateFactory) {
     this.db = db;
     this.queryProvider = queryProvider;
     this.gitManager = gitManager;
@@ -97,6 +103,8 @@
     this.changeInserterFactory = changeInserterFactory;
     this.patchSetInserterFactory = patchSetInserterFactory;
     this.mergeUtilFactory = mergeUtilFactory;
+    this.changeMessagesUtil = changeMessagesUtil;
+    this.updateFactory = updateFactory;
   }
 
   public Change.Id cherryPick(Change change, PatchSet patch,
@@ -113,89 +121,95 @@
 
     Project.NameKey project = change.getProject();
     IdentifiedUser identifiedUser = (IdentifiedUser) currentUser.get();
-    Repository git = null;
-    ObjectInserter oi = null;
-    RevWalk revWalk = null;
-
+    final Repository git;
     try {
       git = gitManager.openRepository(project);
-      oi = git.newObjectInserter();
-      revWalk = new RevWalk(oi.newReader());
-
-      Ref destRef = git.getRef(destinationBranch);
-      if (destRef == null) {
-        throw new InvalidChangeOperationException("Branch "
-            + destinationBranch + " does not exist.");
-      }
-
-      final RevCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
-
-      RevCommit commitToCherryPick =
-          revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
-
-      PersonIdent committerIdent =
-          identifiedUser.newCommitterIdent(TimeUtil.nowTs(),
-              serverTimeZone);
-
-      final ObjectId computedChangeId =
-          ChangeIdUtil
-              .computeChangeId(commitToCherryPick.getTree(), mergeTip,
-                  commitToCherryPick.getAuthorIdent(), committerIdent, message);
-      String commitMessage =
-          ChangeIdUtil.insertId(message, computedChangeId).trim() + '\n';
-
-      RevCommit cherryPickCommit;
-      ProjectState projectState = refControl.getProjectControl().getProjectState();
-      cherryPickCommit =
-          mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
-              commitToCherryPick, committerIdent, commitMessage, revWalk);
-      oi.flush();
-
-      Change.Key changeKey;
-      final List<String> idList = cherryPickCommit.getFooterLines(
-          FooterConstants.CHANGE_ID);
-      if (!idList.isEmpty()) {
-        final String idStr = idList.get(idList.size() - 1).trim();
-        changeKey = new Change.Key(idStr);
-      } else {
-        changeKey = new Change.Key("I" + computedChangeId.name());
-      }
-
-      Branch.NameKey newDest =
-          new Branch.NameKey(change.getProject(), destRef.getName());
-      List<ChangeData> destChanges = queryProvider.get()
-          .setLimit(2)
-          .byBranchKey(newDest, changeKey);
-      if (destChanges.size() > 1) {
-        throw new InvalidChangeOperationException("Several changes with key "
-            + changeKey + " reside on the same branch. "
-            + "Cannot create a new patch set.");
-      } else if (destChanges.size() == 1) {
-        // The change key exists on the destination branch. The cherry pick
-        // will be added as a new patch set.
-        return insertPatchSet(git, revWalk, destChanges.get(0).change(),
-            cherryPickCommit, refControl, identifiedUser);
-      } else {
-        // Change key not found on destination branch. We can create a new
-        // change.
-        return createNewChange(git, revWalk, changeKey, project,
-            patch.getId(), destRef, cherryPickCommit, refControl,
-            identifiedUser, change.getTopic());
-      }
-    } catch (MergeIdenticalTreeException | MergeConflictException e) {
-      throw new MergeException("Cherry pick failed: " + e.getMessage());
     } catch (RepositoryNotFoundException e) {
       throw new NoSuchChangeException(change.getId(), e);
-    } finally {
-      if (revWalk != null) {
+    }
+
+    try {
+      RevWalk revWalk = new RevWalk(git);
+      try {
+        Ref destRef = git.getRef(destinationBranch);
+        if (destRef == null) {
+          throw new InvalidChangeOperationException("Branch "
+              + destinationBranch + " does not exist.");
+        }
+
+        final RevCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
+
+        RevCommit commitToCherryPick =
+            revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
+
+        PersonIdent committerIdent =
+            identifiedUser.newCommitterIdent(TimeUtil.nowTs(),
+                serverTimeZone);
+
+        final ObjectId computedChangeId =
+            ChangeIdUtil
+                .computeChangeId(commitToCherryPick.getTree(), mergeTip,
+                    commitToCherryPick.getAuthorIdent(), committerIdent, message);
+        String commitMessage =
+            ChangeIdUtil.insertId(message, computedChangeId).trim() + '\n';
+
+        RevCommit cherryPickCommit;
+        ObjectInserter oi = git.newObjectInserter();
+        try {
+          ProjectState projectState = refControl.getProjectControl().getProjectState();
+          cherryPickCommit =
+              mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
+                  commitToCherryPick, committerIdent, commitMessage, revWalk);
+        } catch (MergeIdenticalTreeException | MergeConflictException e) {
+          throw new MergeException("Cherry pick failed: " + e.getMessage());
+        } finally {
+          oi.release();
+        }
+
+        Change.Key changeKey;
+        final List<String> idList = cherryPickCommit.getFooterLines(
+            FooterConstants.CHANGE_ID);
+        if (!idList.isEmpty()) {
+          final String idStr = idList.get(idList.size() - 1).trim();
+          changeKey = new Change.Key(idStr);
+        } else {
+          changeKey = new Change.Key("I" + computedChangeId.name());
+        }
+
+        Branch.NameKey newDest =
+            new Branch.NameKey(change.getProject(), destRef.getName());
+        List<ChangeData> destChanges = queryProvider.get()
+            .setLimit(2)
+            .byBranchKey(newDest, changeKey);
+        if (destChanges.size() > 1) {
+          throw new InvalidChangeOperationException("Several changes with key "
+              + changeKey + " reside on the same branch. "
+              + "Cannot create a new patch set.");
+        } else if (destChanges.size() == 1) {
+          // The change key exists on the destination branch. The cherry pick
+          // will be added as a new patch set.
+          return insertPatchSet(git, revWalk, destChanges.get(0).change(),
+              cherryPickCommit, refControl, identifiedUser);
+        } else {
+          // Change key not found on destination branch. We can create a new
+          // change.
+          Change newChange = createNewChange(git, revWalk, changeKey, project,
+              destRef, cherryPickCommit, refControl,
+              identifiedUser, change.getTopic());
+
+          addMessageToSourceChange(change, patch.getId(), destinationBranch,
+              cherryPickCommit, identifiedUser, refControl);
+
+          addMessageToDestinationChange(newChange, change.getDest().getShortName(),
+              identifiedUser, refControl);
+
+          return newChange.getId();
+        }
+      } finally {
         revWalk.release();
       }
-      if (oi != null) {
-        oi.release();
-      }
-      if (git != null) {
-        git.close();
-      }
+    } finally {
+      git.close();
     }
   }
 
@@ -218,8 +232,8 @@
     return change.getId();
   }
 
-  private Change.Id createNewChange(Repository git, RevWalk revWalk,
-      Change.Key changeKey, Project.NameKey project, PatchSet.Id patchSetId,
+  private Change createNewChange(Repository git, RevWalk revWalk,
+      Change.Key changeKey, Project.NameKey project,
       Ref destRef, RevCommit cherryPickCommit, RefControl refControl,
       IdentifiedUser identifiedUser, String topic)
       throws OrmException, InvalidChangeOperationException, IOException {
@@ -256,31 +270,51 @@
           change.getDest().getParentKey().get(), ru.getResult()));
     }
 
-    ins.setMessage(buildChangeMessage(patchSetId, change, cherryPickCommit,
-        identifiedUser))
-        .insert();
+    ins.insert();
 
-    return change.getId();
+    return change;
   }
 
-  private ChangeMessage buildChangeMessage(PatchSet.Id patchSetId, Change dest,
-      RevCommit cherryPickCommit, IdentifiedUser identifiedUser)
-      throws OrmException {
-    ChangeMessage cmsg = new ChangeMessage(
+  private void addMessageToSourceChange(Change change, PatchSet.Id patchSetId,
+      String destinationBranch, RevCommit cherryPickCommit,
+      IdentifiedUser identifiedUser, RefControl refControl) throws OrmException {
+    ChangeMessage changeMessage = new ChangeMessage(
         new ChangeMessage.Key(
             patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
             identifiedUser.getAccountId(), TimeUtil.nowTs(), patchSetId);
-    String destBranchName = dest.getDest().get();
-    StringBuilder msgBuf = new StringBuilder("Patch Set ")
+    StringBuilder sb = new StringBuilder("Patch Set ")
         .append(patchSetId.get())
         .append(": Cherry Picked")
         .append("\n\n")
         .append("This patchset was cherry picked to branch ")
-        .append(destBranchName.substring(
-            destBranchName.indexOf("refs/heads/") + "refs/heads/".length()))
+        .append(destinationBranch)
         .append(" as commit ")
         .append(cherryPickCommit.getId().getName());
-    cmsg.setMessage(msgBuf.toString());
-    return cmsg;
+    changeMessage.setMessage(sb.toString());
+
+    ChangeControl ctl = refControl.getProjectControl().controlFor(change);
+    ChangeUpdate update = updateFactory.create(ctl, change.getCreatedOn());
+    changeMessagesUtil.addChangeMessage(db.get(), update, changeMessage);
+  }
+
+  private void addMessageToDestinationChange(Change change, String sourceBranch,
+      IdentifiedUser identifiedUser, RefControl refControl) throws OrmException {
+    PatchSet.Id patchSetId =
+        db.get().patchSets().get(change.currentPatchSetId()).getId();
+    ChangeMessage changeMessage = new ChangeMessage(
+        new ChangeMessage.Key(
+            patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
+            identifiedUser.getAccountId(), TimeUtil.nowTs(), patchSetId);
+
+    StringBuilder sb = new StringBuilder("Patch Set ")
+      .append(patchSetId.get())
+      .append(": Cherry Picked from branch ")
+      .append(sourceBranch)
+      .append(".");
+    changeMessage.setMessage(sb.toString());
+
+    ChangeControl ctl = refControl.getProjectControl().controlFor(change);
+    ChangeUpdate update = updateFactory.create(ctl, change.getCreatedOn());
+    changeMessagesUtil.addChangeMessage(db.get(), update, changeMessage);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
index 43c379b..febb782 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -50,8 +51,10 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
+import com.google.gerrit.server.config.GerritServerConfig;
 
 import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -83,6 +86,7 @@
   private final ChangeInserter.Factory changeInserterFactory;
   private final ChangeJson json;
   private final ChangeUtil changeUtil;
+  private final boolean allowDrafts;
 
   @Inject
   CreateChange(Provider<ReviewDb> db,
@@ -93,7 +97,8 @@
       CommitValidators.Factory commitValidatorsFactory,
       ChangeInserter.Factory changeInserterFactory,
       ChangeJson json,
-      ChangeUtil changeUtil) {
+      ChangeUtil changeUtil,
+      @GerritServerConfig Config config) {
     this.db = db;
     this.gitManager = gitManager;
     this.serverTimeZone = myIdent.getTimeZone();
@@ -103,14 +108,15 @@
     this.changeInserterFactory = changeInserterFactory;
     this.json = json;
     this.changeUtil = changeUtil;
+    this.allowDrafts = config.getBoolean("change", "allowDrafts", true);
   }
 
   @Override
   public Response<ChangeInfo> apply(TopLevelResource parent,
       ChangeInfo input) throws AuthException, OrmException,
       BadRequestException, UnprocessableEntityException, IOException,
-      InvalidChangeOperationException, ResourceNotFoundException {
-
+      InvalidChangeOperationException, ResourceNotFoundException,
+      MethodNotAllowedException {
     if (Strings.isNullOrEmpty(input.project)) {
       throw new BadRequestException("project must be non-empty");
     }
@@ -128,6 +134,10 @@
           && input.status != ChangeStatus.DRAFT) {
         throw new BadRequestException("unsupported change status");
       }
+
+      if (!allowDrafts && input.status == ChangeStatus.DRAFT) {
+        throw new MethodNotAllowedException("draft workflow is disabled");
+      }
     }
 
     String refName = input.branch;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
index d1742cc..b276aae 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.change;
 
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -57,7 +58,8 @@
   @Override
   public Response<?> apply(ChangeResource rsrc, Input input)
       throws ResourceConflictException, AuthException,
-      ResourceNotFoundException, OrmException, IOException {
+      ResourceNotFoundException, MethodNotAllowedException,
+      OrmException, IOException {
     if (rsrc.getChange().getStatus() != Status.DRAFT) {
       throw new ResourceConflictException("Change is not a draft");
     }
@@ -67,7 +69,7 @@
     }
 
     if (!allowDrafts) {
-      throw new ResourceConflictException("Draft workflow is disabled");
+      throw new MethodNotAllowedException("draft workflow is disabled");
     }
 
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
index 373ec27..f52435e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.change;
 
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -69,7 +70,8 @@
   @Override
   public Response<?> apply(RevisionResource rsrc, Input input)
       throws AuthException, ResourceNotFoundException,
-      ResourceConflictException, OrmException, IOException {
+      ResourceConflictException, MethodNotAllowedException,
+      OrmException, IOException {
     PatchSet patchSet = rsrc.getPatchSet();
     PatchSet.Id patchSetId = patchSet.getId();
     Change change = rsrc.getChange();
@@ -79,7 +81,7 @@
     }
 
     if (!allowDrafts) {
-      throw new ResourceConflictException("Draft workflow is disabled");
+      throw new MethodNotAllowedException("draft workflow is disabled");
     }
 
     if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
index a6a6d1c..5f1ed5b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -241,7 +241,6 @@
               rw,
               null /*inserter*/,
               canMerge,
-              null /*batchRefUpdate*/,
               accepted,
               key.load.dest).dryRun(tip, rev);
         } finally {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index 7ba0548..baddd40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -43,6 +43,7 @@
 import com.google.gerrit.server.notedb.ReviewerState;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ChangeModifiedException;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.ssh.NoSshInfo;
@@ -52,7 +53,6 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
-import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
@@ -111,7 +111,6 @@
   private boolean runHooks;
   private boolean sendMail;
   private Account.Id uploader;
-  private BatchRefUpdate batchRefUpdate;
 
   @Inject
   public PatchSetInserter(ChangeHooks hooks,
@@ -216,11 +215,6 @@
     return this;
   }
 
-  public PatchSetInserter setBatchRefUpdate(BatchRefUpdate batchRefUpdate) {
-    this.batchRefUpdate = batchRefUpdate;
-    return this;
-  }
-
   public Change insert() throws InvalidChangeOperationException, OrmException,
       IOException, NoSuchChangeException {
     init();
@@ -228,23 +222,16 @@
 
     Change c = ctl.getChange();
     Change updatedChange;
-
-    if (batchRefUpdate != null) {
-      // Caller passed in update; add command, but don't execute.
-      batchRefUpdate.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit,
-        patchSet.getRefName(), ReceiveCommand.Type.CREATE));
-    } else {
-      RefUpdate ru = git.updateRef(patchSet.getRefName());
-      ru.setExpectedOldObjectId(ObjectId.zeroId());
-      ru.setNewObjectId(commit);
-      ru.disableRefLog();
-      if (ru.update(revWalk) != RefUpdate.Result.NEW) {
-        throw new IOException(String.format(
-            "Failed to create ref %s in %s: %s", patchSet.getRefName(),
-            c.getDest().getParentKey().get(), ru.getResult()));
-      }
-      gitRefUpdated.fire(c.getProject(), ru);
+    RefUpdate ru = git.updateRef(patchSet.getRefName());
+    ru.setExpectedOldObjectId(ObjectId.zeroId());
+    ru.setNewObjectId(commit);
+    ru.disableRefLog();
+    if (ru.update(revWalk) != RefUpdate.Result.NEW) {
+      throw new IOException(String.format(
+          "Failed to create ref %s in %s: %s", patchSet.getRefName(),
+          c.getDest().getParentKey().get(), ru.getResult()));
     }
+    gitRefUpdated.fire(c.getProject(), ru);
 
     final PatchSet.Id currentPatchSetId = c.currentPatchSetId();
 
@@ -394,12 +381,4 @@
     return changeMessage != null && changeMessage.getKey().getParentKey()
         .equals(patchSet.getId().getParentKey());
   }
-
-  public class ChangeModifiedException extends InvalidChangeOperationException {
-    private static final long serialVersionUID = 1L;
-
-    public ChangeModifiedException(String msg) {
-      super(msg);
-    }
-  }
 }
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 47e7b65..cd54b78 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
@@ -358,7 +358,7 @@
               new PatchLineComment.Key(
                   new Patch.Key(rsrc.getPatchSet().getId(), path),
                   ChangeUtil.messageUUID(db.get())),
-              c.line,
+              c.line != null ? c.line : 0,
               rsrc.getAccountId(),
               parent, timestamp);
         } else if (parent != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
index 47de348..9f77f0e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.change.PublishDraftPatchSet.Input;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.PatchSetNotificationSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
@@ -37,8 +36,6 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
-import org.eclipse.jgit.lib.Config;
-
 import java.io.IOException;
 
 @Singleton
@@ -52,21 +49,18 @@
   private final PatchSetNotificationSender sender;
   private final ChangeHooks hooks;
   private final ChangeIndexer indexer;
-  private final boolean allowDrafts;
 
   @Inject
   public PublishDraftPatchSet(Provider<ReviewDb> dbProvider,
       ChangeUpdate.Factory updateFactory,
       PatchSetNotificationSender sender,
       ChangeHooks hooks,
-      ChangeIndexer indexer,
-      @GerritServerConfig Config cfg) {
+      ChangeIndexer indexer) {
     this.dbProvider = dbProvider;
     this.updateFactory = updateFactory;
     this.sender = sender;
     this.hooks = hooks;
     this.indexer = indexer;
-    this.allowDrafts = cfg.getBoolean("change", "allowDrafts", true);
   }
 
   @Override
@@ -81,10 +75,6 @@
       throw new AuthException("Cannot publish this draft patch set");
     }
 
-    if (!allowDrafts) {
-      throw new ResourceConflictException("Draft workflow is disabled");
-    }
-
     PatchSet updatedPatchSet = updateDraftPatchSet(rsrc);
     Change updatedChange = updateDraftChange(rsrc);
     ChangeUpdate update = updateFactory.create(rsrc.getControl(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 2642831..6891fc7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -227,6 +227,7 @@
         if (msg != null) {
           throw new ResourceConflictException(msg.getMessage());
         }
+        //$FALL-THROUGH$
       default:
         throw new ResourceConflictException("change is " + status(change));
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
index 8b80acd..9890f53 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.changedetail;
 
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -43,7 +42,6 @@
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -140,7 +138,7 @@
       rebase(git, rw, inserter, patchSetId, change,
           uploader, baseCommit, mergeUtilFactory.create(
               changeControl.getProjectControl().getProjectState(), true),
-          committerIdent, true, true, ValidatePolicy.GERRIT, null);
+          committerIdent, true, true, ValidatePolicy.GERRIT);
     } catch (MergeConflictException e) {
       throw new IOException(e.getMessage());
     } finally {
@@ -270,8 +268,6 @@
    * @param sendMail if a mail notification should be sent for the new patch set
    * @param runHooks if hooks should be run for the new patch set
    * @param validate if commit validation should be run for the new patch set
-   * @param batchRefUpdate if not null, a batch ref update for creating new
-   *     refs, which will not be executed.
    * @return the new patch set which is based on the given base commit
    * @throws NoSuchChangeException thrown if the change to which the patch set
    *         belongs does not exist or is not visible to the user
@@ -279,13 +275,14 @@
    * @throws IOException thrown if rebase is not possible or not needed
    * @throws InvalidChangeOperationException thrown if rebase is not allowed
    */
-  public PatchSet rebase(Repository git, RevWalk revWalk,
-      ObjectInserter inserter, PatchSet.Id patchSetId, Change change,
-      IdentifiedUser uploader, RevCommit baseCommit, MergeUtil mergeUtil,
-      PersonIdent committerIdent, boolean sendMail, boolean runHooks,
-      ValidatePolicy validate, @Nullable BatchRefUpdate batchRefUpdate)
-      throws NoSuchChangeException, OrmException, IOException,
-      InvalidChangeOperationException, MergeConflictException {
+  public PatchSet rebase(final Repository git, final RevWalk revWalk,
+      final ObjectInserter inserter, final PatchSet.Id patchSetId,
+      final Change change, final IdentifiedUser uploader, final RevCommit baseCommit,
+      final MergeUtil mergeUtil, PersonIdent committerIdent,
+      boolean sendMail, boolean runHooks, ValidatePolicy validate)
+          throws NoSuchChangeException,
+      OrmException, IOException, InvalidChangeOperationException,
+      MergeConflictException {
     if (!change.currentPatchSetId().equals(patchSetId)) {
       throw new InvalidChangeOperationException("patch set is not current");
     }
@@ -308,9 +305,6 @@
         .setUploader(uploader.getAccountId())
         .setSendMail(sendMail)
         .setRunHooks(runHooks);
-    if (batchRefUpdate != null) {
-      patchSetInserter.setBatchRefUpdate(batchRefUpdate);
-    }
 
     final PatchSet.Id newPatchSetId = patchSetInserter.getPatchSetId();
     final ChangeMessage cmsg = new ChangeMessage(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
index 0044ebd..d97499c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetPreferences.java
@@ -52,6 +52,7 @@
         || i.relativeDateInChangeTable != null
         || i.sizeBarInChangeTable != null
         || i.legacycidInChangeTable != null
+        || i.muteCommonPathPrefixes != null
         || i.reviewCategoryStrategy != null) {
       throw new BadRequestException("unsupported option");
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
index 54cd329..d3ebb95 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
@@ -18,11 +18,15 @@
 public class MergeException extends Exception {
   private static final long serialVersionUID = 1L;
 
-  public MergeException(final String msg) {
-    super(msg, null);
+  public MergeException(String msg) {
+    super(msg);
   }
 
-  public MergeException(final String msg, final Throwable why) {
+  public MergeException(Throwable why) {
+    super(why);
+  }
+
+  public MergeException(String msg, Throwable why) {
     super(msg, why);
   }
 }
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 9fa341a..1f0f6648 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
@@ -76,19 +76,17 @@
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevFlag;
 import org.eclipse.jgit.revwalk.RevSort;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
 import org.joda.time.format.ISODateTimeFormat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -172,7 +170,6 @@
   private Repository repo;
   private RevWalk rw;
   private RevFlag canMergeFlag;
-  private ObjectId oldBranchTip;
   private CodeReviewCommit branchTip;
   private MergeTip mergeTip;
   private ObjectInserter inserter;
@@ -260,7 +257,7 @@
       openSchema();
       openRepository();
 
-      BatchRefUpdate branchUpdate = openBranch();
+      RefUpdate branchUpdate = openBranch();
       boolean reopen = false;
 
       ListMultimap<SubmitType, Change> toSubmit =
@@ -279,9 +276,9 @@
             logDebug("Reopening branch");
             branchUpdate = openBranch();
           }
-          SubmitStrategy strategy = createStrategy(submitType, branchUpdate);
+          SubmitStrategy strategy = createStrategy(submitType);
           MergeTip mergeTip = preMerge(strategy, toMerge.get(submitType));
-          BatchRefUpdate update = updateBranch(strategy, branchUpdate);
+          RefUpdate update = updateBranch(strategy, branchUpdate);
           reopen = true;
 
           updateChangeStatus(toSubmit.get(submitType), mergeTip);
@@ -417,11 +414,10 @@
     return mergeTip;
   }
 
-  private SubmitStrategy createStrategy(SubmitType submitType,
-      BatchRefUpdate branchUpdate)
+  private SubmitStrategy createStrategy(SubmitType submitType)
       throws MergeException, NoSuchProjectException {
     return submitStrategyFactory.create(submitType, db, repo, rw, inserter,
-        canMergeFlag, branchUpdate, getAlreadyAccepted(branchTip), destBranch);
+        canMergeFlag, getAlreadyAccepted(branchTip), destBranch);
   }
 
   private void openRepository() throws MergeException, NoSuchProjectException {
@@ -434,23 +430,25 @@
       String m = "Error opening repository \"" + name.get() + '"';
       throw new MergeException(m, err);
     }
-    inserter = repo.newObjectInserter();
-    rw = CodeReviewCommit.newRevWalk(inserter.newReader());
+
+    rw = CodeReviewCommit.newRevWalk(repo);
     rw.sort(RevSort.TOPO);
     rw.sort(RevSort.COMMIT_TIME_DESC, true);
     canMergeFlag = rw.newFlag("CAN_MERGE");
+
+    inserter = repo.newObjectInserter();
   }
 
-  private BatchRefUpdate openBranch()
+  private RefUpdate openBranch()
       throws MergeException, OrmException, NoSuchChangeException {
     try {
-      BatchRefUpdate branchUpdate = repo.getRefDatabase().newBatchUpdate();
-      Ref oldRef = repo.getRef(destBranch.get());
-      oldBranchTip = oldRef != null ? oldRef.getObjectId() : ObjectId.zeroId();
-      if (!ObjectId.zeroId().equals(oldBranchTip)) {
-        branchTip = (CodeReviewCommit) rw.parseCommit(oldBranchTip);
+      RefUpdate branchUpdate = repo.updateRef(destBranch.get());
+      if (branchUpdate.getOldObjectId() != null) {
+        branchTip =
+            (CodeReviewCommit) rw.parseCommit(branchUpdate.getOldObjectId());
       } else if (repo.getFullBranch().equals(destBranch.get())) {
         branchTip = null;
+        branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
       } else {
         for (ChangeData cd : queryProvider.get().submitted(destBranch)) {
           try {
@@ -462,6 +460,7 @@
           }
         }
       }
+      logDebug("Opened branch {}: {}", destBranch.get(), branchTip);
       return branchUpdate;
     } catch (IOException e) {
       throw new MergeException("Cannot open branch", e);
@@ -495,6 +494,7 @@
 
   private ListMultimap<SubmitType, Change> validateChangeList(
       List<ChangeData> submitted) throws MergeException {
+    logDebug("Validating {} changes", submitted.size());
     ListMultimap<SubmitType, Change> toSubmit = ArrayListMultimap.create();
 
     Map<String, Ref> allRefs;
@@ -514,11 +514,16 @@
       Change chg;
       try {
         ctl = cd.changeControl();
-        chg = cd.change();
+        // Reload change in case index was stale.
+        chg = cd.reloadChange();
       } catch (OrmException e) {
         throw new MergeException("Failed to validate changes", e);
       }
       Change.Id changeId = cd.getId();
+      if (chg.getStatus() != Change.Status.SUBMITTED) {
+        logDebug("Change {} is not submitted: {}", changeId, chg.getStatus());
+        continue;
+      }
       if (chg.currentPatchSetId() == null) {
         logError("Missing current patch set on change " + changeId);
         commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
@@ -632,6 +637,7 @@
       toMerge.put(submitType, commit);
       toSubmit.put(submitType, chg);
     }
+    logDebug("Submitting on this run: {}", toSubmit);
     return toSubmit;
   }
 
@@ -651,11 +657,11 @@
     }
   }
 
-  private BatchRefUpdate updateBranch(SubmitStrategy strategy,
-      BatchRefUpdate branchUpdate) throws MergeException {
+  private RefUpdate updateBranch(SubmitStrategy strategy,
+      RefUpdate branchUpdate) throws MergeException {
     CodeReviewCommit currentTip =
         mergeTip != null ? mergeTip.getCurrentTip() : null;
-    if (branchTip == currentTip) {
+    if (Objects.equals(branchTip, currentTip)) {
       logDebug("Branch already at merge tip {}, no update to perform",
           currentTip.name());
       return null;
@@ -664,7 +670,7 @@
       return null;
     }
 
-    if (RefNames.REFS_CONFIG.equals(destBranch.get())) {
+    if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
       logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG);
       try {
         ProjectConfig cfg =
@@ -676,34 +682,27 @@
             + destProject.getProject().getName(), e);
       }
     }
-    try {
-      inserter.flush();
-    } catch (IOException e) {
-      throw new MergeException("Cannot flush merge results", e);
-    }
 
     branchUpdate.setRefLogIdent(refLogIdent);
-    branchUpdate.setAllowNonFastForwards(false);
-    // TODO(dborowitz): This message is also used by all new patch set ref
-    // updates; find a better wording that works for that case too.
+    branchUpdate.setForceUpdate(false);
+    branchUpdate.setNewObjectId(currentTip);
     branchUpdate.setRefLogMessage("merged", true);
-    ReceiveCommand cmd =
-        new ReceiveCommand(oldBranchTip, currentTip, destBranch.get());
-    branchUpdate.addCommand(cmd);
-
     try {
-      branchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
-      logDebug("Executed batch update: {}", branchUpdate);
-      switch (cmd.getResult()) {
-        case OK:
-          if (cmd.getType() == ReceiveCommand.Type.UPDATE) {
+      RefUpdate.Result result = branchUpdate.update(rw);
+      logDebug("Update of {}: {}..{} returned status {}",
+          branchUpdate.getName(), branchUpdate.getOldObjectId(),
+          branchUpdate.getNewObjectId(), result);
+      switch (result) {
+        case NEW:
+        case FAST_FORWARD:
+          if (branchUpdate.getResult() == RefUpdate.Result.FAST_FORWARD) {
             tagCache.updateFastForward(destBranch.getParentKey(),
-                destBranch.get(),
-                oldBranchTip,
+                branchUpdate.getName(),
+                branchUpdate.getOldObjectId(),
                 currentTip);
           }
 
-          if (RefNames.REFS_CONFIG.equals(destBranch.get())) {
+          if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
             Project p = destProject.getProject();
             projectCache.evict(p);
             destProject = projectCache.get(p.getNameKey());
@@ -722,26 +721,23 @@
           } else {
             msg = "will not retry";
           }
-          throw new IOException(cmd.getResult().name() + ", " + msg);
+          // TODO(dborowitz): Implement RefUpdate.toString().
+          throw new IOException(branchUpdate.getResult().name() + ", " + msg
+              + '\n' + branchUpdate);
         default:
-          throw new IOException(cmd.getResult().name());
+          throw new IOException(branchUpdate.getResult().name()
+              + '\n' + branchUpdate);
       }
     } catch (IOException e) {
-      throw new MergeException("Cannot update " + destBranch.get(), e);
+      throw new MergeException("Cannot update " + branchUpdate.getName(), e);
     }
   }
 
-  private void fireRefUpdated(BatchRefUpdate branchUpdate) {
-    logDebug("Firing ref updated hooks for {}", destBranch.get());
+  private void fireRefUpdated(RefUpdate branchUpdate) {
+    logDebug("Firing ref updated hooks for {}", branchUpdate.getName());
     gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate);
-    Account account = getAccount(mergeTip.getCurrentTip());
-    for (ReceiveCommand cmd : branchUpdate.getCommands()) {
-      if (cmd.getRefName().equals(destBranch.get())) {
-        hooks.doRefUpdatedHook(
-            destBranch, cmd.getOldId(), cmd.getNewId(), account);
-        break;
-      }
-    }
+    hooks.doRefUpdatedHook(destBranch, branchUpdate,
+        getAccount(mergeTip.getCurrentTip()));
   }
 
   private Account getAccount(CodeReviewCommit codeReviewCommit) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index c798f4d..295ad52 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -43,6 +43,7 @@
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.NoMergeBaseException;
 import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
@@ -66,6 +67,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -77,12 +79,6 @@
 import java.util.Set;
 import java.util.TimeZone;
 
-/**
- * Utilities for various kinds of merges and cherry-picks.
- * <p>
- * <b>Note:</b> Unless otherwise noted, the methods in this class do not flush
- * the {@link ObjectInserter}s passed in after performing a merge.
- */
 public class MergeUtil {
   private static final Logger log = LoggerFactory.getLogger(MergeUtil.class);
   private static final String R_HEADS_MASTER =
@@ -181,7 +177,7 @@
     final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
 
     m.setBase(originalCommit.getParent(0));
-    if (m.merge(false, mergeTip, originalCommit)) {
+    if (m.merge(mergeTip, originalCommit)) {
       ObjectId tree = m.getResultTreeId();
       if (tree.equals(mergeTip.getTree())) {
         throw new MergeIdenticalTreeException("identical tree");
@@ -193,7 +189,7 @@
       mergeCommit.setAuthor(originalCommit.getAuthorIdent());
       mergeCommit.setCommitter(cherryPickCommitterIdent);
       mergeCommit.setMessage(commitMsg);
-      return rw.parseCommit(inserter.insert(mergeCommit));
+      return rw.parseCommit(commit(inserter, mergeCommit));
     } else {
       throw new MergeConflictException("merge conflict");
     }
@@ -397,7 +393,7 @@
 
     ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo));
     try {
-      return m.merge(false, mergeTip, toMerge);
+      return m.merge(new AnyObjectId[] {mergeTip, toMerge});
     } catch (LargeObjectException e) {
       log.warn("Cannot merge due to LargeObjectException: " + toMerge.name());
       return false;
@@ -446,7 +442,7 @@
       try {
         ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo));
         m.setBase(toMerge.getParent(0));
-        return m.merge(false, mergeTip, toMerge);
+        return m.merge(mergeTip, toMerge);
       } catch (IOException e) {
         throw new MergeException("Cannot merge " + toMerge.name(), e);
       }
@@ -498,7 +494,7 @@
       throws MergeException {
     final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
     try {
-      if (m.merge(false, mergeTip, n)) {
+      if (m.merge(new AnyObjectId[] {mergeTip, n})) {
         return writeMergeCommit(myIdent, rw, inserter, canMergeFlag, destBranch,
             mergeTip, m.getResultTreeId(), n);
       } else {
@@ -586,7 +582,7 @@
     mergeCommit.setMessage(msgbuf.toString());
 
     CodeReviewCommit mergeResult =
-        (CodeReviewCommit) rw.parseCommit(inserter.insert(mergeCommit));
+        (CodeReviewCommit) rw.parseCommit(commit(inserter, mergeCommit));
     mergeResult.setControl(n.getControl());
     return mergeResult;
   }
@@ -660,10 +656,31 @@
     Merger m = strategy.newMerger(repo, true);
     checkArgument(m instanceof ThreeWayMerger,
         "merge strategy %s does not support three-way merging", strategyName);
-    m.setObjectInserter(inserter);
+    m.setObjectInserter(new ObjectInserter.Filter() {
+      @Override
+      protected ObjectInserter delegate() {
+        return inserter;
+      }
+
+      @Override
+      public void flush() {
+      }
+
+      @Override
+      public void release() {
+      }
+    });
     return (ThreeWayMerger) m;
   }
 
+  public ObjectId commit(final ObjectInserter inserter,
+      final CommitBuilder mergeCommit) throws IOException,
+      UnsupportedEncodingException {
+    ObjectId id = inserter.insert(mergeCommit);
+    inserter.flush();
+    return id;
+  }
+
   public PatchSetApproval markCleanMerges(final RevWalk rw,
       final RevFlag canMergeFlag, final CodeReviewCommit mergeTip,
       final Set<RevCommit> alreadyAccepted) throws MergeException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 604d995..5959b1d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -1268,13 +1268,17 @@
       return;
     }
 
-    if (magicBranch.draft
-        && (!receiveConfig.allowDrafts
-            || projectControl.controlForRef("refs/drafts/" + ref)
-            .isBlocked(Permission.PUSH))) {
-      errors.put(Error.CODE_REVIEW, ref);
-      reject(cmd, "cannot upload drafts");
-      return;
+    if (magicBranch.draft) {
+      if (!receiveConfig.allowDrafts) {
+        errors.put(Error.CODE_REVIEW, ref);
+        reject(cmd, "draft workflow is disabled");
+        return;
+      } else if (projectControl.controlForRef("refs/drafts/" + ref)
+          .isBlocked(Permission.PUSH)) {
+        errors.put(Error.CODE_REVIEW, ref);
+        reject(cmd, "cannot upload drafts");
+        return;
+      }
     }
 
     if (!magicBranch.ctl.canUpload()) {
@@ -1729,6 +1733,7 @@
             addMessage("Change " + c.getChangeId() + ": " + msg.getMessage());
             break;
           }
+          //$FALL-THROUGH$
         default:
           addMessage("change " + c.getChangeId() + " is "
               + c.getStatus().name().toLowerCase());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java
index 0c384b7..8b6da7b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReviewNoteMerger.java
@@ -43,6 +43,7 @@
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ObjectStream;
 import org.eclipse.jgit.notes.Note;
 import org.eclipse.jgit.notes.NoteMerger;
 import org.eclipse.jgit.util.io.UnionInputStream;
@@ -67,12 +68,13 @@
     ObjectLoader lo = reader.open(ours.getData());
     byte[] sep = new byte[] {'\n'};
     ObjectLoader lt = reader.open(theirs.getData());
-    UnionInputStream union = new UnionInputStream(
-        lo.openStream(),
-        new ByteArrayInputStream(sep),
-        lt.openStream());
-    ObjectId noteData = inserter.insert(Constants.OBJ_BLOB,
-        lo.getSize() + sep.length + lt.getSize(), union);
-    return new Note(ours, noteData);
+    try (ObjectStream os = lo.openStream();
+        ByteArrayInputStream b = new ByteArrayInputStream(sep);
+        ObjectStream ts = lt.openStream();
+        UnionInputStream union = new UnionInputStream(os, b, ts)) {
+      ObjectId noteData = inserter.insert(Constants.OBJ_BLOB,
+          lo.getSize() + sep.length + lt.getSize(), union);
+      return new Note(ours, noteData);
+    }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index 2b73ff9..3d65fa7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -207,11 +207,11 @@
           schema.submoduleSubscriptions().bySubmodule(updatedBranch).toList();
 
       if (!subscribers.isEmpty()) {
-        String msgbuf = msg;
-        if (msgbuf == null) {
-          // Initialize the message buffer
-          msgbuf = "";
-
+        // Initialize the message buffer
+        StringBuilder sb = new StringBuilder();
+        if (msg != null) {
+          sb.append(msg);
+        } else {
           // The first updatedBranch on a cascade event of automatic
           // updates of repos is added to updatedSubscribers set so
           // if we face a situation having
@@ -227,8 +227,8 @@
                 && (c.getStatusCode() == CommitMergeStatus.CLEAN_MERGE
                     || c.getStatusCode() == CommitMergeStatus.CLEAN_PICK
                     || c.getStatusCode() == CommitMergeStatus.CLEAN_REBASE)) {
-              msgbuf += "\n";
-              msgbuf += c.getFullMessage();
+              sb.append("\n")
+                .append(c.getFullMessage());
             }
           }
         }
@@ -246,7 +246,7 @@
 
             Map<Branch.NameKey, String> paths = new HashMap<>(1);
               paths.put(updatedBranch, s.getPath());
-              updateGitlinks(s.getSuperProject(), myRw, modules, paths, msgbuf);
+              updateGitlinks(s.getSuperProject(), myRw, modules, paths, sb.toString());
             }
           } catch (SubmoduleException e) {
               log.warn("Cannot update gitlinks for " + s + " due to " + e.getMessage());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index b56ac01..c9fe1d5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.CommitMergeStatus;
 import com.google.gerrit.server.git.MergeConflictException;
@@ -36,8 +37,8 @@
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.ReceiveCommand;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -49,13 +50,16 @@
 
 public class CherryPick extends SubmitStrategy {
   private final PatchSetInfoFactory patchSetInfoFactory;
+  private final GitReferenceUpdated gitRefUpdated;
   private final Map<Change.Id, CodeReviewCommit> newCommits;
 
   CherryPick(SubmitStrategy.Arguments args,
-      PatchSetInfoFactory patchSetInfoFactory) {
+      PatchSetInfoFactory patchSetInfoFactory,
+      GitReferenceUpdated gitRefUpdated) {
     super(args);
 
     this.patchSetInfoFactory = patchSetInfoFactory;
+    this.gitRefUpdated = gitRefUpdated;
     this.newCommits = new HashMap<>();
   }
 
@@ -170,6 +174,8 @@
     ps.setUploader(cherryPickUser.getAccountId());
     ps.setRevision(new RevId(newCommit.getId().getName()));
 
+    RefUpdate ru;
+
     args.db.changes().beginTransaction(n.change().getId());
     try {
       insertAncestors(args.db, ps.getId(), newCommit);
@@ -185,14 +191,23 @@
       }
       args.db.patchSetApprovals().insert(approvals);
 
-      args.batchRefUpdate.addCommand(
-          new ReceiveCommand(ObjectId.zeroId(), newCommit, ps.getRefName()));
+      ru = args.repo.updateRef(ps.getRefName());
+      ru.setExpectedOldObjectId(ObjectId.zeroId());
+      ru.setNewObjectId(newCommit);
+      ru.disableRefLog();
+      if (ru.update(args.rw) != RefUpdate.Result.NEW) {
+        throw new IOException(String.format(
+            "Failed to create ref %s in %s: %s", ps.getRefName(), n.change()
+                .getDest().getParentKey().get(), ru.getResult()));
+      }
 
       args.db.commit();
     } finally {
       args.db.rollback();
     }
 
+    gitRefUpdated.fire(n.change().getProject(), ru);
+
     newCommit.copyFrom(n);
     newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
     newCommit.setControl(args.changeControlFactory.controlFor(n.change(), cherryPickUser));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index 5afe0e2..482dae3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -87,11 +87,11 @@
             IdentifiedUser uploader =
                 args.identifiedUserFactory.create(args.mergeUtil
                     .getSubmitter(n).getAccountId());
-            PatchSet newPatchSet = rebaseChange.rebase(args.repo, args.rw,
-                args.inserter, n.getPatchsetId(), n.change(), uploader,
-                mergeTip.getCurrentTip(), args.mergeUtil,
-                args.serverIdent.get(), false, false, ValidatePolicy.NONE,
-                args.batchRefUpdate);
+            PatchSet newPatchSet =
+                rebaseChange.rebase(args.repo, args.rw, args.inserter,
+                    n.getPatchsetId(), n.change(), uploader,
+                    mergeTip.getCurrentTip(), args.mergeUtil,
+                    args.serverIdent.get(), false, false, ValidatePolicy.NONE);
 
             List<PatchSetApproval> approvals = Lists.newArrayList();
             for (PatchSetApproval a : args.approvalsUtil.byPatchSet(args.db,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index bc536a7..b25b17e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -30,7 +30,6 @@
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.inject.Provider;
 
-import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.RefUpdate.Result;
@@ -61,7 +60,6 @@
     protected final RevWalk rw;
     protected final ObjectInserter inserter;
     protected final RevFlag canMergeFlag;
-    protected final BatchRefUpdate batchRefUpdate;
     protected final Set<RevCommit> alreadyAccepted;
     protected final Branch.NameKey destBranch;
     protected final ApprovalsUtil approvalsUtil;
@@ -73,9 +71,9 @@
         Provider<PersonIdent> serverIdent, ReviewDb db,
         ChangeControl.GenericFactory changeControlFactory, Repository repo,
         RevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag,
-        BatchRefUpdate batchRefUpdate, Set<RevCommit> alreadyAccepted,
-        Branch.NameKey destBranch, ApprovalsUtil approvalsUtil,
-        MergeUtil mergeUtil, ChangeIndexer indexer) {
+        Set<RevCommit> alreadyAccepted, Branch.NameKey destBranch,
+        ApprovalsUtil approvalsUtil, MergeUtil mergeUtil,
+        ChangeIndexer indexer) {
       this.identifiedUserFactory = identifiedUserFactory;
       this.serverIdent = serverIdent;
       this.db = db;
@@ -84,7 +82,6 @@
       this.repo = repo;
       this.rw = rw;
       this.inserter = inserter;
-      this.batchRefUpdate = batchRefUpdate;
       this.canMergeFlag = canMergeFlag;
       this.alreadyAccepted = alreadyAccepted;
       this.destBranch = destBranch;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index af7f622..ac65f8d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.changedetail.RebaseChange;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.MergeException;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.index.ChangeIndexer;
@@ -33,7 +34,6 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
-import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
@@ -55,6 +55,7 @@
   private final Provider<PersonIdent> myIdent;
   private final ChangeControl.GenericFactory changeControlFactory;
   private final PatchSetInfoFactory patchSetInfoFactory;
+  private final GitReferenceUpdated gitRefUpdated;
   private final RebaseChange rebaseChange;
   private final ProjectCache projectCache;
   private final ApprovalsUtil approvalsUtil;
@@ -67,7 +68,7 @@
       @GerritPersonIdent Provider<PersonIdent> myIdent,
       final ChangeControl.GenericFactory changeControlFactory,
       final PatchSetInfoFactory patchSetInfoFactory,
-      final RebaseChange rebaseChange,
+      final GitReferenceUpdated gitRefUpdated, final RebaseChange rebaseChange,
       final ProjectCache projectCache,
       final ApprovalsUtil approvalsUtil,
       final MergeUtil.Factory mergeUtilFactory,
@@ -76,6 +77,7 @@
     this.myIdent = myIdent;
     this.changeControlFactory = changeControlFactory;
     this.patchSetInfoFactory = patchSetInfoFactory;
+    this.gitRefUpdated = gitRefUpdated;
     this.rebaseChange = rebaseChange;
     this.projectCache = projectCache;
     this.approvalsUtil = approvalsUtil;
@@ -83,19 +85,20 @@
     this.indexer = indexer;
   }
 
-  public SubmitStrategy create(SubmitType submitType, ReviewDb db,
-      Repository repo, RevWalk rw, ObjectInserter inserter,
-      RevFlag canMergeFlag, BatchRefUpdate batchRefUpdated,
-      Set<RevCommit> alreadyAccepted, Branch.NameKey destBranch)
+  public SubmitStrategy create(final SubmitType submitType, final ReviewDb db,
+      final Repository repo, final RevWalk rw, final ObjectInserter inserter,
+      final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
+      final Branch.NameKey destBranch)
       throws MergeException, NoSuchProjectException {
     ProjectState project = getProject(destBranch);
-    SubmitStrategy.Arguments args = new SubmitStrategy.Arguments(
-        identifiedUserFactory, myIdent, db, changeControlFactory, repo, rw,
-        inserter, canMergeFlag, batchRefUpdated, alreadyAccepted, destBranch,
-        approvalsUtil, mergeUtilFactory.create(project), indexer);
+    final SubmitStrategy.Arguments args =
+        new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db,
+            changeControlFactory, repo, rw, inserter, canMergeFlag,
+            alreadyAccepted, destBranch,approvalsUtil,
+            mergeUtilFactory.create(project), indexer);
     switch (submitType) {
       case CHERRY_PICK:
-        return new CherryPick(args, patchSetInfoFactory);
+        return new CherryPick(args, patchSetInfoFactory, gitRefUpdated);
       case FAST_FORWARD_ONLY:
         return new FastForwardOnly(args);
       case MERGE_ALWAYS:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
index 652f37e..34ecbc0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/CommentsInNotesUtil.java
@@ -232,7 +232,9 @@
 
     int startLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
     if (startLine == 0) {
-      return null;
+      range.setEndLine(0);
+      ptr.value += 1;
+      return range;
     }
 
     if (note[ptr.value] == '\n') {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 50a8117..0c8c5ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -275,11 +275,24 @@
     try {
       DirCache dc = DirCache.newInCore();
       m.setDirCache(dc);
-      m.setObjectInserter(ins);
+      m.setObjectInserter(new ObjectInserter.Filter() {
+        @Override
+        protected ObjectInserter delegate() {
+          return ins;
+        }
+
+        @Override
+        public void flush() {
+        }
+
+        @Override
+        public void release() {
+        }
+      });
 
       boolean couldMerge;
       try {
-        couldMerge = m.merge(false, b.getParents());
+        couldMerge = m.merge(b.getParents());
       } catch (IOException e) {
         // It is not safe to continue further down in this method as throwing
         // an exception most likely means that the merge tree was not created
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
index 7c35014..53f39f1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPluginProvider.java
@@ -142,9 +142,10 @@
           new URLClassLoader(urls.toArray(new URL[urls.size()]),
               PluginLoader.parentFor(type));
 
+      JarScanner jarScanner = createJarScanner(srcJar);
       ServerPlugin plugin =
           new ServerPlugin(name, description.canonicalUrl, description.user,
-              srcJar, snapshot, new JarScanner(srcJar), description.dataDir,
+              srcJar, snapshot, jarScanner, description.dataDir,
               pluginLoader);
       plugin.setCleanupHandle(new CleanupHandle(tmp, jarFile));
       keep = true;
@@ -155,4 +156,13 @@
       }
     }
   }
+
+  private JarScanner createJarScanner(File srcJar)
+      throws InvalidPluginException {
+    try {
+      return new JarScanner(srcJar);
+    } catch (IOException e) {
+      throw new InvalidPluginException("Cannot scan plugin file " + srcJar, e);
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
index d7d0efd..6a65272 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -43,9 +43,11 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.jar.Attributes;
@@ -67,12 +69,8 @@
 
   private final JarFile jarFile;
 
-  public JarScanner(File srcFile) throws InvalidPluginException {
-    try {
-      this.jarFile = new JarFile(srcFile);
-    } catch (IOException e) {
-      throw new InvalidPluginException("Cannot scan plugin file " + srcFile, e);
-    }
+  public JarScanner(File srcFile) throws IOException {
+    this.jarFile = new JarFile(srcFile);
   }
 
   @Override
@@ -136,6 +134,36 @@
     return result.build();
   }
 
+  public List<String> findImplementationsOf(Class<?> requestedInterface)
+      throws IOException {
+    List<String> result = Lists.newArrayList();
+    String name = requestedInterface.getName().replace('.', '/');
+
+    Enumeration<JarEntry> e = jarFile.entries();
+    while (e.hasMoreElements()) {
+      JarEntry entry = e.nextElement();
+      if (skip(entry)) {
+        continue;
+      }
+
+      ClassData def = new ClassData(Collections.<String>emptySet());
+      try {
+        new ClassReader(read(jarFile, entry)).accept(def, SKIP_ALL);
+      } catch (RuntimeException err) {
+        PluginLoader.log.warn(String.format("Jar %s has invalid class file %s",
+            jarFile.getName(), entry.getName()), err);
+        continue;
+      }
+
+      if (def.isConcrete() && def.interfaces != null
+          && Iterables.contains(Arrays.asList(def.interfaces), name)) {
+        result.add(def.className);
+      }
+    }
+
+    return result;
+  }
+
   private static boolean skip(JarEntry entry) {
     if (!entry.getName().endsWith(".class")) {
       return true; // Avoid non-class resources.
@@ -166,6 +194,7 @@
     String className;
     String annotationName;
     String annotationValue;
+    String[] interfaces;
     Iterable<String> exports;
 
     private ClassData(Iterable<String> exports) {
@@ -183,6 +212,7 @@
         String superName, String[] interfaces) {
       this.className = Type.getObjectType(name).getClassName();
       this.access = access;
+      this.interfaces = interfaces;
     }
 
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
index a1d8ea5..0b037fb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -253,12 +253,14 @@
             public File get() {
               if (!ready) {
                 synchronized (dataDir) {
-                  if (!dataDir.exists() && !dataDir.mkdirs()) {
-                    throw new ProvisionException(String.format(
-                        "Cannot create %s for plugin %s",
-                        dataDir.getAbsolutePath(), getName()));
+                  if (!ready) {
+                    if (!dataDir.exists() && !dataDir.mkdirs()) {
+                      throw new ProvisionException(String.format(
+                          "Cannot create %s for plugin %s",
+                          dataDir.getAbsolutePath(), getName()));
+                    }
+                    ready = true;
                   }
-                  ready = true;
                 }
               }
               return dataDir;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeModifiedException.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeModifiedException.java
new file mode 100644
index 0000000..f3ca8b6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeModifiedException.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+public class ChangeModifiedException extends InvalidChangeOperationException {
+  private static final long serialVersionUID = 1L;
+
+  public ChangeModifiedException(String msg) {
+    super(msg);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index 983549d..834dba5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -166,6 +166,7 @@
               }
               refPrefix = getRefPrefix(refPrefix);
             }
+            //$FALL-THROUGH$
           default: {
             throw new IOException(result.name());
           }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index 9a714f8..4aba333 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -32,6 +32,7 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.errors.LockFailedException;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.slf4j.Logger;
@@ -42,6 +43,8 @@
 @Singleton
 public class DeleteBranch implements RestModifyView<BranchResource, Input>{
   private static final Logger log = LoggerFactory.getLogger(DeleteBranch.class);
+  private static final int MAX_LOCK_FAILURE_CALLS = 10;
+  private static final long SLEEP_ON_LOCK_FAILURE_MS = 15;
 
   static class Input {
   }
@@ -81,14 +84,28 @@
     Repository r = repoManager.openRepository(rsrc.getNameKey());
     try {
       RefUpdate.Result result;
-      RefUpdate u;
-      try {
-        u = r.updateRef(rsrc.getRef());
-        u.setForceUpdate(true);
-        result = u.delete();
-      } catch (IOException e) {
-        log.error("Cannot delete " + rsrc.getBranchKey(), e);
-        throw e;
+      RefUpdate u = r.updateRef(rsrc.getRef());
+      u.setForceUpdate(true);
+      int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+      for (;;) {
+        try {
+          result = u.delete();
+        } catch (LockFailedException e) {
+          result = RefUpdate.Result.LOCK_FAILURE;
+        } catch (IOException e) {
+          log.error("Cannot delete " + rsrc.getBranchKey(), e);
+          throw e;
+        }
+        if (result == RefUpdate.Result.LOCK_FAILURE
+            && --remainingLockFailureCalls > 0) {
+          try {
+            Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
+          } catch (InterruptedException ie) {
+            // ignore
+          }
+        } else {
+          break;
+        }
       }
 
       switch (result) {
@@ -104,7 +121,7 @@
           break;
 
         case REJECTED_CURRENT_BRANCH:
-          log.warn("Cannot delete " + rsrc.getBranchKey() + ": " + result.name());
+          log.error("Cannot delete " + rsrc.getBranchKey() + ": " + result.name());
           throw new ResourceConflictException("cannot delete current branch");
 
         default:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
new file mode 100644
index 0000000..bdc67ac
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
@@ -0,0 +1,181 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import static java.lang.String.format;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.DeleteBranches.Input;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+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;
+
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceiveCommand.Result;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+@Singleton
+class DeleteBranches implements RestModifyView<ProjectResource, Input> {
+  private static final Logger log = LoggerFactory.getLogger(DeleteBranches.class);
+
+  static class Input {
+    List<String> branches;
+
+    static Input init(Input in) {
+      if (in == null) {
+        in = new Input();
+      }
+      if (in.branches == null) {
+        in.branches = Lists.newArrayListWithCapacity(1);
+      }
+      return in;
+    }
+  }
+
+  private final Provider<IdentifiedUser> identifiedUser;
+  private final GitRepositoryManager repoManager;
+  private final Provider<ReviewDb> dbProvider;
+  private final Provider<InternalChangeQuery> queryProvider;
+  private final GitReferenceUpdated referenceUpdated;
+  private final ChangeHooks hooks;
+
+  @Inject
+  DeleteBranches(Provider<IdentifiedUser> identifiedUser,
+      GitRepositoryManager repoManager,
+      Provider<ReviewDb> dbProvider,
+      Provider<InternalChangeQuery> queryProvider,
+      GitReferenceUpdated referenceUpdated,
+      ChangeHooks hooks) {
+    this.identifiedUser = identifiedUser;
+    this.repoManager = repoManager;
+    this.dbProvider = dbProvider;
+    this.queryProvider = queryProvider;
+    this.referenceUpdated = referenceUpdated;
+    this.hooks = hooks;
+  }
+
+  @Override
+  public Response<?> apply(ProjectResource project, Input input)
+      throws OrmException, IOException, ResourceConflictException {
+    input = Input.init(input);
+    Repository r = repoManager.openRepository(project.getNameKey());
+    try {
+      BatchRefUpdate batchUpdate = r.getRefDatabase().newBatchUpdate();
+      for (String branch : input.branches) {
+        batchUpdate.addCommand(createDeleteCommand(project, r, branch));
+      }
+      RevWalk rw = new RevWalk(r);
+      try {
+        batchUpdate.execute(rw, NullProgressMonitor.INSTANCE);
+      } finally {
+        rw.release();
+      }
+      StringBuilder errorMessages = new StringBuilder();
+      for (ReceiveCommand command : batchUpdate.getCommands()) {
+        if (command.getResult() == Result.OK) {
+          postDeletion(project, command);
+        } else {
+          appendAndLogErrorMessage(errorMessages, command);
+        }
+      }
+      if (errorMessages.length() > 0) {
+        throw new ResourceConflictException(errorMessages.toString());
+      }
+    } finally {
+      r.close();
+    }
+    return Response.none();
+  }
+
+  private ReceiveCommand createDeleteCommand(ProjectResource project,
+      Repository r, String branch) throws OrmException, IOException {
+    Ref ref = r.getRefDatabase().getRef(branch);
+    ReceiveCommand command;
+    if (ref == null) {
+      command = new ReceiveCommand(ObjectId.zeroId(), ObjectId.zeroId(), branch);
+      command.setResult(Result.REJECTED_OTHER_REASON,
+          "it doesn't exist or you do not have permission to delete it");
+      return command;
+    }
+    command =
+        new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), ref.getName());
+    Branch.NameKey branchKey =
+        new Branch.NameKey(project.getNameKey(), ref.getName());
+    if (!project.getControl().controlForRef(branchKey).canDelete()) {
+      command.setResult(Result.REJECTED_OTHER_REASON,
+          "it doesn't exist or you do not have permission to delete it");
+    }
+    if (!queryProvider.get().setLimit(1).byBranchOpen(branchKey).isEmpty()) {
+      command.setResult(Result.REJECTED_OTHER_REASON, "it has open changes");
+    }
+    return command;
+  }
+
+  private void appendAndLogErrorMessage(StringBuilder errorMessages,
+      ReceiveCommand cmd) {
+    String msg = null;
+    switch (cmd.getResult()) {
+      case REJECTED_CURRENT_BRANCH:
+        msg = format("Cannot delete %s: it is the current branch",
+            cmd.getRefName());
+        break;
+      case REJECTED_OTHER_REASON:
+        msg = format("Cannot delete %s: %s", cmd.getRefName(), cmd.getMessage());
+        break;
+      default:
+        msg = format("Cannot delete %s: %s", cmd.getRefName(), cmd.getResult());
+        break;
+    }
+    log.error(msg);
+    errorMessages.append(msg);
+    errorMessages.append("\n");
+  }
+
+  private void postDeletion(ProjectResource project, ReceiveCommand cmd)
+      throws OrmException {
+    referenceUpdated.fire(project.getNameKey(), cmd.getRefName(),
+        cmd.getOldId(), cmd.getNewId());
+    Branch.NameKey branchKey =
+        new Branch.NameKey(project.getNameKey(), cmd.getRefName());
+    hooks.doRefUpdatedHook(branchKey, cmd.getOldId(), cmd.getNewId(),
+        identifiedUser.get().getAccount());
+    ResultSet<SubmoduleSubscription> submoduleSubscriptions =
+        dbProvider.get().submoduleSubscriptions().bySuperProject(branchKey);
+    dbProvider.get().submoduleSubscriptions().delete(submoduleSubscriptions);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index ace221d..430d8f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -64,6 +64,7 @@
     put(BRANCH_KIND).to(PutBranch.class);
     get(BRANCH_KIND).to(GetBranch.class);
     delete(BRANCH_KIND).to(DeleteBranch.class);
+    post(PROJECT_KIND, "branches:delete").to(DeleteBranches.class);
     install(new FactoryModuleBuilder().build(CreateBranch.Factory.class));
     get(BRANCH_KIND, "reflog").to(GetReflog.class);
     child(BRANCH_KIND, "files").to(FilesCollection.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
index 519f4f2..2f0386f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
@@ -31,6 +31,8 @@
 
 import java.io.IOException;
 
+import org.eclipse.jgit.lib.Constants;
+
 @Singleton
 public class ProjectsCollection implements
     RestCollection<TopLevelResource, ProjectResource>,
@@ -88,6 +90,9 @@
   }
 
   private ProjectResource _parse(String id) throws IOException {
+    if (id.endsWith(Constants.DOT_GIT_EXT)) {
+      id = id.substring(0, id.length() - Constants.DOT_GIT_EXT.length());
+    }
     ProjectControl ctl;
     try {
       ctl = controlFactory.controlFor(
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 33252e8..ca15287 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
@@ -253,6 +253,7 @@
                           "The value '%s' is not permitted for parameter '%s' of plugin '"
                               + pluginName + "'", value, v.getKey()));
                     }
+                    //$FALL-THROUGH$
                   case STRING:
                     cfg.setString(v.getKey(), value);
                     break;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
index 82260b4..f48cfd8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
@@ -130,7 +130,6 @@
       Paginated p = (Paginated) source;
       while (skipped && r.size() < p.limit() + start) {
         skipped = false;
-        last = null;
         ResultSet<ChangeData> next = p.restart(nextStart);
 
         for (ChangeData data : buffer(source, next)) {
@@ -139,7 +138,6 @@
           } else {
             skipped = true;
           }
-          last = data;
           nextStart++;
         }
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index b3a0a9a..62e1ba9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -445,11 +445,16 @@
 
   public Change change() throws OrmException {
     if (change == null) {
-      change = db.changes().get(legacyId);
+      reloadChange();
     }
     return change;
   }
 
+  public Change reloadChange() throws OrmException {
+    change = db.changes().get(legacyId);
+    return change;
+  }
+
   public ChangeNotes notes() throws OrmException {
     if (notes == null) {
       notes = notesFactory.create(change());
@@ -651,7 +656,13 @@
 
   @Override
   public String toString() {
-    return MoreObjects.toStringHelper(this).addValue(getId()).toString();
+    MoreObjects.ToStringHelper h = MoreObjects.toStringHelper(this);
+    if (change != null) {
+      h.addValue(change);
+    } else {
+      h.addValue(legacyId);
+    }
+    return h.toString();
   }
 
   public static class ChangedLines {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index 2c55f8c..87c33a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -119,7 +119,7 @@
                 SubmitStrategy strategy =
                     args.submitStrategyFactory.create(submitType,
                         db.get(), repo, rw, null, canMergeFlag,
-                        null, getAlreadyAccepted(repo, rw, commit),
+                        getAlreadyAccepted(repo, rw, commit),
                         otherChange.getDest());
                 CodeReviewCommit otherCommit =
                     (CodeReviewCommit) rw.parseCommit(other);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 50b3d88..945baa8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
 /** A version of the database schema. */
 public abstract class SchemaVersion {
   /** The current schema version. */
-  public static final Class<Schema_106> C = Schema_106.class;
+  public static final Class<Schema_107> C = Schema_107.class;
 
   public static int getBinaryVersion() {
     return guessVersion(C);
@@ -46,7 +46,7 @@
     this.versionNbr = guessVersion(getClass());
   }
 
-  private static int guessVersion(Class<?> c) {
+  public static int guessVersion(Class<?> c) {
     String n = c.getName();
     n = n.substring(n.lastIndexOf('_') + 1);
     while (n.startsWith("0"))
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_107.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_107.java
new file mode 100644
index 0000000..13ab09a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_107.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_107 extends SchemaVersion {
+
+  @Inject
+  Schema_107(Provider<Schema_106> prior) {
+    super(prior);
+  }
+
+  @Override
+  protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
+    Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+    try {
+      stmt.executeUpdate("UPDATE accounts set mute_common_path_prefixes = 'Y'");
+    } finally {
+      stmt.close();
+    }
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index c356a19..db17f80 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -812,7 +812,6 @@
         noteString);
   }
 
-
   @Test
   public void patchLineCommentMultipleOnePatchsetOneFileBothSides()
       throws Exception {
@@ -1154,6 +1153,34 @@
   }
 
   @Test
+  public void fileComment() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, otherUser);
+    String uuid = "uuid";
+    String messageForBase = "comment for base";
+    Timestamp now = TimeUtil.nowTs();
+    PatchSet.Id psId = c.currentPatchSetId();
+
+    PatchLineComment commentForBase =
+        newPublishedPatchLineComment(psId, "filename", uuid,
+        null, 0, otherUser, null, now, messageForBase,
+        (short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
+    update.setPatchSetId(psId);
+    update.upsertComment(commentForBase);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    Multimap<PatchSet.Id, PatchLineComment> commentsForBase =
+        notes.getBaseComments();
+    Multimap<PatchSet.Id, PatchLineComment> commentsForPs =
+        notes.getPatchSetComments();
+
+    assertTrue(commentsForPs.isEmpty());
+    assertEquals(commentForBase,
+        Iterables.getOnlyElement(commentsForBase.get(psId)));
+  }
+
+  @Test
   public void patchLineCommentNoRange() throws Exception {
     Change c = newChange();
     ChangeUpdate update = newUpdate(c, otherUser);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 94967f0..3306592 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -411,8 +411,8 @@
 
     a.add(null);
     a.add(new CipherNone.Factory());
-    setCipherFactories(filter(cfg, "cipher", a.toArray(new NamedFactory[a
-        .size()])));
+    setCipherFactories(filter(cfg, "cipher",
+        (NamedFactory<Cipher>[])a.toArray(new NamedFactory[a.size()])));
   }
 
   private void initMacs(final Config cfg) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
index e093cfb..439b8c8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
@@ -268,11 +268,11 @@
   }
 
   private String extractWhat(DispatchCommand dcmd) {
-    String commandName = dcmd.getCommandName();
+    StringBuilder commandName = new StringBuilder(dcmd.getCommandName());
     String[] args = dcmd.getArguments();
     for (int i = 1; i < args.length; i++) {
-      commandName = commandName + "." + args[i];
+      commandName.append(".").append(args[i]);
     }
-    return commandName;
+    return commandName.toString();
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 5e33c8f..3ac72a7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -340,15 +340,16 @@
     }
 
     for (LabelType type : allProjectsControl.getLabelTypes().getLabelTypes()) {
-      String usage;
-      usage = "score for " + type.getName() + "\n";
+      StringBuilder usage = new StringBuilder("score for ")
+        .append(type.getName())
+        .append("\n");
 
       for (LabelValue v : type.getValues()) {
-        usage += v.format() + "\n";
+        usage.append(v.format()).append("\n");
       }
 
       final String name = "--" + type.getName().toLowerCase();
-      optionList.add(new ApproveOption(name, usage, type));
+      optionList.add(new ApproveOption(name, usage.toString(), type));
     }
 
     super.parseCommandLine();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index b484a3f..1cb442d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -307,17 +307,17 @@
   private List<String> readSshKey(final List<String> sshKeys)
       throws UnsupportedEncodingException, IOException {
     if (!sshKeys.isEmpty()) {
-      String sshKey;
       int idx = sshKeys.indexOf("-");
       if (idx >= 0) {
-        sshKey = "";
+        StringBuilder sshKey = new StringBuilder();
         BufferedReader br =
             new BufferedReader(new InputStreamReader(in, "UTF-8"));
         String line;
         while ((line = br.readLine()) != null) {
-          sshKey += line + "\n";
+          sshKey.append(line)
+            .append("\n");
         }
-        sshKeys.set(idx, sshKey);
+        sshKeys.set(idx, sshKey.toString());
       }
     }
     return sshKeys;
diff --git a/lib/codemirror/cm.defs b/lib/codemirror/cm.defs
index cc0880e..db87b25 100644
--- a/lib/codemirror/cm.defs
+++ b/lib/codemirror/cm.defs
@@ -53,6 +53,7 @@
   'erlang',
   'gas',
   'gfm',
+  'go',
   'groovy',
   'haskell',
   'htmlmixed',
diff --git a/plugins/replication b/plugins/replication
index 10fbbce..38c2a6b 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 10fbbcecedc3f85289f8b164a0c7e2968d61a5c6
+Subproject commit 38c2a6b62ecc6fea9b41b1388c56f15ae760f3bb
diff --git a/tools/checkstyle.xml b/tools/checkstyle.xml
new file mode 100644
index 0000000..a9f8cfe
--- /dev/null
+++ b/tools/checkstyle.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!--
+    This configuration file was written by the eclipse-cs plugin configuration editor
+-->
+<!--
+    Checkstyle-Configuration: Google Checks for Gerrit
+    Description:
+Checkstyle configuration based on the Google coding conventions (https://google-styleguide.googlecode.com/svn-history/r130/trunk/javaguide.html),
+edited to remove noisy warnings.
+-->
+<module name="Checker">
+  <property name="severity" value="warning"/>
+  <property name="charset" value="UTF-8"/>
+  <module name="TreeWalker">
+    <module name="OuterTypeFilename"/>
+    <module name="LineLength">
+      <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
+      <property name="max" value="100"/>
+    </module>
+    <module name="OneTopLevelClass"/>
+    <module name="NoLineWrap"/>
+    <module name="EmptyBlock">
+      <property name="option" value="TEXT"/>
+      <property name="tokens" value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
+    </module>
+    <module name="NeedBraces"/>
+    <module name="LeftCurly">
+      <property name="maxLineLength" value="100"/>
+    </module>
+    <module name="RightCurly"/>
+    <module name="RightCurly">
+      <property name="option" value="alone"/>
+      <property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
+    </module>
+    <module name="WhitespaceAround">
+      <property name="severity" value="ignore"/>
+      <property name="allowEmptyConstructors" value="true"/>
+      <property name="allowEmptyMethods" value="true"/>
+      <property name="allowEmptyTypes" value="true"/>
+      <property name="allowEmptyLoops" value="true"/>
+      <message key="ws.notFollowed" value="WhitespaceAround: ''{0}'' is not followed by whitespace."/>
+      <message key="ws.notPreceded" value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="OneStatementPerLine"/>
+    <module name="MultipleVariableDeclarations"/>
+    <module name="ArrayTypeStyle"/>
+    <module name="MissingSwitchDefault"/>
+    <module name="FallThrough"/>
+    <module name="UpperEll"/>
+    <module name="ModifierOrder"/>
+    <module name="EmptyLineSeparator">
+      <property name="severity" value="ignore"/>
+      <property name="allowNoEmptyLineBetweenFields" value="true"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="SeparatorWrap">
+      <property name="severity" value="ignore"/>
+      <property name="option" value="nl"/>
+      <property name="tokens" value="DOT"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="SeparatorWrap">
+      <property name="severity" value="ignore"/>
+      <property name="option" value="EOL"/>
+      <property name="tokens" value="COMMA"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="NoFinalizer"/>
+    <module name="GenericWhitespace">
+      <property name="severity" value="ignore"/>
+      <message key="ws.followed" value="GenericWhitespace ''{0}'' is followed by whitespace."/>
+      <message key="ws.illegalFollow" value="GenericWhitespace ''{0}'' should followed by whitespace."/>
+      <message key="ws.preceded" value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
+      <message key="ws.notPreceded" value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="Indentation">
+      <property name="severity" value="ignore"/>
+      <property name="basicOffset" value="2"/>
+      <property name="caseIndent" value="2"/>
+      <property name="arrayInitIndent" value="2"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="MethodParamPad">
+      <property name="severity" value="ignore"/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+    <module name="OperatorWrap">
+      <property name="severity" value="ignore"/>
+      <property name="option" value="NL"/>
+      <property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR "/>
+      <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+    </module>
+  </module>
+  <module name="FileTabCharacter">
+    <property name="severity" value="ignore"/>
+    <property name="eachLine" value="true"/>
+    <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+  </module>
+</module>