Merge "Make the --other-branches configurable"
diff --git a/.gitignore b/.gitignore
index 9cfedb7..fec4747 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@
 *.pyc
 /gwt-unitCache
 *.swp
+*.asc
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt
index dd372fe..facd133 100644
--- a/Documentation/cmd-show-caches.txt
+++ b/Documentation/cmd-show-caches.txt
@@ -37,34 +37,42 @@
 == EXAMPLES
 
 ====
-	$ ssh -p 29418 review.example.com gerrit show-caches
-	Gerrit Code Review        2.2.2                     now    10:03:34   PDT
-	                                                 uptime     1 min 39 sec
-	
-	  Name               Max |Object Count        |  AvgGet  |Hit Ratio     |
-	                     Age |  Disk    Mem    Cnt|          |Disk Mem  Agg |
-	-------------------------+--------------------+----------+--------------+
-	  accounts           90d |                   1|          |           95%|
-	  accounts_byemail   90d |                    |          |              |
-	  accounts_byname    90d |                   1|          |              |
-	  adv_bases          10m |                    |          |              |
-	D diff               90d |     8             8|          |              |
-	D diff_intraline     90d |     1             1|          |              |
-	  groups             90d |                  19|          |            0%|
-	  groups_byext       90d |                    |          |              |
-	  groups_byinclude   90d |                  21|          |           80%|
-	  groups_byname      90d |                    |          |              |
-	  groups_byuuid      90d |                    |          |              |
-	  project_list       90d |                    |          |              |
-	  projects           90d |                   1|          |           80%|
-	  sshkeys            90d |                   1|          |           90%|
-	D web_sessions       12h |                    |          |              |
-	
-	SSH:      1  users, oldest session started 782 ms ago
-	Tasks:    2  total =    1 running +      0 ready +    1 sleeping
-	Mem:  46.13m total =  16.17m used +  29.96m free +   0.00k buffers
-	     246.56m max
-	           0 open files,        6 cpus available,       23 threads
+  $ ssh -p 29418 review.example.com gerrit show-caches
+  Gerrit Code Review        2.9                       now   11:14:13   CEST
+                                                   uptime    6 days 20 hrs
+    Name                          |Entries              |  AvgGet |Hit Ratio|
+                                  |   Mem   Disk   Space|         |Mem  Disk|
+  --------------------------------+---------------------+---------+---------+
+    accounts                      |  4096               |   3.4ms | 99%     |
+    accounts_byemail              |  1024               |   7.6ms | 98%     |
+    accounts_byname               |  4096               |  11.3ms | 99%     |
+    adv_bases                     |                     |         |         |
+    changes                       |                     |  27.1ms |  0%     |
+    groups                        |  5646               |  11.8ms | 97%     |
+    groups_byinclude              |   230               |   2.4ms | 62%     |
+    groups_byname                 |                     |         |         |
+    groups_byuuid                 |  5612               |  29.2ms | 99%     |
+    groups_external               |     1               |   1.5s  | 98%     |
+    groups_members                |  5714               |  19.7ms | 99%     |
+    ldap_group_existence          |                     |         |         |
+    ldap_groups                   |   650               | 680.5ms | 99%     |
+    ldap_groups_byinclude         |  1024               |         | 83%     |
+    ldap_usernames                |   390               |   3.8ms | 81%     |
+    permission_sort               | 16384               |         | 99%     |
+    plugin_resources              |                     |         |         |
+    project_list                  |     1               |   3.8s  | 99%     |
+    projects                      |  6477               |   2.9ms | 99%     |
+    sshkeys                       |  2048               |  12.5ms | 99%     |
+  D diff                          |  1299  62033 132.36m|  22.0ms | 85%  99%|
+  D diff_intraline                | 12777 218651 128.45m| 171.1ms | 31%  96%|
+  D git_tags                      |     3      6  11.85k|         |  0% 100%|
+  D web_sessions                  |  1024 151714  59.10m|         | 99%  57%|
+
+  SSH:    385  users, oldest session started    6 days 20 hrs ago
+  Tasks:   10  total =    6 running +      0 ready +    4 sleeping
+  Mem:  14.94g total =   3.04g used +  11.89g free +  10.00m buffers
+        28.44g max
+           107 open files,        4 cpus available,      371 threads
 ====
 
 == SEE ALSO
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 49fe77f..2f551f2 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -391,23 +391,6 @@
 specific config option in the `project.config` must be set:
 link:config-project-config.html[receive.requireContributorAgreement].
 
-auth.allowGoogleAccountUpgrade::
-+
-Allows Google Account users to automatically update their Gerrit
-account when/if their Google Account OpenID identity token changes.
-Identity tokens can change if the server changes hostnames, or
-for other reasons known only to Google.  The upgrade path works
-by matching users by email address if the identity is not present,
-and then changing the identity.
-+
-This setting also permits old Gerrit 1.x users to seamlessly upgrade
-from Google Accounts on Google App Engine to OpenID authentication.
-+
-Having this enabled incurs an extra database query when Google
-Account users register with the Gerrit server.
-+
-By default, unset/false.
-
 [[auth.trustContainerAuth]]auth.trustContainerAuth::
 +
 If true then it is the responsibility of the container hosting
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 4635f80..8d58d36 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -41,9 +41,18 @@
 changes and drafts).
 
 ====
-  patchset-created --change <change id> --is-draft <boolean> --change-url <change url> --change-owner <change owner> --project <project name> --branch <branch> --topic <topic> --uploader <uploader> --commit <sha1> --patchset <patchset id>
+  patchset-created --change <change id> --is-draft <boolean> --kind <change kind> --change-url <change url> --change-owner <change owner> --project <project name> --branch <branch> --topic <topic> --uploader <uploader> --commit <sha1> --patchset <patchset id>
 ====
 
+kind:: change kind represents the kind of change uploaded, also represented in link:json.html#patchSet[patchSet]
+
+  REWORK;; Nontrivial content changes.
+
+  TRIVIAL_REBASE;; Conflict-free merge between the new parent and the prior patch set.
+
+  NO_CODE_CHANGE;; No code changed; same tree and same parents.
+
+
 === draft-published
 
 This is called whenever a draft change is published.
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 2bcf5c2..2674398 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -135,34 +135,12 @@
   buck build api_install
 ----
 
-Deploy {extension,plugin,gwt}-api to the remote maven repository:
-
-----
-  buck build api_deploy
-----
-
 Install gerrit.war to the local maven repository:
 
 ----
   buck build war_install
 ----
 
-Deploy gerrit.war to the remote maven repository:
-
-----
-  buck build war_deploy
-----
-
-The type of the repo is induced from the Gerrit version name, i.e.
-
-* `2.9-SNAPSHOT`: snapshot repo
-* `2.9`: release repo
-
-Deploying to the remote repository still depends on Maven, and the credentials
-for the repository need to be
-link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
-configured in Maven's settings.xml file].
-
 === Plugins
 
 To build all core plugins:
@@ -475,17 +453,52 @@
 directories and they will not interfere with each other. Buck's documentation
 covers daemon in http://facebook.github.io/buck/command/buckd.html[buckd].
 
-To use `buckd` the additional watchman program must be installed, see
-link:https://github.com/facebook/watchman[watchman] for details.
+To use `buckd` the additional
+link:https://facebook.github.io/watchman[watchman] program must be installed.
+
+[[watchman]]
+=== Installing watchman
+
+Watchman is used internally by Buck to monitor directory trees and is needed
+for buck daemon to work properly. Because buckd is activated by default in the
+latest version of Buck, it searches for the watchman executable in the
+path and issues a warning when it is not found and kills buckd.
+
+To prepare watchman installation on Linux:
 
 ----
-  buckd
-  buck build gerrit
-  Using buckd.
-  [-] PARSING BUILD FILES...FINISHED 0.6s
-  [-] BUILDING...FINISHED 0.2s
+  git clone https://github.com/facebook/watchman.git
+  cd watchman
+  ./autogen.sh
 ----
 
+To install it in user home directory (without root privileges):
+
+----
+  ./configure --prefix $HOME/watchman
+  make install
+----
+
+To install it system wide:
+
+----
+  ./configure
+  make
+  sudo make install
+----
+
+Put $HOME/watchman/bin/watchman in path or link to $HOME/bin/watchman.
+
+To install watchman on OS X:
+
+----
+  brew install --HEAD watchman
+----
+
+See the original documentation for more information:
+link:https://facebook.github.io/watchman/docs/install.html[Watchman
+installation].
+
 === Override Buck's settings
 
 Additional JVM args for Buck can be set in `.buckjavaargs` in the
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt
index 4a4e894..83c28da 100644
--- a/Documentation/dev-release-deploy-config.txt
+++ b/Documentation/dev-release-deploy-config.txt
@@ -1,6 +1,48 @@
 = Deploy Gerrit Artifacts
 
-Gerrit Artifacts are stored on
+[[deploy-configuration-setting-maven-central]]
+== Deploy Configuration settings for Maven Central
+
+Gerrit Extension API Jar and the Gerrit Plugin API artifacts are stored on
+Maven Central.
+
+Prerequisites: Set up the Sonatype account (see link:https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide#SonatypeOSSMavenRepositoryUsageGuide-2.Signup[Sonatype
+Maven Repository Usage Guide]) and put Sonatype user and password in
+~/.m2/settings.xml:
+
+----
+<server>
+  <id>sonatype-nexus-staging</id>
+  <username>USER</username>
+  <password>PASSWORD</password>
+</server>
+----
+
+PGP key must be generated and published, see
+link:https://docs.sonatype.org/display/Repository/How+To+Generate+PGP+Signatures+With+Maven[How
+To Generate PGP Signatures With Maven] for details. PGP passphrase can also be
+put in ~/.m2/settings.xml:
+
+----
+<settings>
+  <profiles>
+    <profile>
+      <id>gpg</id>
+      <properties>
+        <gpg.executable>gpg2</gpg.executable>
+        <gpg.passphrase>mypassphrase</gpg.passphrase>
+      </properties>
+    </profile>
+  </profiles>
+  <activeProfiles>
+    <activeProfile>gpg</activeProfile>
+  </activeProfiles>
+</settings>
+----
+
+It can also be included in the key chain on OS X.
+
+Gerrit Subproject Artifacts are stored on
 link:https://developers.google.com/storage/[Google Cloud Storage].
 Via the link:https://code.google.com/apis/console/?noredirect[API Console] the
 Gerrit maintainers have access to the `Gerrit Code Review` project.
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 1af54c7..2495233 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -141,29 +141,32 @@
 ==== Publish the Extension and Plugin API Jars
 
 * Make sure you have done the
-link:dev-release-deploy-config.html#deploy-configuration-settings-xml[
+link:dev-release-deploy-config.html#deploy-configuration-setting-maven-central[
 configuration needed for deployment]
 
-* Push the Jars to the storage bucket:
+* Push the Jars to the Maven Central:
 +
 ----
   buck build api_deploy
 ----
 
+* Troubleshooting: to see the commands that are execured in Buck build process
+environment variable VERBOSE can be set:
++
+----
+  VERBOSE=1 buck build api_deploy
+----
 
 [[publish-gerrit-war]]
 ==== Publish the Gerrit WAR (with Core Plugins)
 
-* The WAR file to upload is `buck-out/gen/release.war`
-* Upload WAR to the storage bucket via `https://cloud.google.com/console` (manual via web browser)
-* Push the WAR file to the Maven storage bucket:
+* Push the WAR to the Maven Central:
 +
 ----
   buck build war_deploy
 ----
 
 
-
 [[push-stable]]
 ==== Push the Stable Branch
 
@@ -293,6 +296,17 @@
 for review and get it merged.
 
 
+[[publish-plugins-archetypes-to-maven-central]]
+=== Publish plugin archetypes to Maven Central
+
+Make sure you have done the
+link:dev-release-deploy-config.html#deploy-configuration-setting-maven-central[
+configuration needed for deployment]
+
+----
+  ./tools/plugin_archetype_deploy.sh
+----
+
 [[merge-stable]]
 === Merge `stable` into `master`
 
diff --git a/Documentation/json.txt b/Documentation/json.txt
index 680705c6..3624b3f 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -115,6 +115,14 @@
 
 isDraft:: Whether or not the patch set is a draft patch set.
 
+kind:: Kind of change uploaded.
+
+  REWORK;; Nontrivial content changes.
+
+  TRIVIAL_REBASE;; Conflict-free merge between the new parent and the prior patch set.
+
+  NO_CODE_CHANGE;; No code changed; same tree and same parents.
+
 approvals:: The <<approval,approval attribute>> granted.
 
 comments:: All comments for this patchset in <<patchsetcomment,patchsetComment attributes>>.
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index f7d4aa5..00b957e 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -1031,6 +1031,11 @@
 +
 Controls whether line numbers are shown.
 
+- `Empty Pane`:
++
+Controls whether empty panes are shown or not. The Left pane is empty when a
+file was added; the right pane is empty when a file was deleted.
+
 - `Left Side`:
 +
 Controls whether the left side is shown. This preference is not
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 9c2a4c5..eaf0f6e 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -314,6 +314,18 @@
 +
 Change has been abandoned.
 
+[[size]]
+added:'RELATION''LINES', deleted:'RELATION''LINES', delta/size:'RELATION''LINES'::
++
+True if the number of lines added/deleted/changed satisfies the given relation
+for the given number of lines.
++
+For example, added:>50 will be true for any change which adds at least 50
+lines.
++
+Valid relations are >=, >, <=, <, or no relation, which will match if the
+number of lines is exactly equal.
+
 
 == Argument Quoting
 
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 9b24922..a10e026 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
@@ -15,11 +15,14 @@
 package com.google.gerrit.acceptance.api.project;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 
@@ -27,6 +30,7 @@
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.List;
 
 @NoHttpd
 public class ProjectIT extends AbstractDaemonTest  {
@@ -71,4 +75,45 @@
         .branch("foo")
         .create(new BranchInput());
   }
+
+  @Test
+  public void listProjects() throws Exception {
+    List<ProjectInfo> initialProjects = gApi.projects().list().get();
+
+    gApi.projects().name("foo").create();
+    gApi.projects().name("bar").create();
+
+    List<ProjectInfo> allProjects = gApi.projects().list().get();
+    assertEquals(initialProjects.size() + 2, allProjects.size());
+
+    List<ProjectInfo> projectsWithDescription = gApi.projects().list()
+        .withDescription(true)
+        .get();
+    assertNotNull(projectsWithDescription.get(0).description);
+
+    List<ProjectInfo> projectsWithoutDescription = gApi.projects().list()
+        .withDescription(false)
+        .get();
+    assertNull(projectsWithoutDescription.get(0).description);
+
+    List<ProjectInfo> noMatchingProjects = gApi.projects().list()
+        .withPrefix("fox")
+        .get();
+    assertEquals(0, noMatchingProjects.size());
+
+    List<ProjectInfo> matchingProject = gApi.projects().list()
+        .withPrefix("fo")
+        .get();
+    assertEquals(1, matchingProject.size());
+
+    List<ProjectInfo> limitOneProject = gApi.projects().list()
+        .withLimit(1)
+        .get();
+    assertEquals(1, limitOneProject.size());
+
+    List<ProjectInfo> startAtOneProjects = gApi.projects().list()
+        .withStart(1)
+        .get();
+    assertEquals(allProjects.size() - 1, startAtOneProjects.size());
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index 10220e5..e92e613 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -119,6 +119,11 @@
     suggestions.add("status:merged");
     suggestions.add("status:abandoned");
 
+    suggestions.add("added:");
+    suggestions.add("deleted:");
+    suggestions.add("delta:");
+    suggestions.add("size:");
+
     suggestions.add("AND");
     suggestions.add("OR");
     suggestions.add("NOT");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
index 5c66f3c..8a4666b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/DiffPreferences.java
@@ -40,6 +40,7 @@
     p.manualReview(in.isManualReview());
     p.renderEntireFile(in.isRenderEntireFile());
     p.theme(in.getTheme());
+    p.hideEmptyPane(in.isHideEmptyPane());
     return p;
   }
 
@@ -59,6 +60,7 @@
     p.setManualReview(manualReview());
     p.setRenderEntireFile(renderEntireFile());
     p.setTheme(theme());
+    p.setHideEmptyPane(hideEmptyPane());
   }
 
   public final void ignoreWhitespace(Whitespace i) {
@@ -84,6 +86,7 @@
   public final native void expandAllComments(boolean e) /*-{ this.expand_all_comments = e }-*/;
   public final native void manualReview(boolean r) /*-{ this.manual_review = r }-*/;
   public final native void renderEntireFile(boolean r) /*-{ this.render_entire_file = r }-*/;
+  public final native void hideEmptyPane(boolean s) /*-{ this.hide_empty_pane = s }-*/;
   public final void showLineNumbers(boolean s) { hideLineNumbers(!s); }
 
   public final Whitespace ignoreWhitespace() {
@@ -111,6 +114,7 @@
   public final native boolean expandAllComments() /*-{ return this.expand_all_comments || false }-*/;
   public final native boolean manualReview() /*-{ return this.manual_review || false }-*/;
   public final native boolean renderEntireFile() /*-{ return this.render_entire_file || false }-*/;
+  public final native boolean hideEmptyPane() /*-{ return this.hide_empty_pane || false }-*/;
   public final boolean showLineNumbers() { return !hideLineNumbers(); }
   public final boolean autoReview() { return !manualReview(); }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
index 4703ef7..a84ca99 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
@@ -249,9 +249,6 @@
       } else if (k.isScheme(OpenIdUrls.URL_YAHOO)) {
         return OpenIdUtil.C.nameYahoo();
 
-      } else if (k.isScheme(AccountExternalId.LEGACY_GAE)) {
-        return OpenIdUtil.C.nameGoogle() + " (Imported from Google AppEngine)";
-
       } else {
         return k.getExternalId();
       }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
index 162c19e..7b56a13 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/CommitBox.java
@@ -46,7 +46,6 @@
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.ScrollPanel;
-import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwtexpui.clippy.client.CopyableLabel;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
index 2a9f7ba..d56654f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -14,7 +14,9 @@
 
 package com.google.gerrit.client.diff;
 
+import com.google.gerrit.client.account.DiffPreferences;
 import com.google.gerrit.client.changes.ChangeInfo.RevisionInfo;
+import com.google.gerrit.reviewdb.client.Patch.ChangeType;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
@@ -52,6 +54,7 @@
     String showTabs();
     String showLineNumbers();
     String hideA();
+    String hideB();
     String columnMargin();
     String padding();
   }
@@ -77,6 +80,7 @@
   private boolean header;
   private boolean headerVisible;
   private boolean visibleA;
+  private ChangeType changeType;
 
   DiffTable(SideBySide2 parent, PatchSet.Id base, PatchSet.Id revision,
       String path) {
@@ -115,6 +119,15 @@
     };
   }
 
+  void setVisibleB(boolean show) {
+    if (show) {
+      removeStyleName(style.hideB());
+      parent.syncScroll(DisplaySide.A); // match A's viewport
+    } else {
+      addStyleName(style.hideB());
+    }
+  }
+
   boolean isHeaderVisible() {
     return headerVisible;
   }
@@ -139,7 +152,12 @@
     return h;
   }
 
-  void set(JsArray<RevisionInfo> list, DiffInfo info) {
+  ChangeType getChangeType() {
+    return changeType;
+  }
+
+  void set(DiffPreferences prefs, JsArray<RevisionInfo> list, DiffInfo info) {
+    this.changeType = info.change_type();
     patchSetSelectBoxA.setUpPatchSetNav(list, info.meta_a());
     patchSetSelectBoxB.setUpPatchSetNav(list, info.meta_b());
 
@@ -165,6 +183,15 @@
       header = false;
       UIObject.setVisible(diffHeaderRow, false);
     }
+    setHideEmptyPane(prefs.hideEmptyPane());
+  }
+
+  void setHideEmptyPane(boolean hide) {
+    if (changeType == ChangeType.ADDED) {
+      setVisibleA(!hide);
+    } else if (changeType == ChangeType.DELETED) {
+      setVisibleB(!hide);
+    }
   }
 
   void refresh() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
index 01e47b8..ead90ca 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
@@ -29,13 +29,15 @@
       border-bottom: 1px solid #ddd;
     }
 
-    .difftable {
+    .difftable .patchSetNav,
+    .difftable .CodeMirror {
       -webkit-touch-callout: none;
       -webkit-user-select: none;
       -khtml-user-select: none;
       -moz-user-select: none;
       -ms-user-select: none;
     }
+
     .difftable .CodeMirror-lines { padding: 0; }
     .difftable .CodeMirror pre {
       padding: 0;
@@ -57,6 +59,11 @@
       display: none;
     }
 
+    .hideB .psNavB,
+    .hideB .b {
+      display: none;
+    }
+
     .table {
       width: 100%;
       table-layout: fixed;
@@ -65,6 +72,7 @@
     .table td { padding: 0 }
     .a, .b { width: 50% }
     .hideA .psNavB, .hideA .b { width: 100% }
+    .hideB .psNavA, .hideB .a { width: 100% }
 
     .overview {
       width: 10px;
@@ -93,7 +101,7 @@
     .dark .noIntraline .a .intralineBg { background-color: #400; }
     .dark .noIntraline .b .intralineBg { background-color: #444; }
 
-    .patchSetNav {
+    .patchSetNav, .diff_header {
       background-color: #f7f7f7;
       line-height: 1;
     }
@@ -158,12 +166,15 @@
       z-index: 2;
       cursor: text;
     }
+
     .diff_header {
-      margin: 0 0 3px 0;
       font-size: 12px;
       font-weight: bold;
       color: #5252ad;
     }
+    .diff_header pre {
+      margin: 0 0 3px 0;
+    }
   </ui:style>
   <g:HTMLPanel styleName='{style.difftable}'>
     <table class='{style.table}'>
@@ -176,10 +187,8 @@
         </td>
         <td class='{style.overview}' />
       </tr>
-      <tr ui:field='diffHeaderRow' class='{style.patchSetNav}'>
-        <td colspan='2'><pre
-            ui:field='diffHeaderText'
-            class='{style.diff_header}' /></td>
+      <tr ui:field='diffHeaderRow' class='{style.diff_header}'>
+        <td colspan='2'><pre ui:field='diffHeaderText' /></td>
         <td class='{style.overview}' />
       </tr>
       <tr>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
index 5021758..ac11cd5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Theme;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.client.Patch.ChangeType;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.RepeatingCommand;
@@ -87,6 +88,7 @@
   @UiField ToggleButton showTabs;
   @UiField ToggleButton lineNumbers;
   @UiField ToggleButton leftSide;
+  @UiField ToggleButton emptyPane;
   @UiField ToggleButton topMenu;
   @UiField ToggleButton manualReview;
   @UiField ToggleButton expandAllComments;
@@ -151,6 +153,9 @@
     showTabs.setValue(prefs.showTabs());
     lineNumbers.setValue(prefs.showLineNumbers());
     leftSide.setValue(view.diffTable.isVisibleA());
+    emptyPane.setValue(!prefs.hideEmptyPane());
+    leftSide.setEnabled(!(prefs.hideEmptyPane()
+        && view.diffTable.getChangeType() == ChangeType.ADDED));
     topMenu.setValue(!prefs.hideTopMenu());
     manualReview.setValue(prefs.manualReview());
     expandAllComments.setValue(prefs.expandAllComments());
@@ -295,6 +300,21 @@
     view.diffTable.setVisibleA(e.getValue());
   }
 
+  @UiHandler("emptyPane")
+  void onHideEmptyPane(ValueChangeEvent<Boolean> e) {
+    prefs.hideEmptyPane(!e.getValue());
+    view.diffTable.setHideEmptyPane(prefs.hideEmptyPane());
+    if (prefs.hideEmptyPane()) {
+      if (view.diffTable.getChangeType() == ChangeType.ADDED) {
+        leftSide.setValue(false);
+        leftSide.setEnabled(false);
+      }
+    } else {
+      leftSide.setValue(view.diffTable.isVisibleA());
+      leftSide.setEnabled(true);
+    }
+  }
+
   @UiHandler("topMenu")
   void onTopMenu(ValueChangeEvent<Boolean> e) {
     prefs.hideTopMenu(!e.getValue());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
index 670c3e3..af53916 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PreferencesBox.ui.xml
@@ -229,6 +229,13 @@
         </g:ToggleButton></td>
       </tr>
       <tr>
+        <th><ui:msg>Empty Pane</ui:msg></th>
+        <td><g:ToggleButton ui:field='emptyPane'>
+          <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
         <th><ui:msg>Left Side</ui:msg></th>
         <td><g:ToggleButton ui:field='leftSide'>
           <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
index 4ed1cdd..2327046 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
@@ -202,7 +202,7 @@
         info.revisions().copyKeysIntoChildren("name");
         JsArray<RevisionInfo> list = info.revisions().values();
         RevisionInfo.sortRevisionInfoByNumber(list);
-        diffTable.set(list, diff);
+        diffTable.set(prefs, list, diff);
         header.setChangeInfo(info);
       }}));
 
@@ -884,7 +884,9 @@
   }
 
   void syncScroll(DisplaySide masterSide) {
-    scrollSynchronizer.syncScroll(masterSide);
+    if (scrollSynchronizer != null) {
+      scrollSynchronizer.syncScroll(masterSide);
+    }
   }
 
   private String getContentType(DiffInfo.FileMeta meta) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java
index 0db7a88..72e238d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/SmallResource.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.httpd.plugins;
 
+import com.google.common.net.HttpHeaders;
 import com.google.gerrit.common.Nullable;
 
 import java.io.IOException;
@@ -55,7 +56,13 @@
   void send(HttpServletRequest req, HttpServletResponse res)
       throws IOException {
     if (0 < lastModified) {
-      res.setDateHeader("Last-Modified", lastModified);
+      long ifModifiedSince = req.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
+      if (ifModifiedSince > 0 && ifModifiedSince == lastModified) {
+        res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+        return;
+      } else {
+        res.setDateHeader("Last-Modified", lastModified);
+      }
     }
     res.setContentType(contentType);
     if (characterEncoding != null) {
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index c790740..64c7ed5 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -72,6 +72,7 @@
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
+import org.apache.lucene.index.IndexableField;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
@@ -113,9 +114,14 @@
 
   public static final String CHANGES_OPEN = "open";
   public static final String CHANGES_CLOSED = "closed";
-  private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
-  private static final String CHANGE_FIELD = ChangeField.CHANGE.getName();
+
+  private static final String ADDED_FIELD = ChangeField.ADDED.getName();
   private static final String APPROVAL_FIELD = ChangeField.APPROVAL.getName();
+  private static final String CHANGE_FIELD = ChangeField.CHANGE.getName();
+  private static final String DELETED_FIELD = ChangeField.DELETED.getName();
+  private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
+  private static final ImmutableSet<String> FIELDS = ImmutableSet.of(
+      ADDED_FIELD, APPROVAL_FIELD, CHANGE_FIELD, DELETED_FIELD, ID_FIELD);
 
   private static final Map<Schema<ChangeData>, Version> LUCENE_VERSIONS;
   static {
@@ -127,6 +133,8 @@
     Version lucene44 = Version.LUCENE_44;
     @SuppressWarnings("deprecation")
     Version lucene46 = Version.LUCENE_46;
+    @SuppressWarnings("deprecation")
+    Version lucene47 = Version.LUCENE_47;
     for (Map.Entry<Integer, Schema<ChangeData>> e
         : ChangeSchemas.ALL.entrySet()) {
       if (e.getKey() <= 3) {
@@ -135,8 +143,10 @@
         versions.put(e.getValue(), lucene44);
       } else if (e.getKey() <= 8) {
         versions.put(e.getValue(), lucene46);
+      } else if (e.getKey() <= 10) {
+        versions.put(e.getValue(), lucene47);
       } else {
-        versions.put(e.getValue(), Version.LUCENE_47);
+        versions.put(e.getValue(), Version.LUCENE_48);
       }
     }
     LUCENE_VERSIONS = versions.build();
@@ -348,9 +358,6 @@
     setReady(sitePaths, schema.getVersion(), ready);
   }
 
-  private static final ImmutableSet<String> FIELDS =
-      ImmutableSet.of(ID_FIELD, CHANGE_FIELD, APPROVAL_FIELD);
-
   @SuppressWarnings("deprecation")
   private static Sort getSort(Schema<ChangeData> schema,
       Predicate<ChangeData> p) {
@@ -460,10 +467,12 @@
       return changeDataFactory.create(db.get(), new Change.Id(id));
     }
 
+    // Change proto.
     Change change = ChangeProtoField.CODEC.decode(
         cb.bytes, cb.offset, cb.length);
     ChangeData cd = changeDataFactory.create(db.get(), change);
 
+    // Approvals.
     BytesRef[] approvalsBytes = doc.getBinaryValues(APPROVAL_FIELD);
     if (approvalsBytes != null) {
       List<PatchSetApproval> approvals =
@@ -474,6 +483,16 @@
       }
       cd.setCurrentApprovals(approvals);
     }
+
+    // Changed lines.
+    IndexableField added = doc.getField(ADDED_FIELD);
+    IndexableField deleted = doc.getField(DELETED_FIELD);
+    if (added != null && deleted != null) {
+      cd.setChangedLines(
+          added.numericValue().intValue(),
+          deleted.numericValue().intValue());
+    }
+
     return cd;
   }
 
@@ -497,7 +516,7 @@
     FieldType<?> type = values.getField().getType();
     Store store = store(values.getField());
 
-    if (type == FieldType.INTEGER) {
+    if (type == FieldType.INTEGER || type == FieldType.INTEGER_RANGE) {
       for (Object value : values.getValues()) {
         doc.add(new IntField(name, (Integer) value, store));
       }
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
index 3221b8a..dfe3f2d 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.FieldType;
 import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.IntegerRangePredicate;
 import com.google.gerrit.server.index.RegexPredicate;
 import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.TimestampRangePredicate;
@@ -135,6 +136,8 @@
       throws QueryParseException {
     if (p.getType() == FieldType.INTEGER) {
       return intQuery(p);
+    } else if (p.getType() == FieldType.INTEGER_RANGE) {
+      return intRangeQuery(p);
     } else if (p.getType() == FieldType.TIMESTAMP) {
       return timestampQuery(p);
     } else if (p.getType() == FieldType.EXACT) {
@@ -169,6 +172,28 @@
     return new TermQuery(intTerm(p.getField().getName(), value));
   }
 
+  private Query intRangeQuery(IndexPredicate<ChangeData> p)
+      throws QueryParseException {
+    if (p instanceof IntegerRangePredicate) {
+      IntegerRangePredicate<ChangeData> r =
+          (IntegerRangePredicate<ChangeData>) p;
+      int minimum = r.getMinimumValue();
+      int maximum = r.getMaximumValue();
+      if (minimum == maximum) {
+        // Just fall back to a standard integer query.
+        return new TermQuery(intTerm(p.getField().getName(), minimum));
+      } else {
+        return NumericRangeQuery.newIntRange(
+            r.getField().getName(),
+            minimum,
+            maximum,
+            true,
+            true);
+      }
+    }
+    throw new QueryParseException("not an integer range: " + p);
+  }
+
   private Query sortKeyQuery(SortKeyPredicate p) {
     long min = p.getMinValue(schema);
     long max = p.getMaxValue(schema);
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index 3fa83f4..b443c4d 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -25,7 +25,7 @@
       <defaultValue>http://code.google.com/p/gerrit/</defaultValue>
     </requiredProperty>
     <requiredProperty key="Gwt-Version">
-      <defaultValue>2.6.0</defaultValue>
+      <defaultValue>2.6.1</defaultValue>
     </requiredProperty>
 
     <requiredProperty key="gerritApiVersion">
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java
index a19ae08..cf951c1 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountDiffPreference.java
@@ -91,6 +91,7 @@
     p.setShowTabs(true);
     p.setContext(DEFAULT_CONTEXT);
     p.setManualReview(false);
+    p.setHideEmptyPane(false);
     return p;
   }
 
@@ -152,6 +153,9 @@
   @Column(id = 19, length = 20, notNull = false)
   protected String theme;
 
+  @Column(id = 20)
+  protected boolean hideEmptyPane;
+
   protected AccountDiffPreference() {
   }
 
@@ -178,6 +182,7 @@
     this.hideTopMenu = p.hideTopMenu;
     this.hideLineNumbers = p.hideLineNumbers;
     this.renderEntireFile = p.renderEntireFile;
+    this.hideEmptyPane = p.hideEmptyPane;
   }
 
   public Account.Id getAccountId() {
@@ -330,4 +335,12 @@
   public void setTheme(Theme theme) {
     this.theme = theme != null ? theme.name() : null;
   }
+
+  public boolean isHideEmptyPane() {
+    return hideEmptyPane;
+  }
+
+  public void setHideEmptyPane(boolean hideEmptyPane) {
+    this.hideEmptyPane = hideEmptyPane;
+  }
 }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
index 4be5113..8181d50 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountExternalId.java
@@ -36,9 +36,6 @@
   /** Scheme for the username used to authenticate an account, e.g. over SSH. */
   public static final String SCHEME_USERNAME = "username:";
 
-  /** Very old scheme from Gerrit Code Review 1.x imports. */
-  public static final String LEGACY_GAE = "Google Account ";
-
   public static class Key extends StringKey<com.google.gwtorm.client.Key<?>> {
     private static final long serialVersionUID = 1L;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 1819272..257799a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.change.ChangeKindCache;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
@@ -209,6 +210,8 @@
 
     private final AccountCache accountCache;
 
+    private final ChangeKindCache changeKindCache;
+
     private final EventFactory eventFactory;
 
     private final SitePaths sitePaths;
@@ -236,6 +239,7 @@
       final SitePaths sitePath,
       final ProjectCache projectCache,
       final AccountCache accountCache,
+      final ChangeKindCache changeKindCache,
       final EventFactory eventFactory,
       final SitePaths sitePaths,
       final DynamicSet<ChangeListener> unrestrictedListeners) {
@@ -244,6 +248,7 @@
         this.hookQueue = queue.createQueue(1, "hook");
         this.projectCache = projectCache;
         this.accountCache = accountCache;
+        this.changeKindCache = changeKindCache;
         this.eventFactory = eventFactory;
         this.sitePaths = sitePath;
         this.unrestrictedListeners = unrestrictedListeners;
@@ -354,11 +359,13 @@
         event.change = eventFactory.asChangeAttribute(change);
         event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
         event.uploader = eventFactory.asAccountAttribute(uploader.getAccount());
+        event.kind = changeKindCache.getChangeKind(db, change, patchSet);
         fireEvent(change, event, db);
 
         final List<String> args = new ArrayList<>();
         addArg(args, "--change", event.change.id);
-        addArg(args, "--is-draft", patchSet.isDraft() ? "true" : "false");
+        addArg(args, "--is-draft", String.valueOf(patchSet.isDraft()));
+        addArg(args, "--kind", String.valueOf(event.kind));
         addArg(args, "--change-url", event.change.url);
         addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
         addArg(args, "--project", event.change.project);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 3ba3076..77ebe0f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.gerrit.common.auth.openid.OpenIdUrls;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.Permission;
@@ -27,7 +26,6 @@
 import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
@@ -38,9 +36,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /** Tracks authentication related details for user accounts. */
@@ -52,7 +48,6 @@
   private final SchemaFactory<ReviewDb> schema;
   private final AccountCache byIdCache;
   private final AccountByEmailCache byEmailCache;
-  private final AuthConfig authConfig;
   private final Realm realm;
   private final IdentifiedUser.GenericFactory userFactory;
   private final ChangeUserName.Factory changeUserNameFactory;
@@ -62,14 +57,13 @@
   @Inject
   AccountManager(final SchemaFactory<ReviewDb> schema,
       final AccountCache byIdCache, final AccountByEmailCache byEmailCache,
-      final AuthConfig authConfig, final Realm accountMapper,
+      final Realm accountMapper,
       final IdentifiedUser.GenericFactory userFactory,
       final ChangeUserName.Factory changeUserNameFactory,
       final ProjectCache projectCache) throws OrmException {
     this.schema = schema;
     this.byIdCache = byIdCache;
     this.byEmailCache = byEmailCache;
-    this.authConfig = authConfig;
     this.realm = accountMapper;
     this.userFactory = userFactory;
     this.changeUserNameFactory = changeUserNameFactory;
@@ -197,67 +191,6 @@
 
   private AuthResult create(final ReviewDb db, final AuthRequest who)
       throws OrmException, AccountException {
-    if (authConfig.isAllowGoogleAccountUpgrade()
-        && who.isScheme(OpenIdUrls.URL_GOOGLE + "?")
-        && who.getEmailAddress() != null) {
-      final List<AccountExternalId> openId = new ArrayList<>();
-      final List<AccountExternalId> v1 = new ArrayList<>();
-
-      for (final AccountExternalId extId : db.accountExternalIds()
-          .byEmailAddress(who.getEmailAddress())) {
-        if (extId.isScheme(OpenIdUrls.URL_GOOGLE + "?")) {
-          openId.add(extId);
-        } else if (extId.isScheme(AccountExternalId.LEGACY_GAE)) {
-          v1.add(extId);
-        }
-      }
-
-      if (!openId.isEmpty()) {
-        // The user has already registered with an OpenID from Google, but
-        // Google may have changed the user's OpenID identity if this server
-        // name has changed. Insert a new identity for the user.
-        //
-        final Account.Id accountId = openId.get(0).getAccountId();
-
-        if (openId.size() > 1) {
-          // Validate all matching identities are actually the same user.
-          //
-          for (final AccountExternalId extId : openId) {
-            if (!accountId.equals(extId.getAccountId())) {
-              throw new AccountException("Multiple user accounts for "
-                  + who.getEmailAddress() + " using Google Accounts provider");
-            }
-          }
-        }
-
-        final AccountExternalId newId = createId(accountId, who);
-        newId.setEmailAddress(who.getEmailAddress());
-
-        db.accountExternalIds().upsert(Collections.singleton(newId));
-        if (openId.size() == 1) {
-          final AccountExternalId oldId = openId.get(0);
-          db.accountExternalIds().delete(Collections.singleton(oldId));
-        }
-        return new AuthResult(accountId, newId.getKey(), false);
-
-      } else if (v1.size() == 1) {
-        // Exactly one user was imported from Gerrit 1.x with this email
-        // address. Upgrade their account by deleting the legacy import
-        // identity and creating a new identity matching the token we have.
-        //
-        final AccountExternalId oldId = v1.get(0);
-        final AccountExternalId newId = createId(oldId.getAccountId(), who);
-        newId.setEmailAddress(who.getEmailAddress());
-
-        db.accountExternalIds().upsert(Collections.singleton(newId));
-        db.accountExternalIds().delete(Collections.singleton(oldId));
-        return new AuthResult(newId.getAccountId(), newId.getKey(), false);
-
-      } else if (v1.size() > 1) {
-        throw new AccountException("Multiple Gerrit 1.x accounts found");
-      }
-    }
-
     final Account.Id newId = new Account.Id(db.nextAccountId());
     final Account account = new Account(newId, TimeUtil.nowTs());
     final AccountExternalId extId = createId(newId, who);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
index ccb0418..031788a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
@@ -72,6 +72,7 @@
       info.syntaxHighlighting = p.isSyntaxHighlighting() ? true : null;
       info.tabSize = p.getTabSize();
       info.renderEntireFile = p.isRenderEntireFile() ? true : null;
+      info.hideEmptyPane = p.isHideEmptyPane() ? true : null;
       info.theme = p.getTheme();
       return info;
     }
@@ -92,6 +93,7 @@
     public Boolean hideTopMenu;
     public Boolean hideLineNumbers;
     public Boolean renderEntireFile;
+    public Boolean hideEmptyPane;
     public int tabSize;
     public Theme theme;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
index 146f241..c056f63 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
@@ -50,6 +50,7 @@
     Boolean renderEntireFile;
     Integer tabSize;
     Theme theme;
+    Boolean hideEmptyPane;
   }
 
   private final Provider<CurrentUser> self;
@@ -136,6 +137,9 @@
       if (input.theme != null) {
         p.setTheme(input.theme);
       }
+      if (input.hideEmptyPane != null) {
+        p.setHideEmptyPane(input.hideEmptyPane);
+      }
 
       db.accountDiffPreferences().upsert(Collections.singleton(p));
       db.commit();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
index 71f2730..edb49fcc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
@@ -14,23 +14,32 @@
 
 package com.google.gerrit.server.api.projects;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.extensions.api.projects.ProjectApi;
 import com.google.gerrit.extensions.api.projects.Projects;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.server.project.ListProjects;
 import com.google.gerrit.server.project.ProjectsCollection;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import java.io.IOException;
+import java.util.List;
 
 class ProjectsImpl extends Projects.NotImplemented implements Projects {
   private final ProjectsCollection projects;
   private final ProjectApiImpl.Factory api;
+  private final Provider<ListProjects> listProvider;
 
   @Inject
-  ProjectsImpl(ProjectsCollection projects, ProjectApiImpl.Factory api) {
+  ProjectsImpl(ProjectsCollection projects,
+      ProjectApiImpl.Factory api,
+      Provider<ListProjects> listProvider) {
     this.projects = projects;
     this.api = api;
+    this.listProvider = listProvider;
   }
 
   @Override
@@ -43,4 +52,24 @@
       throw new RestApiException("Cannot retrieve project");
     }
   }
+
+  @Override
+  public ListRequest list() {
+    return new ListRequest() {
+      @Override
+      public List<ProjectInfo> get() throws RestApiException {
+        return list(this);
+      }
+    };
+  }
+
+  private List<ProjectInfo> list(ListRequest request) throws RestApiException {
+    ListProjects lp = listProvider.get();
+    lp.setShowDescription(request.getDescription());
+    lp.setLimit(request.getLimit());
+    lp.setStart(request.getStart());
+    lp.setMatchPrefix(request.getPrefix());
+
+    return ImmutableList.copyOf(lp.apply().values());
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index 33d161c..b083850 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -36,6 +36,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -43,6 +44,7 @@
 import java.io.IOException;
 import java.util.Collections;
 
+@Singleton
 public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
     UiAction<ChangeResource> {
   private static final Logger log = LoggerFactory.getLogger(Abandon.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
index 0e0984d..7b55b63 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.server.change;
 
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.project.ProjectState;
 
 import org.eclipse.jgit.lib.ObjectId;
@@ -28,4 +31,6 @@
 public interface ChangeKindCache {
   public ChangeKind getChangeKind(ProjectState project, Repository repo,
       ObjectId prior, ObjectId next);
+
+  public ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch);
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index 220dcb6..8e2b6ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -23,10 +23,17 @@
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.cache.Weigher;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Module;
 import com.google.inject.Singleton;
@@ -45,6 +52,7 @@
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.util.Collection;
 import java.util.concurrent.ExecutionException;
 
 public class ChangeKindCacheImpl implements ChangeKindCache {
@@ -69,11 +77,21 @@
   @VisibleForTesting
   public static class NoCache implements ChangeKindCache {
     private final boolean useRecursiveMerge;
+    private final ChangeData.Factory changeDataFactory;
+    private final ProjectCache projectCache;
+    private final GitRepositoryManager repoManager;
+
 
     @Inject
     NoCache(
-        @GerritServerConfig Config serverConfig) {
+        @GerritServerConfig Config serverConfig,
+        ChangeData.Factory changeDataFactory,
+        ProjectCache projectCache,
+        GitRepositoryManager repoManager) {
       this.useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
+      this.changeDataFactory = changeDataFactory;
+      this.projectCache = projectCache;
+      this.repoManager = repoManager;
     }
 
     @Override
@@ -88,6 +106,12 @@
         return ChangeKind.REWORK;
       }
     }
+
+    @Override
+    public ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch) {
+      return getChangeKindInternal(this, db, change, patch, changeDataFactory,
+          projectCache, repoManager);
+    }
   }
 
   public static class Key implements Serializable {
@@ -161,6 +185,10 @@
   private static class Loader extends CacheLoader<Key, ChangeKind> {
     @Override
     public ChangeKind load(Key key) throws IOException {
+      if (Objects.equal(key.prior, key.next)) {
+        return ChangeKind.NO_CODE_CHANGE;
+      }
+
       RevWalk walk = new RevWalk(key.repo);
       try {
         RevCommit prior = walk.parseCommit(key.prior);
@@ -224,13 +252,22 @@
 
   private final LoadingCache<Key, ChangeKind> cache;
   private final boolean useRecursiveMerge;
+  private final ChangeData.Factory changeDataFactory;
+  private final ProjectCache projectCache;
+  private final GitRepositoryManager repoManager;
 
   @Inject
   ChangeKindCacheImpl(
       @GerritServerConfig Config serverConfig,
-      @Named(ID_CACHE) LoadingCache<Key, ChangeKind> cache) {
+      @Named(ID_CACHE) LoadingCache<Key, ChangeKind> cache,
+      ChangeData.Factory changeDataFactory,
+      ProjectCache projectCache,
+      GitRepositoryManager repoManager) {
     this.cache = cache;
     this.useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
+    this.changeDataFactory = changeDataFactory;
+    this.projectCache = projectCache;
+    this.repoManager = repoManager;
   }
 
   @Override
@@ -244,4 +281,64 @@
       return ChangeKind.REWORK;
     }
   }
+
+  @Override
+  public ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch) {
+    return getChangeKindInternal(this, db, change, patch, changeDataFactory,
+        projectCache, repoManager);
+  }
+
+  private static ChangeKind getChangeKindInternal(
+      ChangeKindCache cache,
+      ReviewDb db,
+      Change change,
+      PatchSet patch,
+      ChangeData.Factory changeDataFactory,
+      ProjectCache projectCache,
+      GitRepositoryManager repoManager) {
+    Repository repo = null;
+    // TODO - dborowitz: add NEW_CHANGE type for default.
+    ChangeKind kind = ChangeKind.REWORK;
+    // Trivial case: if we're on the first patch, we don't need to open
+    // the repository.
+    if (patch.getId().get() > 1) {
+      try {
+        ProjectState projectState = projectCache.checkedGet(change.getProject());
+
+        repo = repoManager.openRepository(change.getProject());
+
+        ChangeData cd = changeDataFactory.create(db, change);
+        Collection<PatchSet> patchSetCollection = cd.patches();
+        PatchSet priorPs = patch;
+        for (PatchSet ps : patchSetCollection) {
+          if (ps.getId().get() < patch.getId().get() &&
+              (ps.getId().get() > priorPs.getId().get() ||
+                  priorPs == patch)) {
+            // We only want the previous patch set, so walk until the last one
+            priorPs = ps;
+          }
+        }
+
+        // If we still think the previous patch is the current patch,
+        // we only have one patch set.  Return the default.
+        // This can happen if a user creates a draft, uploads a second patch,
+        // and deletes the draft.
+        if (priorPs != patch) {
+          kind =
+              cache.getChangeKind(projectState, repo,
+                  ObjectId.fromString(priorPs.getRevision().get()),
+                  ObjectId.fromString(patch.getRevision().get()));
+        }
+      } catch (IOException | OrmException e) {
+        // Do nothing; assume we have a complex change
+        log.warn("Unable to get change kind for patchSet " + patch.getPatchSetId() +
+            "of change " + change.getChangeId(), e);
+      } finally {
+        if (repo != null) {
+          repo.close();
+        }
+      }
+    }
+    return kind;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
index fae8971..226f70f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -34,9 +34,11 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import java.io.IOException;
 
+@Singleton
 public class CherryPick implements RestModifyView<RevisionResource, CherryPickInput>,
     UiAction<RevisionResource> {
   private final Provider<ReviewDb> dbProvider;
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 59b52a1..28885db 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
@@ -30,11 +30,13 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import org.eclipse.jgit.lib.Config;
 
 import java.io.IOException;
 
+@Singleton
 public class DeleteDraftChange implements
     RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
   public static class Input {
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 7b9b3d9..46d0ed9 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
@@ -34,11 +34,13 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import org.eclipse.jgit.lib.Config;
 
 import java.io.IOException;
 
+@Singleton
 public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Input>,
     UiAction<RevisionResource> {
   public static class Input {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java
index b1a3290..af81627 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EditMessage.java
@@ -30,8 +30,10 @@
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -39,6 +41,7 @@
 
 import java.io.IOException;
 
+@Singleton
 class EditMessage implements RestModifyView<RevisionResource, Input>,
     UiAction<RevisionResource> {
   private final ChangeUtil changeUtil;
@@ -76,7 +79,7 @@
           rsrc.getControl(),
           rsrc.getPatchSet().getId(),
           input.message,
-          myIdent));
+          new PersonIdent(myIdent, TimeUtil.nowTs())));
     } catch (InvalidChangeOperationException e) {
       throw new BadRequestException(e.getMessage());
     } catch (NoSuchChangeException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
index 599d5ab..cd8b89f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
@@ -36,11 +36,13 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import org.eclipse.jgit.lib.Config;
 
 import java.io.IOException;
 
+@Singleton
 public class Publish implements RestModifyView<RevisionResource, Input>,
     UiAction<RevisionResource> {
   public static class Input {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index 1ef0ee3..8d96f2d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -35,10 +35,12 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import java.io.IOException;
 import java.util.Collections;
 
+@Singleton
 class PutTopic implements RestModifyView<ChangeResource, Input>,
     UiAction<ChangeResource> {
   private final Provider<ReviewDb> dbProvider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index e1a019f..bbd501c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -33,9 +33,11 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import java.io.IOException;
 
+@Singleton
 public class Rebase implements RestModifyView<RevisionResource, Input>,
     UiAction<RevisionResource> {
   public static class Input {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index b3641e5..9055036 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -37,6 +37,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -44,6 +45,7 @@
 import java.io.IOException;
 import java.util.Collections;
 
+@Singleton
 public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
     UiAction<ChangeResource> {
   private static final Logger log = LoggerFactory.getLogger(Restore.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index 022c12c..326c872 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -32,13 +32,16 @@
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.ssh.NoSshInfo;
+import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 import org.eclipse.jgit.lib.PersonIdent;
 
 import java.io.IOException;
 
+@Singleton
 public class Revert implements RestModifyView<ChangeResource, RevertInput>,
     UiAction<ChangeResource> {
   private final ChangeJson json;
@@ -70,7 +73,7 @@
       Change.Id revertedChangeId =
           changeUtil.revert(control, change.currentPatchSetId(),
               Strings.emptyToNull(input.message),
-              myIdent, new NoSshInfo());
+              new PersonIdent(myIdent, TimeUtil.nowTs()), new NoSshInfo());
 
       return json.format(revertedChangeId);
     } catch (InvalidChangeOperationException e) {
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 776de37..8ed242c 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
@@ -58,6 +58,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.CommitBuilder;
@@ -69,6 +70,7 @@
 import java.util.List;
 import java.util.Map;
 
+@Singleton
 public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
     UiAction<RevisionResource> {
   public enum Status {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 1d640c2..b1ec6e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -54,8 +54,6 @@
   private final SignedToken emailReg;
   private final SignedToken restToken;
 
-  private final boolean allowGoogleAccountUpgrade;
-
   @Inject
   AuthConfig(@GerritServerConfig final Config cfg)
       throws XsrfException {
@@ -97,13 +95,6 @@
     } else {
       restToken = null;
     }
-
-    if (authType == AuthType.OPENID) {
-      allowGoogleAccountUpgrade =
-          cfg.getBoolean("auth", "allowgoogleaccountupgrade", false);
-    } else {
-      allowGoogleAccountUpgrade = false;
-    }
   }
 
   private static List<OpenIdProviderPattern> toPatterns(Config cfg, String name) {
@@ -172,10 +163,6 @@
     return restToken;
   }
 
-  public boolean isAllowGoogleAccountUpgrade() {
-    return allowGoogleAccountUpgrade;
-  }
-
   /** OpenID identities which the server permits for authentication. */
   public List<OpenIdProviderPattern> getAllowedOpenIDs() {
     return allowedOpenIDs;
@@ -237,14 +224,6 @@
   }
 
   private boolean isTrusted(final AccountExternalId id) {
-    if (id.isScheme(AccountExternalId.LEGACY_GAE)) {
-      // Assume this is a trusted token, its a legacy import from
-      // a fairly well respected provider and only takes effect if
-      // the administrator has the import still enabled
-      //
-      return isAllowGoogleAccountUpgrade();
-    }
-
     if (id.isScheme(AccountExternalId.SCHEME_MAILTO)) {
       // mailto identities are created by sending a unique validation
       // token to the address and asking them to come back to the site
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
index 763437f..b734007 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -46,7 +46,7 @@
   private static final Logger log =
       LoggerFactory.getLogger(QueryDocumentationExecutor.class);
 
-  private static final Version LUCENE_VERSION = Version.LUCENE_47;
+  private static final Version LUCENE_VERSION = Version.LUCENE_48;
 
   private IndexSearcher searcher;
   private QueryParser parser;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
index fbaf4ef..10e0214 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/PatchSetCreatedEvent.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.events;
 
+import com.google.gerrit.server.change.ChangeKind;
 import com.google.gerrit.server.data.AccountAttribute;
 import com.google.gerrit.server.data.ChangeAttribute;
 import com.google.gerrit.server.data.PatchSetAttribute;
@@ -23,4 +24,5 @@
     public ChangeAttribute change;
     public PatchSetAttribute patchSet;
     public AccountAttribute uploader;
+    public ChangeKind kind;
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index a5777da..41dfba5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.ChangeStatusPredicate;
+import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
 import com.google.gwtorm.protobuf.CodecFactory;
 import com.google.gwtorm.protobuf.ProtobufCodec;
 import com.google.gwtorm.server.OrmException;
@@ -428,6 +429,40 @@
         }
       };
 
+  /** The number of inserted lines in this change. */
+  public static final FieldDef<ChangeData, Integer> ADDED =
+      new FieldDef.Single<ChangeData, Integer>(
+          ChangeQueryBuilder.FIELD_ADDED, FieldType.INTEGER_RANGE, true) {
+        @Override
+        public Integer get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.changedLines().insertions;
+        }
+      };
+
+  /** The number of deleted lines in this change. */
+  public static final FieldDef<ChangeData, Integer> DELETED =
+      new FieldDef.Single<ChangeData, Integer>(
+          ChangeQueryBuilder.FIELD_DELETED, FieldType.INTEGER_RANGE, true) {
+        @Override
+        public Integer get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.changedLines().deletions;
+        }
+      };
+
+  /** The total number of modified lines in this change. */
+  public static final FieldDef<ChangeData, Integer> DELTA =
+      new FieldDef.Single<ChangeData, Integer>(
+          ChangeQueryBuilder.FIELD_DELTA, FieldType.INTEGER_RANGE, false) {
+        @Override
+        public Integer get(ChangeData input, FillArgs args)
+            throws OrmException {
+          ChangedLines changedLines = input.changedLines();
+          return changedLines.insertions + changedLines.deletions;
+        }
+      };
+
   private static <T> List<byte[]> toProtos(ProtobufCodec<T> codec, Collection<T> objs)
       throws OrmException {
     List<byte[]> result = Lists.newArrayListWithCapacity(objs.size());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
index 1a628d3..8bb8f0b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeSchemas.java
@@ -217,6 +217,32 @@
         ChangeField.APPROVAL,
         ChangeField.MERGEABLE);
 
+  static final Schema<ChangeData> V11 = release(
+        ChangeField.LEGACY_ID,
+        ChangeField.ID,
+        ChangeField.STATUS,
+        ChangeField.PROJECT,
+        ChangeField.PROJECTS,
+        ChangeField.REF,
+        ChangeField.TOPIC,
+        ChangeField.UPDATED,
+        ChangeField.FILE_PART,
+        ChangeField.PATH,
+        ChangeField.OWNER,
+        ChangeField.REVIEWER,
+        ChangeField.COMMIT,
+        ChangeField.TR,
+        ChangeField.LABEL,
+        ChangeField.REVIEWED,
+        ChangeField.COMMIT_MESSAGE,
+        ChangeField.COMMENT,
+        ChangeField.CHANGE,
+        ChangeField.APPROVAL,
+        ChangeField.MERGEABLE,
+        ChangeField.ADDED,
+        ChangeField.DELETED,
+        ChangeField.DELTA);
+
 
 
   private static Schema<ChangeData> release(Collection<FieldDef<ChangeData, ?>> fields) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
index d4f9966..872179d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.index;
 
+import com.google.common.base.Preconditions;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gwtorm.server.OrmException;
@@ -47,6 +48,8 @@
       extends FieldDef<I, Iterable<T>> {
     Repeatable(String name, FieldType<T> type, boolean stored) {
       super(name, type, stored);
+      Preconditions.checkArgument(type != FieldType.INTEGER_RANGE,
+          "Range queries against repeated fields are unsupported");
     }
 
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
index a3247b9..4c40769 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
@@ -23,6 +23,10 @@
   public static final FieldType<Integer> INTEGER =
       new FieldType<Integer>("INTEGER");
 
+  /** A single-integer-valued field matched using range queries. */
+  public static final FieldType<Integer> INTEGER_RANGE =
+      new FieldType<Integer>("INTEGER_RANGE");
+
   /** A single integer-valued field. */
   public static final FieldType<Long> LONG =
       new FieldType<Long>("LONG");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IntegerRangePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IntegerRangePredicate.java
new file mode 100644
index 0000000..1259951
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IntegerRangePredicate.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index;
+
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.util.RangeUtil;
+import com.google.gerrit.server.util.RangeUtil.Range;
+import com.google.gwtorm.server.OrmException;
+
+public abstract class IntegerRangePredicate<T> extends IndexPredicate<T> {
+  private final Range range;
+
+  protected IntegerRangePredicate(FieldDef<T, Integer> type,
+      String value) throws QueryParseException {
+    super(type, value);
+    range = RangeUtil.getRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE);
+    if (range == null) {
+      throw new QueryParseException("Invalid range predicate: " + value);
+    }
+  }
+
+  protected abstract int getValueInt(T object) throws OrmException;
+
+  @Override
+  public boolean match(T object) throws OrmException {
+    int valueInt = getValueInt(object);
+    return valueInt >= range.min && valueInt <= range.max;
+  }
+
+  /** Return the minimum value of this predicate's range, inclusive. */
+  public int getMinimumValue() {
+    return range.min;
+  }
+
+  /** Return the maximum value of this predicate's range, inclusive. */
+  public int getMaximumValue() {
+    return range.max;
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
index 9277f6b..f611052 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
@@ -25,6 +25,7 @@
 import java.sql.Timestamp;
 import java.util.Date;
 
+// TODO: Migrate this to IntegerRangePredicate
 public abstract class TimestampRangePredicate<I> extends IndexPredicate<I> {
   @SuppressWarnings({"deprecation", "unchecked"})
   protected static FieldDef<ChangeData, Timestamp> updatedField(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
index 93fc063..76b896a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/PatchSetNotificationSender.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
@@ -54,7 +55,7 @@
   private static final Logger log =
       LoggerFactory.getLogger(PatchSetNotificationSender.class);
 
-  private final ReviewDb db;
+  private final Provider<ReviewDb> db;
   private final GitRepositoryManager repoManager;
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final ApprovalsUtil approvalsUtil;
@@ -63,7 +64,7 @@
   private final ReplacePatchSetSender.Factory replacePatchSetFactory;
 
   @Inject
-  public PatchSetNotificationSender(ReviewDb db,
+  public PatchSetNotificationSender(Provider<ReviewDb> db,
       ChangeHooks hooks,
       GitRepositoryManager repoManager,
       PatchSetInfoFactory patchSetInfoFactory,
@@ -104,7 +105,7 @@
       recipients.remove(me);
 
       if (newChange) {
-        approvalsUtil.addReviewers(db, update, labelTypes, updatedChange,
+        approvalsUtil.addReviewers(db.get(), update, labelTypes, updatedChange,
             updatedPatchSet, info, recipients.getReviewers(),
             Collections.<Account.Id> emptySet());
         try {
@@ -118,12 +119,12 @@
           log.error("Cannot send email for new change " + updatedChange.getId(), e);
         }
       } else {
-        approvalsUtil.addReviewers(db, update, labelTypes, updatedChange,
+        approvalsUtil.addReviewers(db.get(), update, labelTypes, updatedChange,
             updatedPatchSet, info, recipients.getReviewers(),
-            approvalsUtil.getReviewers(db, notes).values());
+            approvalsUtil.getReviewers(db.get(), notes).values());
         final ChangeMessage msg =
             new ChangeMessage(new ChangeMessage.Key(updatedChange.getId(),
-                ChangeUtil.messageUUID(db)), me,
+                ChangeUtil.messageUUID(db.get())), me,
                 updatedPatchSet.getCreatedOn(), updatedPatchSet.getId());
         msg.setMessage("Uploaded patch set " + updatedPatchSet.getPatchSetId() + ".");
         try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
index 2911f6e..fe1086b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GarbageCollect.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
 import com.google.gerrit.server.project.GarbageCollect.Input;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -35,6 +36,7 @@
 import java.util.Collections;
 
 @RequiresCapability(GlobalCapability.RUN_GC)
+@Singleton
 public class GarbageCollect implements RestModifyView<ProjectResource, Input>,
     UiAction<ProjectResource> {
   public static class Input {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index 6a4f013..7c8b29f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.extensions.webui.UiActions;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -42,6 +43,7 @@
 import java.util.Set;
 import java.util.TreeMap;
 
+@Singleton
 public class ListBranches implements RestReadView<ProjectResource> {
   private final GitRepositoryManager repoManager;
   private final DynamicMap<RestView<BranchResource>> branchViews;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
new file mode 100644
index 0000000..95da72a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IntegerRangePredicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
+
+public class AddedPredicate extends IntegerRangePredicate<ChangeData> {
+  AddedPredicate(String value) throws QueryParseException {
+    super(ChangeField.ADDED, value);
+  }
+
+  @Override
+  protected int getValueInt(ChangeData changeData) throws OrmException {
+    return changeData.changedLines().insertions;
+  }
+}
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 e5248f2..da19d19 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
@@ -342,6 +342,10 @@
     return changedLines;
   }
 
+  public void setChangedLines(int insertions, int deletions) {
+    changedLines = new ChangedLines(insertions, deletions);
+  }
+
   public Change.Id getId() {
     return legacyId;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 4c595ea..ac7b9ae 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -75,6 +75,7 @@
   // NOTE: As new search operations are added, please keep the
   // SearchSuggestOracle up to date.
 
+  public static final String FIELD_ADDED = "added";
   public static final String FIELD_AFTER = "after";
   public static final String FIELD_AGE = "age";
   public static final String FIELD_BEFORE = "before";
@@ -83,6 +84,8 @@
   public static final String FIELD_COMMENT = "comment";
   public static final String FIELD_COMMIT = "commit";
   public static final String FIELD_CONFLICTS = "conflicts";
+  public static final String FIELD_DELETED = "deleted";
+  public static final String FIELD_DELTA = "delta";
   public static final String FIELD_DRAFTBY = "draftby";
   public static final String FIELD_FILE = "file";
   public static final String FIELD_IS = "is";
@@ -676,6 +679,30 @@
     return sortkey_before(sortKey);
   }
 
+  @Operator
+  public Predicate<ChangeData> added(String value)
+      throws QueryParseException {
+    return new AddedPredicate(value);
+  }
+
+  @Operator
+  public Predicate<ChangeData> deleted(String value)
+      throws QueryParseException {
+    return new DeletedPredicate(value);
+  }
+
+  @Operator
+  public Predicate<ChangeData> size(String value)
+      throws QueryParseException {
+    return delta(value);
+  }
+
+  @Operator
+  public Predicate<ChangeData> delta(String value)
+      throws QueryParseException {
+    return new DeltaPredicate(value);
+  }
+
   @Override
   protected Predicate<ChangeData> defaultField(String query) {
     if (query.startsWith("refs/")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
new file mode 100644
index 0000000..478990d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IntegerRangePredicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gwtorm.server.OrmException;
+
+public class DeletedPredicate extends IntegerRangePredicate<ChangeData> {
+  DeletedPredicate(String value) throws QueryParseException {
+    super(ChangeField.DELETED, value);
+  }
+
+  @Override
+  protected int getValueInt(ChangeData changeData) throws OrmException {
+    return changeData.changedLines().deletions;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
new file mode 100644
index 0000000..39b860a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.IntegerRangePredicate;
+import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
+import com.google.gwtorm.server.OrmException;
+
+public class DeltaPredicate extends IntegerRangePredicate<ChangeData> {
+  DeltaPredicate(String value) throws QueryParseException {
+    super(ChangeField.DELTA, value);
+  }
+
+  @Override
+  protected int getValueInt(ChangeData changeData) throws OrmException {
+    ChangedLines changedLines = changeData.changedLines();
+    return changedLines.insertions + changedLines.deletions;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 60f7ffa..9b5aae3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -24,12 +24,12 @@
 import com.google.gerrit.server.query.OrPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.util.LabelVote;
+import com.google.gerrit.server.util.RangeUtil;
+import com.google.gerrit.server.util.RangeUtil.Range;
 import com.google.inject.Provider;
 
 import java.util.List;
 import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public class LabelPredicate extends OrPredicate<ChangeData> {
   private static final int MAX_LABEL_VALUE = 4;
@@ -102,43 +102,28 @@
       // Try next format.
     }
 
+    Range range;
     if (parsed == null) {
-      Matcher m = Pattern.compile("(>|>=|=|<|<=)([+-]?\\d+)$").matcher(v);
-      if (m.find()) {
-        parsed = new Parsed(v.substring(0, m.start()), m.group(1),
-            value(m.group(2)));
-      } else {
-        parsed = new Parsed(v, "=", 1);
+      range = RangeUtil.getRange(v, -MAX_LABEL_VALUE, MAX_LABEL_VALUE);
+      if (range == null) {
+        range = new Range(v, 1, 1);
       }
+    } else {
+      range = RangeUtil.getRange(
+          parsed.label,
+          parsed.test,
+          parsed.expVal,
+          -MAX_LABEL_VALUE,
+          MAX_LABEL_VALUE);
     }
+    String prefix = range.prefix;
+    int min = range.min;
+    int max = range.max;
 
-    int min, max;
-    switch (parsed.test) {
-      case "=":
-      default:
-        min = max = parsed.expVal;
-        break;
-      case ">":
-        min = parsed.expVal + 1;
-        max = MAX_LABEL_VALUE;
-        break;
-      case ">=":
-        min = parsed.expVal;
-        max = MAX_LABEL_VALUE;
-        break;
-      case "<":
-        min = -MAX_LABEL_VALUE;
-        max = parsed.expVal - 1;
-        break;
-      case "<=":
-        min = -MAX_LABEL_VALUE;
-        max = parsed.expVal;
-        break;
-    }
     List<Predicate<ChangeData>> r =
         Lists.newArrayListWithCapacity(max - min + 1);
     for (int i = min; i <= max; i++) {
-      r.add(onePredicate(args, parsed.label, i));
+      r.add(onePredicate(args, prefix, i));
     }
     return r;
   }
@@ -152,13 +137,6 @@
     }
   }
 
-  private static int value(String value) {
-    if (value.startsWith("+")) {
-      value = value.substring(1);
-    }
-    return Integer.parseInt(value);
-  }
-
   private static Predicate<ChangeData> noLabelQuery(Args args, String label) {
     List<Predicate<ChangeData>> r =
         Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
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 fe5ffd8..77c51a5 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_96> C = Schema_96.class;
+  public static final Class<Schema_97> C = Schema_97.class;
 
   public static class Module extends AbstractModule {
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_97.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_97.java
new file mode 100644
index 0000000..0670377
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_97.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class Schema_97 extends SchemaVersion {
+  @Inject
+  Schema_97(Provider<Schema_96> prior) {
+    super(prior);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/RangeUtil.java
new file mode 100644
index 0000000..5c5e2f9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/RangeUtil.java
@@ -0,0 +1,119 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.util;
+
+import com.google.common.primitives.Ints;
+import com.google.gerrit.common.Nullable;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class RangeUtil {
+  private static final Pattern RANGE_PATTERN =
+      Pattern.compile("(>|>=|=|<|<=|)([+-]?\\d+)$");
+
+  private RangeUtil() {}
+
+  public static class Range {
+    /** The prefix of the query, before the range component. */
+    public final String prefix;
+
+    /** The minimum value specified in the query, inclusive. */
+    public final int min;
+
+    /** The maximum value specified in the query, inclusive. */
+    public final int max;
+
+    public Range(String prefix, int min, int max) {
+      this.prefix = prefix;
+      this.min = min;
+      this.max = max;
+    }
+  }
+
+  /**
+   * Determine the range of values being requested in the given query.
+   *
+   * @param rangeQuery the raw query, e.g. "added:>12345"
+   * @param minValue the minimum possible value for the field, inclusive
+   * @param maxValue the maximum possible value for the field, inclusive
+   * @return the calculated {@link Range}, or null if the query is invalid
+   */
+  @Nullable
+  public static Range getRange(String rangeQuery, int minValue, int maxValue) {
+    Matcher m = RANGE_PATTERN.matcher(rangeQuery);
+    String prefix;
+    String test;
+    Integer queryInt;
+    if (m.find()) {
+      prefix = rangeQuery.substring(0, m.start());
+      test = m.group(1);
+      queryInt = value(m.group(2));
+      if (queryInt == null) {
+        return null;
+      }
+    } else {
+      return null;
+    }
+
+    return getRange(prefix, test, queryInt, minValue, maxValue);
+  }
+
+  /**
+   * Determine the range of values being requested in the given query.
+   *
+   * @param prefix a prefix string which is copied into the range
+   * @param test the test operator, one of &gt;, &gt;=, =, &lt;, or &lt;=
+   * @param queryInt the integer being queried
+   * @param minValue the minimum possible value for the field, inclusive
+   * @param maxValue the maximum possible value for the field, inclusive
+   * @return the calculated {@link Range}
+   */
+  public static Range getRange(
+      String prefix, String test, int queryInt, int minValue, int maxValue) {
+    int min, max;
+    switch (test) {
+      case "=":
+      default:
+        min = max = queryInt;
+        break;
+      case ">":
+        min = Ints.saturatedCast(queryInt + 1L);
+        max = maxValue;
+        break;
+      case ">=":
+        min = queryInt;
+        max = maxValue;
+        break;
+      case "<":
+        min = minValue;
+        max = Ints.saturatedCast(queryInt - 1L);
+        break;
+      case "<=":
+        min = minValue;
+        max = queryInt;
+        break;
+    }
+
+    return new Range(prefix, min, max);
+  }
+
+  private static Integer value(String value) {
+    if (value.startsWith("+")) {
+      value = value.substring(1);
+    }
+    return Ints.tryParse(value);
+  }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 5a534d5..fb50744 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -814,6 +814,44 @@
   }
 
   @Test
+  public void bySize() throws Exception {
+    TestRepository<InMemoryRepository> repo = createProject("repo");
+
+    // added = 3, deleted = 0, delta = 3
+    RevCommit commit1 = repo.parseBody(
+        repo.commit().add("file1", "foo\n\foo\nfoo").create());
+    // added = 0, deleted = 2, delta = 2
+    RevCommit commit2 = repo.parseBody(
+        repo.commit().parent(commit1).add("file1", "foo").create());
+
+    Change change1 = newChange(repo, commit1, null, null, null).insert();
+    Change change2 = newChange(repo, commit2, null, null, null).insert();
+
+    assertTrue(query("added:>4").isEmpty());
+    assertResultEquals(change1, queryOne("added:3"));
+    assertResultEquals(change1, queryOne("added:>2"));
+    assertResultEquals(change1, queryOne("added:>=3"));
+    assertResultEquals(change2, queryOne("added:<1"));
+    assertResultEquals(change2, queryOne("added:<=0"));
+
+    assertTrue(query("deleted:>3").isEmpty());
+    assertResultEquals(change2, queryOne("deleted:2"));
+    assertResultEquals(change2, queryOne("deleted:>1"));
+    assertResultEquals(change2, queryOne("deleted:>=2"));
+    assertResultEquals(change1, queryOne("deleted:<1"));
+    assertResultEquals(change1, queryOne("deleted:<=0"));
+
+    for (String str : Lists.newArrayList("delta", "size")) {
+      assertTrue(query(str + ":<2").isEmpty());
+      assertResultEquals(change1, queryOne(str + ":3"));
+      assertResultEquals(change1, queryOne(str + ":>2"));
+      assertResultEquals(change1, queryOne(str + ":>=3"));
+      assertResultEquals(change2, queryOne(str + ":<3"));
+      assertResultEquals(change2, queryOne(str + ":<=2"));
+    }
+  }
+
+  @Test
   public void byDefault() throws Exception {
     TestRepository<InMemoryRepository> repo = createProject("repo");
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
index cf60297..1dcd83b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/LuceneQueryChangesV7Test.java
@@ -54,6 +54,11 @@
   @Override
   @Test
   public void byDefault() {}
+
+  @Ignore
+  @Override
+  @Test
+  public void bySize() {}
   // End tests for features not supported in V7.
 
   @Test
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
new file mode 100644
index 0000000..e8031ef
--- /dev/null
+++ b/gerrit-war/pom.xml
@@ -0,0 +1,59 @@
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.google.gerrit</groupId>
+  <artifactId>gerrit-war</artifactId>
+  <version>2.9-SNAPSHOT</version>
+  <packaging>war</packaging>
+  <name>Gerrit Code Review - WAR</name>
+  <description>Gerrit WAR</description>
+  <url>http://code.google.com/p/gerrit/</url>
+
+  <licenses>
+    <license>
+      <name>The Apache Software License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <scm>
+    <url>https://gerrit.googlesource.com/gerrit</url>
+    <connection>https://gerrit.googlesource.com/gerrit</connection>
+  </scm>
+
+  <developers>
+    <developer>
+      <name>Dave Borowitz</name>
+    </developer>
+    <developer>
+      <name>David Pursehouse</name>
+    </developer>
+    <developer>
+      <name>Edwin Kempin</name>
+    </developer>
+    <developer>
+      <name>Martin Fick</name>
+    </developer>
+    <developer>
+      <name>Saša Živkov</name>
+    </developer>
+    <developer>
+      <name>Shawn Pearce</name>
+    </developer>
+  </developers>
+
+  <mailingLists>
+    <mailingList>
+      <name>Repo and Gerrit Discussion</name>
+      <post>repo-discuss@googlegroups.com</post>
+      <subscribe>https://groups.google.com/forum/#!forum/repo-discuss</subscribe>
+      <unsubscribe>https://groups.google.com/forum/#!forum/repo-discuss</unsubscribe>
+      <archive>https://groups.google.com/forum/#!forum/repo-discuss</archive>
+    </mailingList>
+  </mailingLists>
+
+  <issueManagement>
+    <url>http://code.google.com/p/gerrit/issues/list</url>
+    <system>Google Code Issue Tracker</system>
+  </issueManagement>
+</project>
diff --git a/lib/BUCK b/lib/BUCK
index 61aa6cb..636ae14 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -25,9 +25,9 @@
 
 maven_jar(
   name = 'gwtorm',
-  id = 'gwtorm:gwtorm:1.10',
-  bin_sha1 = '9ba8aa0c5df74a2fd635ec342c51a732e808543f',
-  src_sha1 = '734b33f1b9561cbc8e445c6e18e73700dbe53bcc',
+  id = 'gwtorm:gwtorm:1.11',
+  bin_sha1 = '526c98e8a200c5b3b34dcee69ccf6bc95daf84db',
+  src_sha1 = 'fd07a195026be40709feece4483b48349c58cfe3',
   license = 'Apache2.0',
   deps = [':protobuf'],
   repository = GERRIT,
diff --git a/lib/asciidoctor/java/DocIndexer.java b/lib/asciidoctor/java/DocIndexer.java
index 45cad3d..96f3eb5 100644
--- a/lib/asciidoctor/java/DocIndexer.java
+++ b/lib/asciidoctor/java/DocIndexer.java
@@ -51,7 +51,7 @@
 import java.util.zip.ZipOutputStream;
 
 public class DocIndexer {
-  private static final Version LUCENE_VERSION = Version.LUCENE_47;
+  private static final Version LUCENE_VERSION = Version.LUCENE_48;
   private static final Pattern SECTION_HEADER = Pattern.compile("^=+ (.*)");
 
   @Option(name = "-o", usage = "output JAR file")
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
index ce10120..8d2b718 100644
--- a/lib/gwt/BUCK
+++ b/lib/gwt/BUCK
@@ -1,11 +1,11 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '2.6.0'
+VERSION = '2.6.1'
 
 maven_jar(
   name = 'user',
   id = 'com.google.gwt:gwt-user:' + VERSION,
-  sha1 = '8237d1bd37127188107388c8724be44a0e9f73ca',
+  sha1 = 'c078b1b8cc0281214b0eb458d2c283d039374fad',
   license = 'Apache2.0',
   attach_source = False,
 )
@@ -13,7 +13,7 @@
 maven_jar(
   name = 'dev',
   id = 'com.google.gwt:gwt-dev:' + VERSION,
-  sha1 = 'af3d9ad2fb8be30dc87fdcd6d9a373b2ab675802',
+  sha1 = 'db237e4be0aa1fe43425d2c51ab5485dba211ddd',
   license = 'Apache2.0',
   deps = [
     ':javax-validation',
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK
index a56b5ab..9ccc5aa 100644
--- a/lib/lucene/BUCK
+++ b/lib/lucene/BUCK
@@ -1,11 +1,11 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '4.7.0'
+VERSION = '4.8.1'
 
 maven_jar(
   name = 'core',
   id = 'org.apache.lucene:lucene-core:' + VERSION,
-  sha1 = '12d2b92d15158ac0d7b2864f537403acb4d7f69e',
+  sha1 = 'a549eef6316a2c38d4cda932be809107deeaf8a7',
   license = 'Apache2.0',
   exclude = [
     'META-INF/LICENSE.txt',
@@ -16,7 +16,7 @@
 maven_jar(
   name = 'analyzers-common',
   id = 'org.apache.lucene:lucene-analyzers-common:' + VERSION,
-  sha1 = '399fa6b0d750c8e5c9e4ae73e6407c8b3ed4e8c1',
+  sha1 = '6e3731524351c83cd21022a23bee5e87f0575555',
   license = 'Apache2.0',
   exclude = [
     'META-INF/LICENSE.txt',
@@ -27,6 +27,6 @@
 maven_jar(
   name = 'query-parser',
   id = 'org.apache.lucene:lucene-queryparser:' + VERSION,
-  sha1 = 'f78a804de1582c511224d214c2d9c82ce48379e7',
+  sha1 = 'f3e105d74137906fdeb2c7bc4dd68c08564778f9',
   license = 'Apache2.0',
 )
diff --git a/tools/eclipse/gerrit_gwt_debug.launch b/tools/eclipse/gerrit_gwt_debug.launch
index 945050d..c09997f 100644
--- a/tools/eclipse/gerrit_gwt_debug.launch
+++ b/tools/eclipse/gerrit_gwt_debug.launch
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/gerrit/buck-out/gen/lib/gwt/dev/gwt-dev-2.6.0.jar"/>
+<listEntry value="/gerrit/buck-out/gen/lib/gwt/dev/gwt-dev-2.6.1.jar"/>
 </listAttribute>
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
 <listEntry value="1"/>
diff --git a/tools/maven/BUCK b/tools/maven/BUCK
index b6b501e..4d53783 100644
--- a/tools/maven/BUCK
+++ b/tools/maven/BUCK
@@ -1,11 +1,13 @@
 include_defs('//VERSION')
 include_defs('//tools/maven/package.defs')
 
-TYPE = 'snapshot' if GERRIT_VERSION.endswith('-SNAPSHOT') else 'release'
+URL = 'https://oss.sonatype.org/content/repositories/snapshots' \
+      if GERRIT_VERSION.endswith('-SNAPSHOT') else \
+        'https://oss.sonatype.org/service/local/staging/deploy/maven2'
 
 maven_package(
-  repository = 'gerrit-api-repository',
-  url = 'gs://gerrit-api/%s' % TYPE,
+  repository = 'sonatype-nexus-staging',
+  url = URL,
   version = GERRIT_VERSION,
   jar = {
     'gerrit-extension-api': '//gerrit-extension-api:extension-api',
diff --git a/tools/maven/fake_pom_deploy.xml b/tools/maven/fake_pom_deploy.xml
deleted file mode 100644
index a531200..0000000
--- a/tools/maven/fake_pom_deploy.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0">
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>com.google.gerrit</groupId>
-  <artifactId>Gerrit-Code-Review-Maven</artifactId>
-  <version>1</version>
-  <distributionManagement>
-    <repository>
-      <id>gerrit-api-repository</id>
-      <name>Gerrit API Repository</name>
-      <url>gs://gerrit-api</url>
-      <uniqueVersion>true</uniqueVersion>
-    </repository>
-  </distributionManagement>
-  <build>
-    <extensions>
-      <extension>
-        <groupId>com.googlesource.gerrit</groupId>
-        <artifactId>gs-maven-wagon</artifactId>
-        <version>3.3</version>
-      </extension>
-    </extensions>
-  </build>
-  <pluginRepositories>
-    <pluginRepository>
-      <id>gerrit-maven-repository</id>
-      <url>https://gerrit-maven.storage.googleapis.com/</url>
-    </pluginRepository>
-  </pluginRepositories>
-</project>
diff --git a/tools/maven/fake_pom_install.xml b/tools/maven/fake_pom_install.xml
deleted file mode 100644
index d066a4a..0000000
--- a/tools/maven/fake_pom_install.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0">
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>com.google.gerrit</groupId>
-  <artifactId>Gerrit-Code-Review-Maven</artifactId>
-  <version>1</version>
-</project>
diff --git a/tools/maven/mvn.py b/tools/maven/mvn.py
index a4aaa5a..9e36b48 100644
--- a/tools/maven/mvn.py
+++ b/tools/maven/mvn.py
@@ -15,14 +15,11 @@
 
 from __future__ import print_function
 from optparse import OptionParser
-from os import path
+from os import path, environ
 
 from sys import stderr
 from tools.util import check_output
 
-def mvn(action):
-  return ['mvn', '--file', path.join(self, 'fake_pom_%s.xml' % action)]
-
 opts = OptionParser()
 opts.add_option('--repository', help='maven repository id')
 opts.add_option('--url', help='maven repository url')
@@ -36,33 +33,38 @@
   print('version is empty', file=stderr)
   exit(1)
 
-common = [
-  '-DgroupId=com.google.gerrit',
-  '-Dversion=%s' % args.v,
-]
-
-self = path.dirname(path.abspath(__file__))
+root = path.abspath(__file__)
+for _ in range(0, 3):
+  root = path.dirname(root)
 
 if 'install' == args.a:
-  cmd = mvn(args.a) + ['install:install-file'] + common
+  cmd = [
+    'mvn',
+    'install:install-file',
+    '-Dversion=%s' % args.v,
+  ]
 elif 'deploy' == args.a:
-  cmd = mvn(args.a) + [
-    'deploy:deploy-file',
+  cmd = [
+    'mvn',
+    'gpg:sign-and-deploy-file',
     '-DrepositoryId=%s' % args.repository,
     '-Durl=%s' % args.url,
-  ] + common
+  ]
 else:
   print("unknown action -a %s" % args.a, file=stderr)
   exit(1)
 
 for spec in args.s:
   artifact, packaging_type, src = spec.split(':')
+  exe = cmd + [
+    '-DpomFile=%s' % path.join(root, '%s/pom.xml' % artifact),
+    '-Dpackaging=%s' % packaging_type,
+    '-Dfile=%s' % src,
+  ]
   try:
-    check_output(cmd + [
-      '-DartifactId=%s' % artifact,
-      '-Dpackaging=%s' % packaging_type,
-      '-Dfile=%s' % src,
-    ])
+    if environ.get('VERBOSE'):
+      print(' '.join(exe), file=stderr)
+    check_output(exe)
   except Exception as e:
     print('%s command failed: %s' % (args.a, e), file=stderr)
     exit(1)
diff --git a/tools/plugin_archetype_deploy.sh b/tools/plugin_archetype_deploy.sh
new file mode 100755
index 0000000..929e1fd
--- /dev/null
+++ b/tools/plugin_archetype_deploy.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+help()
+{
+  cat <<'eof'
+Usage: plugin_archetype_deploy [option]
+
+Deploys Gerrit plugin Maven archetypes to Maven Central
+
+Valid options:
+  --help                     show this message
+  --dry-run                  don't execute commands, just print them
+eof
+exit
+}
+
+function getver
+{
+  grep "$1" $root/VERSION | sed "s/.*'\(.*\)'/\1/"
+}
+
+function instroot
+{
+  bindir=${0%/*}
+
+  case $bindir in
+  ./*) bindir=$PWD/$bindir ;;
+  esac
+
+  cd $bindir/..
+  pwd
+}
+
+function doIt
+{
+  case $dryRun in
+    true) echo "$@" ;;
+    *) "$@" ;;
+  esac
+}
+
+function build_and_deploy
+{
+  module=${PWD##*/}
+  doIt mvn package gpg:sign-and-deploy-file \
+    -Durl=$url \
+    -DrepositoryId=sonatype-nexus-staging \
+    -DpomFile=pom.xml \
+    -Dfile=target/$module-$ver.jar
+}
+
+while [ $# -gt 0 ]; do
+  test "$1" == --dry-run && dryRun=true
+  test "$1" == --help && help
+  shift
+done
+
+root=$(instroot)
+cd "$root"
+ver=$(getver GERRIT_VERSION)
+[[ $ver == *-SNAPSHOT ]] \
+  && url="https://oss.sonatype.org/content/repositories/snapshots" \
+  || url="https://oss.sonatype.org/service/local/staging/deploy/maven2"
+
+for d in gerrit-plugin-archetype \
+         gerrit-plugin-js-archetype \
+         gerrit-plugin-gwt-archetype ; do
+  (cd "$d"; build_and_deploy)
+done
+
diff --git a/tools/version.py b/tools/version.py
index 92143ba..a994bd8 100755
--- a/tools/version.py
+++ b/tools/version.py
@@ -32,7 +32,8 @@
 
 for project in ['gerrit-extension-api', 'gerrit-plugin-api',
                 'gerrit-plugin-archetype', 'gerrit-plugin-gwt-archetype',
-                'gerrit-plugin-gwtui', 'gerrit-plugin-js-archetype']:
+                'gerrit-plugin-gwtui', 'gerrit-plugin-js-archetype',
+                'gerrit-war']:
   pom = os.path.join(project, 'pom.xml')
   try:
     outxml = ""