Merge "Revision API: Add method to get patch"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 610b65f..f07ac18 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -871,6 +871,17 @@
 +
 Default is "Submit patch set ${patchSet} into ${branch}".
 
+[[change.submitTooltipAncestors]]change.submitTooltipAncestors::
++
+Tooltip for the submit button if there are ancestors which would
+also be submitted by submitting the change. Additionally to the variables
+as in link:#change.submitTooltip[change.submitTooltip], there is the
+variable `${submitSize}` indicating the number of changes which are
+submitted.
++
+Default is "Submit all ${topicSize} changes of the same topic (${submitSize}
+changes including ancestors and other changes related by topic)".
+
 [[change.submitWholeTopic]]change.submitWholeTopic::
 +
 Determines if the submit button submits the whole topic instead of
@@ -2289,10 +2300,6 @@
 +
 A link:http://lucene.apache.org/[Lucene] index is used.
 +
-* `SOLR`
-+
-A link:https://cwiki.apache.org/confluence/display/solr/SolrCloud[
-SolrCloud] index is used.
 
 +
 By default, `LUCENE`.
@@ -2413,17 +2420,6 @@
   maxBufferedDocs = 500
 ----
 
-==== Solr configuration
-
-Open and closed changes are indexed in separate indexes named
-'changes_open' and 'changes_closed' respectively.
-
-The following settings are only used when the index type is `SOLR`.
-
-[[index.url]]index.url::
-+
-URL of the index server.
-
 [[ldap]]
 === Section ldap
 
diff --git a/Documentation/database-setup.txt b/Documentation/database-setup.txt
index 4f854fb..b3b72c4 100644
--- a/Documentation/database-setup.txt
+++ b/Documentation/database-setup.txt
@@ -146,6 +146,51 @@
 Visit SAP MaxDB's link:http://maxdb.sap.com/documentation/[documentation] for further
 information regarding using SAP MaxDB.
 
+[[createdb_db2]]
+=== DB2
+
+IBM DB2 is a supported database for running Gerrit Code Review. However it is
+recommended only for environments where you intend to run Gerrit on an existing
+DB2 installation to reduce administrative overhead.
+
+Create a system wide user for the Gerrit application, and grant the user
+full rights on the newly created database:
+
+----
+  db2 => create database gerrit
+  db2 => connect to gerrit
+  db2 => grant connect,accessctrl,dataaccess,dbadm,secadm on database to gerrit2;
+----
+
+JDBC driver db2jcc4.jar and db2jcc_license_cu.jar must be obtained
+from your DB2 distribution. Gerrit initialization process tries to copy
+it from a known location:
+
+----
+/opt/ibm/db2/V10.5/java/db2jcc4.jar
+/opt/ibm/db2/V10.5/java/db2jcc_license_cu.jar
+----
+
+If these files cannot be located at this place, then an alternative location
+can be provided during init step execution.
+
+Sample database section in $site_path/etc/gerrit.config:
+
+----
+[database]
+        type = db2
+        database = gerrit
+        hostname = localhost
+        username = gerrit2
+        port = 50001
+----
+
+Sample database section in $site_path/etc/secure.config:
+
+----
+[database]
+        password = secret_pasword
+----
 
 GERRIT
 ------
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
index 248e1fe..21efc87 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
@@ -276,7 +276,7 @@
       }
     }
 
-    Splitter s = Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings();
+    Splitter s = Splitter.on(CharMatcher.whitespace()).omitEmptyStrings();
     assertThat(filtered).containsExactlyElementsIn(
         Ordering.natural().sortedCopy(s.split(out))).inOrder();
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index 66c99f1..24bb72b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -52,15 +52,14 @@
     commonActionsAssertions(actions);
     // We want to treat a single change in a topic not as a whole topic,
     // so regardless of how submitWholeTopic is configured:
-    noSubmitWholeTopicAssertions(actions);
+    noSubmitWholeTopicAssertions(actions, 1);
   }
 
   @Test
-  public void revisionActionsTwoChangeChangesInTopic() throws Exception {
+  public void revisionActionsTwoChangesInTopic() throws Exception {
     String changeId = createChangeWithTopic().getChangeId();
     approve(changeId);
-    // create another change with the same topic
-    createChangeWithTopic().getChangeId();
+    String changeId2 = createChangeWithTopic().getChangeId();
     Map<String, ActionInfo> actions = getActions(changeId);
     commonActionsAssertions(actions);
     if (isSubmitWholeTopicEnabled()) {
@@ -68,14 +67,19 @@
       assertThat(info.enabled).isNull();
       assertThat(info.label).isEqualTo("Submit whole topic");
       assertThat(info.method).isEqualTo("POST");
-      assertThat(info.title).isEqualTo("Other changes in this topic are not ready");
+      assertThat(info.title).isEqualTo("This change depends on other " +
+          "changes which are not ready");
     } else {
-      noSubmitWholeTopicAssertions(actions);
+      noSubmitWholeTopicAssertions(actions, 1);
+
+      assertThat(getActions(changeId2).get("submit")).isNull();
+      approve(changeId2);
+      noSubmitWholeTopicAssertions(getActions(changeId2), 2);
     }
   }
 
   @Test
-  public void revisionActionsTwoChangeChangesInTopic_conflicting() throws Exception {
+  public void revisionActionsTwoChangesInTopic_conflicting() throws Exception {
     String changeId = createChangeWithTopic().getChangeId();
     approve(changeId);
 
@@ -99,38 +103,66 @@
       assertThat(info.label).isEqualTo("Submit whole topic");
       assertThat(info.method).isEqualTo("POST");
       assertThat(info.title).isEqualTo(
-          "Clicking the button would fail for other changes in the topic");
+          "Clicking the button would fail for other changes");
     } else {
-      noSubmitWholeTopicAssertions(actions);
+      noSubmitWholeTopicAssertions(actions, 1);
     }
   }
 
   @Test
-  public void revisionActionsTwoChangeChangesInTopicReady() throws Exception {
-    String changeId = createChangeWithTopic().getChangeId();
+  public void revisionActionsTwoChangesInTopicWithAncestorReady()
+      throws Exception {
+    String changeId = createChange().getChangeId();
     approve(changeId);
+    approve(changeId);
+    String changeId1 = createChangeWithTopic().getChangeId();
+    approve(changeId1);
     // create another change with the same topic
     String changeId2 = createChangeWithTopic().getChangeId();
     approve(changeId2);
-    Map<String, ActionInfo> actions = getActions(changeId);
+    Map<String, ActionInfo> actions = getActions(changeId1);
     commonActionsAssertions(actions);
     if (isSubmitWholeTopicEnabled()) {
       ActionInfo info = actions.get("submit");
       assertThat(info.enabled).isTrue();
       assertThat(info.label).isEqualTo("Submit whole topic");
       assertThat(info.method).isEqualTo("POST");
-      assertThat(info.title).isEqualTo("Submit all 2 changes of the same topic");
+      assertThat(info.title).isEqualTo("Submit all 2 changes of the same " +
+          "topic (3 changes including ancestors " +
+          "and other changes related by topic)");
     } else {
-      noSubmitWholeTopicAssertions(actions);
+      noSubmitWholeTopicAssertions(actions, 2);
     }
   }
 
-  private void noSubmitWholeTopicAssertions(Map<String, ActionInfo> actions) {
+  @Test
+  public void revisionActionsReadyWithAncestors() throws Exception {
+    String changeId = createChange().getChangeId();
+    approve(changeId);
+    approve(changeId);
+    String changeId1 = createChange().getChangeId();
+    approve(changeId1);
+    String changeId2 = createChangeWithTopic().getChangeId();
+    approve(changeId2);
+    Map<String, ActionInfo> actions = getActions(changeId2);
+    commonActionsAssertions(actions);
+    // The topic contains only one change, so standard text applies
+    noSubmitWholeTopicAssertions(actions, 3);
+  }
+
+  private void noSubmitWholeTopicAssertions(Map<String, ActionInfo> actions,
+      int nrChanges) {
     ActionInfo info = actions.get("submit");
     assertThat(info.enabled).isTrue();
     assertThat(info.label).isEqualTo("Submit");
     assertThat(info.method).isEqualTo("POST");
-    assertThat(info.title).isEqualTo("Submit patch set 1 into master");
+    if (nrChanges == 1) {
+      assertThat(info.title).isEqualTo("Submit patch set 1 into master");
+    } else {
+      assertThat(info.title).isEqualTo(String.format(
+          "Submit patch set 1 and ancestors (%d changes " +
+          "altogether) into master", nrChanges));
+    }
   }
 
   private void commonActionsAssertions(Map<String, ActionInfo> actions) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 9f51d4a..9a5a4f2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -93,7 +93,7 @@
   public final native String branch() /*-{ return this.branch; }-*/;
   public final native String topic() /*-{ return this.topic; }-*/;
   public final native String changeId() /*-{ return this.change_id; }-*/;
-  public final native boolean mergeable() /*-{ return this.mergeable || false; }-*/;
+  public final native boolean mergeable() /*-{ return this.mergeable ? true : false; }-*/;
   public final native int insertions() /*-{ return this.insertions; }-*/;
   public final native int deletions() /*-{ return this.deletions; }-*/;
   private final native String statusRaw() /*-{ return this.status; }-*/;
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 33c1e70..53d6713 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
@@ -312,7 +312,6 @@
     return schema;
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   public void replace(ChangeData cd) throws IOException {
     Term id = QueryBuilder.idTerm(cd);
@@ -332,7 +331,6 @@
     }
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   public void delete(Change.Id id) throws IOException {
     Term idTerm = QueryBuilder.idTerm(id);
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index c975ef9..b52e7af 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -103,7 +103,6 @@
     '//gerrit-lucene:lucene',
     '//gerrit-oauth:oauth',
     '//gerrit-openid:openid',
-    '//gerrit-solr:solr',
     '//lib:args4j',
     '//lib:gwtorm',
     '//lib:protobuf',
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 139af9e..0a0d000 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -77,7 +77,6 @@
 import com.google.gerrit.server.ssh.NoSshKeyCache;
 import com.google.gerrit.server.ssh.NoSshModule;
 import com.google.gerrit.server.ssh.SshAddressesModule;
-import com.google.gerrit.solr.SolrIndexModule;
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
@@ -383,8 +382,6 @@
     switch (indexType) {
       case LUCENE:
         return luceneModule != null ? luceneModule : new LuceneIndexModule();
-      case SOLR:
-        return new SolrIndexModule();
       default:
         throw new IllegalStateException("unsupported index.type = " + indexType);
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
index db436d5..8b90e54 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
@@ -141,7 +141,7 @@
                 MoreExecutors.directExecutor());
           }
 
-          mpm.waitFor(Futures.transform(Futures.successfulAsList(futures),
+          mpm.waitFor(Futures.transformAsync(Futures.successfulAsList(futures),
               new AsyncFunction<List<?>, Void>() {
                   @Override
                 public ListenableFuture<Void> apply(List<?> input)
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index 64dd514..5bedfe3 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -35,7 +35,6 @@
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.index.SiteIndexer;
-import com.google.gerrit.solr.SolrIndexModule;
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.Module;
@@ -122,9 +121,6 @@
       case LUCENE:
         changeIndexModule = new LuceneIndexModule(version, threads, outputBase);
         break;
-      case SOLR:
-        changeIndexModule = new SolrIndexModule(false, threads, outputBase);
-        break;
       default:
         throw new IllegalStateException("unsupported index.type");
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DB2Initializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DB2Initializer.java
new file mode 100644
index 0000000..3f6abcf
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DB2Initializer.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import static com.google.gerrit.pgm.init.api.InitUtil.username;
+
+import com.google.gerrit.pgm.init.api.Section;
+
+
+public class DB2Initializer implements DatabaseConfigInitializer {
+
+  @Override
+  public void initConfig(Section databaseSection) {
+    final String defPort = "50001";
+    databaseSection.string("Server hostname", "hostname", "localhost");
+    databaseSection.string("Server port", "port", defPort, false);
+    databaseSection.string("Database name", "database", "gerrit");
+    databaseSection.string("Database username", "username", username());
+    databaseSection.password("username", "password");
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
index 607d6b4..a0c24a6 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/DatabaseConfigModule.java
@@ -30,6 +30,8 @@
   protected void configure() {
     bind(SitePaths.class).toInstance(site);
     bind(DatabaseConfigInitializer.class).annotatedWith(
+        Names.named("db2")).to(DB2Initializer.class);
+    bind(DatabaseConfigInitializer.class).annotatedWith(
         Names.named("h2")).to(H2Initializer.class);
     bind(DatabaseConfigInitializer.class).annotatedWith(
         Names.named("jdbc")).to(JDBCInitializer.class);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
index 3fcc911..abea521 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
@@ -84,6 +84,8 @@
       libraries.mysqlDriver.downloadRequired();
     } else if (dci instanceof OracleInitializer) {
       libraries.oracleDriver.downloadRequired();
+    } else if (dci instanceof DB2Initializer) {
+      libraries.db2Driver.downloadRequired();
     }
 
     dci.initConfig(database);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
index acb8a6b..a177fe7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -51,9 +51,6 @@
     ui.header("Index");
 
     IndexType type = index.select("Type", "type", IndexType.LUCENE);
-    if (type == IndexType.SOLR) {
-      index.string("Solr Index URL", "url", "localhost:9983");
-    }
     if (site.isNew && type == IndexType.LUCENE) {
       LuceneChangeIndex.setReady(
           site, ChangeSchemas.getLatest().getVersion(), true);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
index 7c7b14d..869e1c4 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
@@ -39,6 +39,8 @@
 
   /* final */LibraryDownloader bouncyCastleProvider;
   /* final */LibraryDownloader bouncyCastleSSL;
+  /* final */LibraryDownloader db2Driver;
+  /* final */LibraryDownloader db2DriverLicense;
   /* final */LibraryDownloader mysqlDriver;
   /* final */LibraryDownloader oracleDriver;
 
@@ -87,16 +89,25 @@
     LibraryDownloader dl = (LibraryDownloader) field.get(this);
     dl.setName(get(cfg, n, "name"));
     dl.setJarUrl(get(cfg, n, "url"));
-    dl.setSHA1(get(cfg, n, "sha1"));
+    dl.setSHA1(getOptional(cfg, n, "sha1"));
     dl.setRemove(get(cfg, n, "remove"));
     for (String d : cfg.getStringList("library", n, "needs")) {
       dl.addNeeds((LibraryDownloader) getClass().getDeclaredField(d).get(this));
     }
   }
 
+  private static String getOptional(Config cfg, String name, String key) {
+    return doGet(cfg, name, key, false);
+  }
+
   private static String get(Config cfg, String name, String key) {
+    return doGet(cfg, name, key, true);
+  }
+
+  private static final String doGet(Config cfg, String name, String key,
+      boolean required) {
     String val = cfg.getString("library", name, key);
-    if (val == null || val.isEmpty()) {
+    if ((val == null || val.isEmpty()) && required) {
       throw new IllegalStateException("Variable library." + name + "." + key
           + " is required within " + RESOURCE_FILE);
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
index 00c7c58..9c9843e 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
@@ -300,6 +300,8 @@
 
   private void verifyFileChecksum() {
     if (sha1 == null) {
+      System.err.println();
+      System.err.flush();
       return;
     }
     Hasher h = Hashing.sha1().newHasher();
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
index 36e8921..20dc4ce 100644
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
@@ -39,3 +39,17 @@
   url = file:///u01/app/oracle/product/11.2.0/xe/jdbc/lib/ojdbc6.jar
   sha1 = 2f89cd9176772c3a6c261ce6a8e3d0d4425f5679
   remove = ojdbc6.jar
+
+[library "db2Driver"]
+  name = DB2 Type 4 JDBC driver (10.5)
+  url = file:///opt/ibm/db2/V10.5/java/db2jcc4.jar
+  sha1 = 9344d4fd41d6511f2d1d1deb7759056495b3a39b
+  needs = db2DriverLicense
+  remove = db2jcc4.jar
+
+# Omit SHA-1 for license JAR as it's not stable and depends on the product
+# the customer has purchased.
+[library "db2DriverLicense"]
+  name = DB2 Type 4 JDBC driver license (10.5)
+  url = file:///opt/ibm/db2/V10.5/java/db2jcc_license_cu.jar
+  remove = db2jcc_license_cu.jar
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
index cd3b3b1..9f7eb93 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/HashtagsUtil.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.change;
 
-import static com.google.common.base.CharMatcher.WHITESPACE;
-
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
 import com.google.gerrit.common.ChangeHooks;
@@ -45,7 +43,8 @@
 
 @Singleton
 public class HashtagsUtil {
-  private static final CharMatcher LEADER = WHITESPACE.or(CharMatcher.is('#'));
+  private static final CharMatcher LEADER =
+      CharMatcher.whitespace().or(CharMatcher.is('#'));
   private static final String PATTERN = "(?:\\s|\\A)#[\\p{L}[0-9]-_]+";
 
   private final ChangeUpdate.Factory updateFactory;
@@ -69,7 +68,7 @@
 
   public static String cleanupHashtag(String hashtag) {
     hashtag = LEADER.trimLeadingFrom(hashtag);
-    hashtag = WHITESPACE.trimTrailingFrom(hashtag);
+    hashtag = CharMatcher.whitespace().trimTrailingFrom(hashtag);
     return hashtag;
   }
 
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 3d4dc26..2b8e3bc 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
@@ -14,18 +14,12 @@
 
 package com.google.gerrit.server.change;
 
-import static com.google.gerrit.common.data.SubmitRecord.Status.OK;
-
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.ParameterizedString;
-import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -46,9 +40,9 @@
 import com.google.gerrit.server.git.ChangeSet;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeOp;
+import com.google.gerrit.server.git.MergeSuperSet;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.gwtorm.server.OrmException;
@@ -64,9 +58,6 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -77,16 +68,21 @@
 
   private static final String DEFAULT_TOOLTIP =
       "Submit patch set ${patchSet} into ${branch}";
+  private static final String DEFAULT_TOOLTIP_ANCESTORS =
+      "Submit patch set ${patchSet} and ancestors (${submitSize} changes " +
+      "altogether) into ${branch}";
   private static final String DEFAULT_TOPIC_TOOLTIP =
-      "Submit all ${topicSize} changes of the same topic";
-  private static final String BLOCKED_TOPIC_TOOLTIP =
-      "Other changes in this topic are not ready";
-  private static final String BLOCKED_HIDDEN_TOPIC_TOOLTIP =
-      "Other hidden changes in this topic are not ready";
-  private static final String CLICK_FAILURE_OTHER_TOOLTIP =
-      "Clicking the button would fail for other changes in the topic";
+      "Submit all ${topicSize} changes of the same topic " +
+      "(${submitSize} changes including ancestors and other " +
+      "changes related by topic)";
+  private static final String BLOCKED_SUBMIT_TOOLTIP =
+      "This change depends on other changes which are not ready";
+  private static final String BLOCKED_HIDDEN_SUBMIT_TOOLTIP =
+      "This change depends on other hidden changes which are not ready";
   private static final String CLICK_FAILURE_TOOLTIP =
-      "Clicking the button would fail.";
+      "Clicking the button would fail";
+  private static final String CLICK_FAILURE_OTHER_TOOLTIP =
+      "Clicking the button would fail for other changes";
 
   public static class Output {
     transient Change change;
@@ -100,11 +96,14 @@
   private final GitRepositoryManager repoManager;
   private final ChangeData.Factory changeDataFactory;
   private final ChangeMessagesUtil cmUtil;
+  private final ChangeControl.GenericFactory changeControlFactory;
   private final Provider<MergeOp> mergeOpProvider;
+  private final MergeSuperSet mergeSuperSet;
   private final AccountsCollection accounts;
   private final ChangesCollection changes;
   private final String label;
   private final ParameterizedString titlePattern;
+  private final ParameterizedString titlePatternWithAncestors;
   private final String submitTopicLabel;
   private final ParameterizedString submitTopicTooltip;
   private final boolean submitWholeTopic;
@@ -115,7 +114,9 @@
       GitRepositoryManager repoManager,
       ChangeData.Factory changeDataFactory,
       ChangeMessagesUtil cmUtil,
+      ChangeControl.GenericFactory changeControlFactory,
       Provider<MergeOp> mergeOpProvider,
+      MergeSuperSet mergeSuperSet,
       AccountsCollection accounts,
       ChangesCollection changes,
       @GerritServerConfig Config cfg,
@@ -124,7 +125,9 @@
     this.repoManager = repoManager;
     this.changeDataFactory = changeDataFactory;
     this.cmUtil = cmUtil;
+    this.changeControlFactory = changeControlFactory;
     this.mergeOpProvider = mergeOpProvider;
+    this.mergeSuperSet = mergeSuperSet;
     this.accounts = accounts;
     this.changes = changes;
     this.label = MoreObjects.firstNonNull(
@@ -133,6 +136,10 @@
     this.titlePattern = new ParameterizedString(MoreObjects.firstNonNull(
         cfg.getString("change", null, "submitTooltip"),
         DEFAULT_TOOLTIP));
+    this.titlePatternWithAncestors = new ParameterizedString(
+        MoreObjects.firstNonNull(
+            cfg.getString("change", null, "submitTooltipAncestors"),
+            DEFAULT_TOOLTIP_ANCESTORS));
     submitWholeTopic = wholeTopicEnabled(cfg);
     this.submitTopicLabel = MoreObjects.firstNonNull(
         Strings.emptyToNull(cfg.getString("change", null, "submitTopicLabel")),
@@ -170,16 +177,7 @@
           rsrc.getPatchSet().getRevision().get()));
     }
 
-    List<Change> changes;
-    if (submitWholeTopic && !Strings.isNullOrEmpty(change.getTopic())) {
-      changes = new ArrayList<>();
-      for (ChangeData cd : getChangesByTopic(change.getTopic())) {
-        changes.add(cd.change());
-      }
-    } else {
-      changes = Arrays.asList(change);
-    }
-    ChangeSet submittedChanges = ChangeSet.create(changes);
+    ChangeSet submittedChanges = ChangeSet.create(change);
 
     try {
       ReviewDb db = dbProvider.get();
@@ -207,22 +205,24 @@
   }
 
   /**
-   * @param changeList list of changes to be submitted at once
+   * @param cs set of changes to be submitted at once
    * @param identifiedUser the user who is checking to submit
    * @return a reason why any of the changes is not submittable or null
    */
-  private String problemsForSubmittingChanges(
-      List<ChangeData> changeList,
-      IdentifiedUser identifiedUser) {
+  private String problemsForSubmittingChangeset(
+      ChangeSet cs, IdentifiedUser identifiedUser) {
     try {
-      for (ChangeData c : changeList) {
-        ChangeControl changeControl = c.changeControl().forUser(
-            identifiedUser);
+      for (PatchSet.Id psId : cs.patchIds()) {
+        ReviewDb db = dbProvider.get();
+        ChangeControl changeControl = changeControlFactory
+            .controlFor(psId.getParentKey(), identifiedUser);
+        ChangeData c = changeDataFactory.create(db, changeControl);
+
         if (!changeControl.isVisible(dbProvider.get())) {
-          return BLOCKED_HIDDEN_TOPIC_TOOLTIP;
+          return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
         }
         if (!changeControl.canSubmit()) {
-          return BLOCKED_TOPIC_TOOLTIP;
+          return BLOCKED_SUBMIT_TOOLTIP;
         }
         // Recheck mergeability rather than using value stored in the index,
         // which may be stale.
@@ -237,11 +237,11 @@
         if (!mergeable) {
           return CLICK_FAILURE_OTHER_TOOLTIP;
         }
-        checkSubmitRule(c, c.currentPatchSet(), false);
+        MergeOp.checkSubmitRule(c);
       }
     } catch (ResourceConflictException e) {
-      return BLOCKED_TOPIC_TOOLTIP;
-    } catch (OrmException e) {
+      return BLOCKED_SUBMIT_TOOLTIP;
+    } catch (NoSuchChangeException | OrmException e) {
       log.error("Error checking if change is submittable", e);
       throw new OrmRuntimeException("Could not determine problems for the change", e);
     }
@@ -260,7 +260,7 @@
     ChangeData cd = changeDataFactory.create(db, resource.getControl());
 
     try {
-      checkSubmitRule(cd, cd.currentPatchSet(), false);
+      MergeOp.checkSubmitRule(cd);
     } catch (ResourceConflictException e) {
       visible = false;
     } catch (OrmException e) {
@@ -282,40 +282,55 @@
       throw new OrmRuntimeException("Could not determine mergeability", e);
     }
 
-    List<ChangeData> changesByTopic = null;
-    if (submitWholeTopic && !Strings.isNullOrEmpty(topic)) {
-      changesByTopic = getChangesByTopic(topic);
+    ChangeSet cs;
+    try {
+      cs = mergeSuperSet.completeChangeSet(db,
+          ChangeSet.create(cd.change()));
+    } catch (OrmException | IOException e) {
+      throw new OrmRuntimeException("Could not determine complete set of " +
+          "changes to be submitted", e);
     }
-    if (submitWholeTopic
+
+    int topicSize = 0;
+    if (!Strings.isNullOrEmpty(topic)) {
+      topicSize = getChangesByTopic(topic).size();
+    }
+    boolean treatWithTopic = submitWholeTopic
         && !Strings.isNullOrEmpty(topic)
-        && changesByTopic.size() > 1) {
+        && topicSize > 1;
+
+    String submitProblems = problemsForSubmittingChangeset(cs,
+        resource.getUser());
+    if (submitProblems != null) {
+      return new UiAction.Description()
+        .setLabel(treatWithTopic ? submitTopicLabel : label)
+        .setTitle(submitProblems)
+        .setVisible(true)
+        .setEnabled(false);
+    }
+
+    if (treatWithTopic) {
       Map<String, String> params = ImmutableMap.of(
-          "topicSize", String.valueOf(changesByTopic.size()));
-      String topicProblems = problemsForSubmittingChanges(changesByTopic,
-          resource.getUser());
-      if (topicProblems != null) {
-        return new UiAction.Description()
-          .setLabel(submitTopicLabel)
-          .setTitle(topicProblems)
-          .setVisible(true)
-          .setEnabled(false);
-      } else {
-        return new UiAction.Description()
+          "topicSize", String.valueOf(topicSize),
+          "submitSize", String.valueOf(cs.size()));
+      return new UiAction.Description()
           .setLabel(submitTopicLabel)
           .setTitle(Strings.emptyToNull(
               submitTopicTooltip.replace(params)))
           .setVisible(true)
           .setEnabled(Boolean.TRUE.equals(enabled));
-      }
     } else {
       RevId revId = resource.getPatchSet().getRevision();
       Map<String, String> params = ImmutableMap.of(
           "patchSet", String.valueOf(resource.getPatchSet().getPatchSetId()),
           "branch", resource.getChange().getDest().getShortName(),
-          "commit", ObjectId.fromString(revId.get()).abbreviate(7).name());
+          "commit", ObjectId.fromString(revId.get()).abbreviate(7).name(),
+          "submitSize", String.valueOf(cs.size()));
+      ParameterizedString tp = cs.size() > 1 ? titlePatternWithAncestors :
+          titlePattern;
       return new UiAction.Description()
         .setLabel(label)
-        .setTitle(Strings.emptyToNull(titlePattern.replace(params)))
+        .setTitle(Strings.emptyToNull(tp.replace(params)))
         .setVisible(true)
         .setEnabled(Boolean.TRUE.equals(enabled));
     }
@@ -340,95 +355,6 @@
         .orNull();
   }
 
-  private List<SubmitRecord> checkSubmitRule(ChangeData cd,
-      PatchSet patchSet, boolean force)
-          throws ResourceConflictException, OrmException {
-    List<SubmitRecord> results = new SubmitRuleEvaluator(cd)
-        .setPatchSet(patchSet)
-        .evaluate();
-    Optional<SubmitRecord> ok = findOkRecord(results);
-    if (ok.isPresent()) {
-      // Rules supplied a valid solution.
-      return ImmutableList.of(ok.get());
-    } else if (force) {
-      return results;
-    } else if (results.isEmpty()) {
-      throw new IllegalStateException(String.format(
-          "SubmitRuleEvaluator.evaluate returned empty list for %s in %s",
-          patchSet.getId(),
-          cd.change().getProject().get()));
-    }
-
-    for (SubmitRecord record : results) {
-      switch (record.status) {
-        case CLOSED:
-          throw new ResourceConflictException("change is closed");
-
-        case RULE_ERROR:
-          throw new ResourceConflictException(String.format(
-              "rule error: %s",
-              record.errorMessage));
-
-        case NOT_READY:
-          StringBuilder msg = new StringBuilder();
-          for (SubmitRecord.Label lbl : record.labels) {
-            switch (lbl.status) {
-              case OK:
-              case MAY:
-                continue;
-
-              case REJECT:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("blocked by ").append(lbl.label);
-                continue;
-
-              case NEED:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("needs ").append(lbl.label);
-                continue;
-
-              case IMPOSSIBLE:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("needs ").append(lbl.label)
-                   .append(" (check project access)");
-                continue;
-
-              default:
-                throw new IllegalStateException(String.format(
-                    "Unsupported SubmitRecord.Label %s for %s in %s",
-                    lbl.toString(),
-                    patchSet.getId(),
-                    cd.change().getProject().get()));
-            }
-          }
-          throw new ResourceConflictException(msg.toString());
-
-        default:
-          throw new IllegalStateException(String.format(
-              "Unsupported SubmitRecord %s for %s in %s",
-              record,
-              patchSet.getId().getId(),
-              cd.change().getProject().get()));
-      }
-    }
-    throw new IllegalStateException();
-  }
-
-  private static Optional<SubmitRecord> findOkRecord(Collection<SubmitRecord> in) {
-    return Iterables.tryFind(in, new Predicate<SubmitRecord>() {
-      @Override
-      public boolean apply(SubmitRecord input) {
-        return input.status == OK;
-      }
-    });
-  }
-
   static String status(Change change) {
     return change != null ? change.getStatus().name().toLowerCase() : "deleted";
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
index b01ae2f1..c6ba605 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
@@ -80,7 +80,7 @@
   }
 
   private static boolean isPluginNameSane(String pluginName) {
-    return CharMatcher.JAVA_LETTER_OR_DIGIT
+    return CharMatcher.javaLetterOrDigit()
         .or(CharMatcher.is('-'))
         .matchesAllOf(pluginName);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
index 6c3b499..a18a3a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeSet.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 
 /** A set of changes grouped together to be submitted atomically.*/
@@ -29,6 +30,7 @@
     ImmutableSet.Builder<Project.NameKey> pb = ImmutableSet.builder();
     ImmutableSet.Builder<Branch.NameKey> bb = ImmutableSet.builder();
     ImmutableSet.Builder<Change.Id> ib = ImmutableSet.builder();
+    ImmutableSet.Builder<PatchSet.Id> psb = ImmutableSet.builder();
     ImmutableSetMultimap.Builder<Project.NameKey, Branch.NameKey> pbb =
         ImmutableSetMultimap.builder();
     ImmutableSetMultimap.Builder<Project.NameKey, Change.Id> pcb =
@@ -42,13 +44,14 @@
       pb.add(project);
       bb.add(branch);
       ib.add(c.getId());
+      psb.add(c.currentPatchSetId());
       pbb.put(project, branch);
       pcb.put(project, c.getId());
       cbb.put(branch, c.getId());
     }
 
-    return new AutoValue_ChangeSet(pb.build(), bb.build(),
-        ib.build(), pbb.build(), pcb.build(), cbb.build());
+    return new AutoValue_ChangeSet(pb.build(), bb.build(), ib.build(),
+        psb.build(), pbb.build(), pcb.build(), cbb.build());
   }
 
   public static ChangeSet create(Change change) {
@@ -58,6 +61,7 @@
   public abstract ImmutableSet<Project.NameKey> projects();
   public abstract ImmutableSet<Branch.NameKey> branches();
   public abstract ImmutableSet<Change.Id> ids();
+  public abstract ImmutableSet<PatchSet.Id> patchIds();
   public abstract ImmutableSetMultimap<Project.NameKey, Branch.NameKey>
       branchesByProject();
   public abstract ImmutableSetMultimap<Project.NameKey, Change.Id>
@@ -69,4 +73,8 @@
   public int hashCode() {
     return ids().hashCode();
   }
+
+  public int size() {
+    return ids().size();
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 648d125..d945f77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -225,7 +225,7 @@
     });
   }
 
-  private List<SubmitRecord> checkSubmitRule(ChangeData cd)
+  public static List<SubmitRecord> checkSubmitRule(ChangeData cd)
       throws ResourceConflictException, OrmException {
     PatchSet patchSet = cd.currentPatchSet();
     List<SubmitRecord> results = new SubmitRuleEvaluator(cd)
@@ -237,7 +237,9 @@
       return ImmutableList.of(ok.get());
     } else if (results.isEmpty()) {
       throw new IllegalStateException(String.format(
-          "SubmitRuleEvaluator.evaluate returned empty list for %s in %s",
+          "SubmitRuleEvaluator.evaluate for change %s " +
+          "returned empty list for %s in %s",
+          cd.getId(),
           patchSet.getId(),
           cd.change().getProject().get()));
     }
@@ -245,15 +247,17 @@
     for (SubmitRecord record : results) {
       switch (record.status) {
         case CLOSED:
-          throw new ResourceConflictException("change is closed");
+          throw new ResourceConflictException(String.format(
+              "change %s is closed", cd.getId()));
 
         case RULE_ERROR:
           throw new ResourceConflictException(String.format(
-              "rule error: %s",
-              record.errorMessage));
+              "rule error for change %s: %s",
+              cd.getId(), record.errorMessage));
 
         case NOT_READY:
           StringBuilder msg = new StringBuilder();
+          msg.append(cd.getId() + ":");
           for (SubmitRecord.Label lbl : record.labels) {
             switch (lbl.status) {
               case OK:
@@ -261,32 +265,27 @@
                 continue;
 
               case REJECT:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("blocked by ").append(lbl.label);
+                msg.append(" blocked by ").append(lbl.label);
+                msg.append(";");
                 continue;
 
               case NEED:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("needs ").append(lbl.label);
+                msg.append(" needs ").append(lbl.label);
+                msg.append(";");
                 continue;
 
               case IMPOSSIBLE:
-                if (msg.length() > 0) {
-                  msg.append("; ");
-                }
-                msg.append("needs ").append(lbl.label)
+                msg.append(" needs ").append(lbl.label)
                 .append(" (check project access)");
+                msg.append(";");
                 continue;
 
               default:
                 throw new IllegalStateException(String.format(
-                    "Unsupported SubmitRecord.Label %s for %s in %s",
+                    "Unsupported SubmitRecord.Label %s for %s in %s in %s",
                     lbl.toString(),
                     patchSet.getId(),
+                    cd.getId(),
                     cd.change().getProject().get()));
             }
           }
@@ -303,21 +302,37 @@
     throw new IllegalStateException();
   }
 
-  private void checkPermissions(ChangeSet cs)
+  private void checkSubmitRulesAndState(ChangeSet cs)
       throws ResourceConflictException, OrmException {
+
+    StringBuilder msgbuf = new StringBuilder();
+    List<Change.Id> problemChanges = new ArrayList<>();
     for (Change.Id id : cs.ids()) {
-      ChangeData cd = changeDataFactory.create(db, id);
-      if (cd.change().getStatus() != Change.Status.NEW){
-        throw new OrmException("Change " + cd.change().getChangeId()
-            + " is in state " + cd.change().getStatus());
-      } else {
-        records.put(cd.change().getId(), checkSubmitRule(cd));
+      try {
+        ChangeData cd = changeDataFactory.create(db, id);
+        if (cd.change().getStatus() != Change.Status.NEW){
+          throw new ResourceConflictException("Change " +
+              cd.change().getChangeId() + " is in state " +
+              cd.change().getStatus());
+        } else {
+          records.put(cd.change().getId(), checkSubmitRule(cd));
+        }
+      } catch (ResourceConflictException e) {
+        msgbuf.append(e.getMessage() + "\n");
+        problemChanges.add(id);
       }
     }
+    String reason = msgbuf.toString();
+    if (!reason.isEmpty()) {
+        throw new ResourceConflictException("The change could not be " +
+            "submitted because it depends on change(s) " +
+            problemChanges.toString() + ", which could not be submitted " +
+            "because:\n" + reason);
+    }
   }
 
   public void merge(ReviewDb db, ChangeSet changes, IdentifiedUser caller,
-      boolean checkPermissions) throws NoSuchChangeException,
+      boolean checkSubmitRules) throws NoSuchChangeException,
       OrmException, ResourceConflictException {
     logPrefix = String.format("[%s]: ", String.valueOf(changes.hashCode()));
     this.db = db;
@@ -325,9 +340,9 @@
     try {
       ChangeSet cs = mergeSuperSet.completeChangeSet(db, changes);
       logDebug("Calculated to merge {}", cs);
-      if (checkPermissions) {
-        logDebug("Checking permissions");
-        checkPermissions(cs);
+      if (checkSubmitRules) {
+        logDebug("Checking submit rules and state");
+        checkSubmitRulesAndState(cs);
       }
       try {
         integrateIntoHistory(cs, caller);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index b0107cc..52932c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -635,7 +635,7 @@
 
   private static LabelValue parseLabelValue(String src) {
     List<String> parts = ImmutableList.copyOf(
-        Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings().limit(2)
+        Splitter.on(CharMatcher.whitespace()).omitEmptyStrings().limit(2)
         .split(src));
     if (parts.isEmpty()) {
       throw new IllegalArgumentException("empty value");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index 28f7f7e..afb7c22 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -38,7 +38,7 @@
  */
 public class IndexModule extends LifecycleModule {
   public enum IndexType {
-    LUCENE, SOLR
+    LUCENE
   }
 
   /** Type of secondary index. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java
index 45b7c4d..31fbb40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ReindexAfterUpdate.java
@@ -69,7 +69,7 @@
 
   @Override
   public void onGitReferenceUpdated(final Event event) {
-    Futures.transform(
+    Futures.transformAsync(
         executor.submit(new GetChanges(event)),
         new AsyncFunction<List<Change>, List<Void>>() {
           @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
index 775e967..1bc6b05 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
@@ -213,7 +213,7 @@
     }
 
     try {
-      mpm.waitFor(Futures.transform(Futures.successfulAsList(futures),
+      mpm.waitFor(Futures.transformAsync(Futures.successfulAsList(futures),
           new AsyncFunction<List<?>, Void>() {
             @Override
             public ListenableFuture<Void> apply(List<?> input) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index d73a95d..7a4fb6e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -318,7 +318,7 @@
   }
 
   private static boolean isValidParameterName(String name) {
-    return CharMatcher.JAVA_LETTER_OR_DIGIT
+    return CharMatcher.javaLetterOrDigit()
         .or(CharMatcher.is('-'))
         .matchesAllOf(name) && !name.startsWith("-");
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DB2.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DB2.java
new file mode 100644
index 0000000..4ad8e2f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DB2.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import static com.google.gerrit.server.schema.JdbcUtil.hostname;
+import static com.google.gerrit.server.schema.JdbcUtil.port;
+
+import com.google.gerrit.server.config.ConfigSection;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.Config;
+
+public class DB2 extends BaseDataSourceType {
+  private Config cfg;
+
+  @Inject
+  public DB2(@GerritServerConfig final Config cfg) {
+    super("com.ibm.db2.jcc.DB2Driver");
+    this.cfg = cfg;
+  }
+
+  @Override
+  public String getUrl() {
+    final StringBuilder b = new StringBuilder();
+    final ConfigSection dbc = new ConfigSection(cfg, "database");
+    b.append("jdbc:db2://");
+    b.append(hostname(dbc.optional("hostname")));
+    b.append(port(dbc.optional("port")));
+    b.append("/");
+    b.append(dbc.required("database"));
+    return b.toString();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
index f500444..f50f123 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceModule.java
@@ -21,6 +21,7 @@
 
   @Override
   protected void configure() {
+    bind(DataSourceType.class).annotatedWith(Names.named("db2")).to(DB2.class);
     bind(DataSourceType.class).annotatedWith(Names.named("h2")).to(H2.class);
     bind(DataSourceType.class).annotatedWith(Names.named("jdbc")).to(JDBC.class);
     bind(DataSourceType.class).annotatedWith(Names.named("mysql")).to(MySql.class);
diff --git a/gerrit-solr/BUCK b/gerrit-solr/BUCK
deleted file mode 100644
index ec3c728..0000000
--- a/gerrit-solr/BUCK
+++ /dev/null
@@ -1,20 +0,0 @@
-java_library(
-  name = 'solr',
-  srcs = glob(['src/main/java/**/*.java']),
-  deps = [
-    '//gerrit-antlr:query_exception',
-    '//gerrit-extension-api:api',
-    '//gerrit-lucene:query_builder',
-    '//gerrit-reviewdb:server',
-    '//gerrit-server:server',
-    '//lib:guava',
-    '//lib:gwtorm',
-    '//lib/guice:guice',
-    '//lib/jgit:jgit',
-    '//lib/log:api',
-    '//lib/lucene:analyzers-common',
-    '//lib/lucene:core',
-    '//lib/solr:solrj',
-  ],
-  visibility = ['PUBLIC'],
-)
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/IndexVersionCheck.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/IndexVersionCheck.java
deleted file mode 100644
index 0faa691..0000000
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/IndexVersionCheck.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.solr;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.ChangeSchemas;
-import com.google.inject.Inject;
-import com.google.inject.ProvisionException;
-
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Map;
-
-class IndexVersionCheck implements LifecycleListener {
-  public static final Map<String, Integer> SCHEMA_VERSIONS = ImmutableMap.of(
-      SolrChangeIndex.CHANGES_OPEN, ChangeSchemas.getLatest().getVersion(),
-      SolrChangeIndex.CHANGES_CLOSED, ChangeSchemas.getLatest().getVersion());
-
-  public static Path solrIndexConfig(SitePaths sitePaths) {
-    return sitePaths.index_dir.resolve("gerrit_index.config");
-  }
-
-  private final SitePaths sitePaths;
-
-  @Inject
-  IndexVersionCheck(SitePaths sitePaths) {
-    this.sitePaths = sitePaths;
-  }
-
-  @Override
-  public void start() {
-    // TODO Query schema version from a special meta-document
-    Path path = solrIndexConfig(sitePaths);
-    try {
-      FileBasedConfig cfg = new FileBasedConfig(path.toFile(), FS.detect());
-      cfg.load();
-      for (Map.Entry<String, Integer> e : SCHEMA_VERSIONS.entrySet()) {
-        int schemaVersion = cfg.getInt("index", e.getKey(), "schemaVersion", 0);
-        if (schemaVersion != e.getValue()) {
-          throw new ProvisionException(String.format(
-              "wrong index schema version for \"%s\": expected %d, found %d%s",
-              e.getKey(), e.getValue(), schemaVersion, upgrade()));
-        }
-      }
-    } catch (IOException e) {
-      throw new ProvisionException("unable to read " + path);
-    } catch (ConfigInvalidException e) {
-      throw new ProvisionException("invalid config file " + path);
-    }
-  }
-
-  @Override
-  public void stop() {
-    // Do nothing.
-  }
-
-  private final String upgrade() {
-    return "\nRun reindex to rebuild the index:\n"
-        + "$ java -jar gerrit.war reindex -d "
-        + sitePaths.site_path.toAbsolutePath();
-  }
-}
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
deleted file mode 100644
index b9e47954..0000000
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrChangeIndex.java
+++ /dev/null
@@ -1,338 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.solr;
-
-import static com.google.gerrit.server.index.IndexRewriteImpl.CLOSED_STATUSES;
-import static com.google.gerrit.server.index.IndexRewriteImpl.OPEN_STATUSES;
-import static com.google.gerrit.solr.IndexVersionCheck.SCHEMA_VERSIONS;
-import static com.google.gerrit.solr.IndexVersionCheck.solrIndexConfig;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.lucene.QueryBuilder;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.ChangeField;
-import com.google.gerrit.server.index.ChangeIndex;
-import com.google.gerrit.server.index.FieldDef.FillArgs;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.IndexRewriteImpl;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.Schema.Values;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeDataSource;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
-
-import org.apache.lucene.analysis.standard.StandardAnalyzer;
-import org.apache.lucene.analysis.util.CharArraySet;
-import org.apache.lucene.search.Query;
-import org.apache.solr.client.solrj.SolrQuery;
-import org.apache.solr.client.solrj.SolrQuery.SortClause;
-import org.apache.solr.client.solrj.SolrServer;
-import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.impl.CloudSolrServer;
-import org.apache.solr.common.SolrDocument;
-import org.apache.solr.common.SolrDocumentList;
-import org.apache.solr.common.SolrInputDocument;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-
-import java.io.IOException;
-import java.sql.Timestamp;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/** Secondary index implementation using a remote Solr instance. */
-class SolrChangeIndex implements ChangeIndex, LifecycleListener {
-  public static final String CHANGES_OPEN = "changes_open";
-  public static final String CHANGES_CLOSED = "changes_closed";
-  private static final String ID_FIELD = ChangeField.LEGACY_ID.getName();
-
-  private final Provider<ReviewDb> db;
-  private final ChangeData.Factory changeDataFactory;
-  private final FillArgs fillArgs;
-  private final SitePaths sitePaths;
-  private final IndexCollection indexes;
-  private final CloudSolrServer openIndex;
-  private final CloudSolrServer closedIndex;
-  private final Schema<ChangeData> schema;
-  private final QueryBuilder queryBuilder;
-
-  SolrChangeIndex(
-      @GerritServerConfig Config cfg,
-      Provider<ReviewDb> db,
-      ChangeData.Factory changeDataFactory,
-      FillArgs fillArgs,
-      SitePaths sitePaths,
-      IndexCollection indexes,
-      Schema<ChangeData> schema,
-      String base) throws IOException {
-    this.db = db;
-    this.changeDataFactory = changeDataFactory;
-    this.fillArgs = fillArgs;
-    this.sitePaths = sitePaths;
-    this.indexes = indexes;
-    this.schema = schema;
-
-    String url = cfg.getString("index", null, "url");
-    if (Strings.isNullOrEmpty(url)) {
-      throw new IllegalStateException("index.url must be supplied");
-    }
-
-    queryBuilder = new QueryBuilder(
-        new StandardAnalyzer(CharArraySet.EMPTY_SET));
-
-    base = Strings.nullToEmpty(base);
-    openIndex = new CloudSolrServer(url);
-    openIndex.setDefaultCollection(base + CHANGES_OPEN);
-
-    closedIndex = new CloudSolrServer(url);
-    closedIndex.setDefaultCollection(base + CHANGES_CLOSED);
-  }
-
-  @Override
-  public void start() {
-    indexes.setSearchIndex(this);
-    indexes.addWriteIndex(this);
-  }
-
-  @Override
-  public void stop() {
-    openIndex.shutdown();
-    closedIndex.shutdown();
-  }
-
-  @Override
-  public Schema<ChangeData> getSchema() {
-    return schema;
-  }
-
-  @Override
-  public void close() {
-    stop();
-  }
-
-  @Override
-  public void replace(ChangeData cd) throws IOException {
-    String id = cd.getId().toString();
-    SolrInputDocument doc = toDocument(cd);
-    try {
-      if (cd.change().getStatus().isOpen()) {
-        closedIndex.deleteById(id);
-        openIndex.add(doc);
-      } else {
-        openIndex.deleteById(id);
-        closedIndex.add(doc);
-      }
-    } catch (OrmException | SolrServerException e) {
-      throw new IOException(e);
-    }
-    commit(openIndex);
-    commit(closedIndex);
-  }
-
-  @Override
-  public void delete(Change.Id id) throws IOException {
-    String idString = Integer.toString(id.get());
-    delete(idString, openIndex);
-    delete(idString, closedIndex);
-  }
-
-  private void delete(String id, CloudSolrServer index) throws IOException {
-    try {
-      index.deleteById(id);
-      commit(index);
-    } catch (SolrServerException e) {
-      throw new IOException(e);
-    }
-  }
-
-  @Override
-  public void deleteAll() throws IOException {
-    try {
-      openIndex.deleteByQuery("*:*");
-      closedIndex.deleteByQuery("*:*");
-    } catch (SolrServerException e) {
-      throw new IOException(e);
-    }
-    commit(openIndex);
-    commit(closedIndex);
-  }
-
-  @Override
-  public ChangeDataSource getSource(Predicate<ChangeData> p, int start, int limit)
-      throws QueryParseException {
-    Set<Change.Status> statuses = IndexRewriteImpl.getPossibleStatus(p);
-    List<SolrServer> indexes = Lists.newArrayListWithCapacity(2);
-    if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
-      indexes.add(openIndex);
-    }
-    if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
-      indexes.add(closedIndex);
-    }
-    return new QuerySource(indexes, queryBuilder.toQuery(p), start, limit,
-        getSorts());
-  }
-
-  private static List<SortClause> getSorts() {
-    return ImmutableList.of(
-        new SortClause(
-          ChangeField.UPDATED.getName(), SolrQuery.ORDER.desc),
-        new SortClause(
-          ChangeField.LEGACY_ID.getName(), SolrQuery.ORDER.desc));
-  }
-
-  private void commit(SolrServer server) throws IOException {
-    try {
-      server.commit();
-    } catch (SolrServerException e) {
-      throw new IOException(e);
-    }
-  }
-
-  private class QuerySource implements ChangeDataSource {
-    private final List<SolrServer> servers;
-    private final SolrQuery query;
-
-    public QuerySource(List<SolrServer> indexes, Query q, int start, int limit,
-        List<SortClause> sorts) {
-      this.servers = indexes;
-
-      query = new SolrQuery(q.toString());
-      query.setParam("shards.tolerant", true);
-      query.setParam("rows", Integer.toString(limit));
-      if (start != 0) {
-        query.setParam("start", Integer.toString(start));
-      }
-      query.setFields(ID_FIELD);
-      query.setSorts(sorts);
-    }
-
-    @Override
-    public int getCardinality() {
-      return 10; // TODO: estimate from solr?
-    }
-
-    @Override
-    public boolean hasChange() {
-      return false;
-    }
-
-    @Override
-    public String toString() {
-      return query.getQuery();
-    }
-
-    @Override
-    public ResultSet<ChangeData> read() throws OrmException {
-      try {
-        // TODO Sort documents during merge to select only top N.
-        SolrDocumentList docs = new SolrDocumentList();
-        for (SolrServer index : servers) {
-          docs.addAll(index.query(query).getResults());
-        }
-
-        List<ChangeData> result = Lists.newArrayListWithCapacity(docs.size());
-        for (SolrDocument doc : docs) {
-          Integer v = (Integer) doc.getFieldValue(ID_FIELD);
-          result.add(
-              changeDataFactory.create(db.get(), new Change.Id(v.intValue())));
-        }
-
-        final List<ChangeData> r = Collections.unmodifiableList(result);
-        return new ResultSet<ChangeData>() {
-          @Override
-          public Iterator<ChangeData> iterator() {
-            return r.iterator();
-          }
-
-          @Override
-          public List<ChangeData> toList() {
-            return r;
-          }
-
-          @Override
-          public void close() {
-            // Do nothing.
-          }
-        };
-      } catch (SolrServerException e) {
-        throw new OrmException(e);
-      }
-    }
-  }
-
-  private SolrInputDocument toDocument(ChangeData cd) {
-    SolrInputDocument result = new SolrInputDocument();
-    for (Values<ChangeData> values : schema.buildFields(cd, fillArgs)) {
-      add(result, values);
-    }
-    return result;
-  }
-
-  private void add(SolrInputDocument doc, Values<ChangeData> values) {
-    String name = values.getField().getName();
-    FieldType<?> type = values.getField().getType();
-
-    if (type == FieldType.INTEGER) {
-      for (Object value : values.getValues()) {
-        doc.addField(name, value);
-      }
-    } else if (type == FieldType.LONG) {
-      for (Object value : values.getValues()) {
-        doc.addField(name, value);
-      }
-    } else if (type == FieldType.TIMESTAMP) {
-      for (Object value : values.getValues()) {
-        doc.addField(name, ((Timestamp) value).getTime());
-      }
-    } else if (type == FieldType.EXACT
-        || type == FieldType.PREFIX
-        || type == FieldType.FULL_TEXT) {
-      for (Object value : values.getValues()) {
-        doc.addField(name, value);
-      }
-    } else {
-      throw FieldType.badFieldType(type);
-    }
-  }
-
-  @Override
-  public void markReady(boolean ready) throws IOException {
-    // TODO Move the schema version information to a special meta-document
-    FileBasedConfig cfg = new FileBasedConfig(
-        solrIndexConfig(sitePaths).toFile(),
-        FS.detect());
-    for (Map.Entry<String, Integer> e : SCHEMA_VERSIONS.entrySet()) {
-      cfg.setInt("index", e.getKey(), "schemaVersion",
-          ready ? e.getValue() : -1);
-    }
-    cfg.save();
-  }
-}
diff --git a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java b/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
deleted file mode 100644
index 0133e33..0000000
--- a/gerrit-solr/src/main/java/com/google/gerrit/solr/SolrIndexModule.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.solr;
-
-import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.ChangeIndex;
-import com.google.gerrit.server.index.ChangeSchemas;
-import com.google.gerrit.server.index.FieldDef.FillArgs;
-import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexModule;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.inject.Provider;
-import com.google.inject.Provides;
-import com.google.inject.Singleton;
-
-import org.eclipse.jgit.lib.Config;
-
-import java.io.IOException;
-
-public class SolrIndexModule extends LifecycleModule {
-  private final boolean checkVersion;
-  private final int threads;
-  private final String base;
-
-  public SolrIndexModule() {
-    this(true, 0, null);
-  }
-
-  public SolrIndexModule(boolean checkVersion, int threads, String base) {
-    this.checkVersion = checkVersion;
-    this.threads = threads;
-    this.base = base;
-  }
-
-  @Override
-  protected void configure() {
-    install(new IndexModule(threads));
-    bind(ChangeIndex.class).to(SolrChangeIndex.class);
-    listener().to(SolrChangeIndex.class);
-    if (checkVersion) {
-      listener().to(IndexVersionCheck.class);
-    }
-  }
-
-  @Provides
-  @Singleton
-  IndexConfig getIndexConfig(@GerritServerConfig Config cfg) {
-    return IndexConfig.fromConfig(cfg);
-  }
-
-  @Provides
-  @Singleton
-  public SolrChangeIndex getChangeIndex(@GerritServerConfig Config cfg,
-      Provider<ReviewDb> db,
-      ChangeData.Factory changeDataFactory,
-      SitePaths sitePaths,
-      IndexCollection indexes,
-      FillArgs fillArgs) throws IOException {
-    return new SolrChangeIndex(cfg, db, changeDataFactory, fillArgs, sitePaths,
-        indexes, ChangeSchemas.getLatest(), base);
-  }
-}
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index 35f6084..1f82849 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -17,7 +17,6 @@
     '//gerrit-reviewdb:server',
     '//gerrit-server:server',
     '//gerrit-server/src/main/prolog:common',
-    '//gerrit-solr:solr',
     '//gerrit-sshd:sshd',
     '//lib:guava',
     '//lib:gwtorm',
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 22f3f0b..50c822e 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -61,7 +61,6 @@
 import com.google.gerrit.server.securestore.SecureStoreClassName;
 import com.google.gerrit.server.ssh.NoSshModule;
 import com.google.gerrit.server.ssh.SshAddressesModule;
-import com.google.gerrit.solr.SolrIndexModule;
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
@@ -302,9 +301,6 @@
       case LUCENE:
         changeIndexModule = new LuceneIndexModule();
         break;
-      case SOLR:
-        changeIndexModule = new SolrIndexModule();
-        break;
       default:
         throw new IllegalStateException("unsupported index.type");
     }
diff --git a/lib/BUCK b/lib/BUCK
index 6a6871e..1dbac0a 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -26,9 +26,9 @@
 
 maven_jar(
   name = 'gwtorm_client',
-  id = 'com.google.gerrit:gwtorm:1.14-14-gf54f1f1',
-  bin_sha1 = 'c02267e0245dd06930ea64a2d7c5ddc5ba6d9cfb',
-  src_sha1 = '3d17ae8a173eb34d89098c748f28cddd5080adbc',
+  id = 'com.google.gerrit:gwtorm:1.14-16-gc4e356a',
+  bin_sha1 = '01225468065812bbe5f27972df6dafa9d796d833',
+  src_sha1 = '3622460ed58684cb33f786e3748637c8eea324f9',
   license = 'Apache2.0',
   repository = GERRIT,
 )
@@ -58,8 +58,8 @@
 
 maven_jar(
   name = 'guava',
-  id = 'com.google.guava:guava:18.0',
-  sha1 = 'cce0823396aa693798f8882e64213b1772032b09',
+  id = 'com.google.guava:guava:19.0-rc1',
+  sha1 = '0364538ac107b8943a1f4d68ac50f1b0421bb983',
   license = 'Apache2.0',
 )
 
@@ -78,15 +78,15 @@
 
 maven_jar(
   name = 'jsch',
-  id = 'com.jcraft:jsch:0.1.51',
-  sha1 = '6ceee2696b07cc320d0e1aaea82c7b40768aca0f',
+  id = 'com.jcraft:jsch:0.1.53',
+  sha1 = '658b682d5c817b27ae795637dfec047c63d29935',
   license = 'jsch',
 )
 
 maven_jar(
   name = 'servlet-api-3_1',
-  id = 'org.apache.tomcat:tomcat-servlet-api:8.0.5',
-  sha1 = '9ef01afc25481b82aa8f3615db536869f2dc961e',
+  id = 'org.apache.tomcat:tomcat-servlet-api:8.0.24',
+  sha1 = '5d9e2e895e3111622720157d0aa540066d5fce3a',
   license = 'Apache2.0',
   exclude = ['META-INF/NOTICE', 'META-INF/LICENSE'],
 )
@@ -193,8 +193,8 @@
 
 maven_jar(
   name = 'truth',
-  id = 'com.google.truth:truth:0.26',
-  sha1 = 'b5802815625d82f39c33219299771f3d64301b06',
+  id = 'com.google.truth:truth:0.27',
+  sha1 = 'bd17774d2dc0fffa884d42c07d2537e86c67acd6',
   license = 'DO_NOT_DISTRIBUTE',
   exported_deps = [
     ':guava',
diff --git a/lib/asciidoctor/BUCK b/lib/asciidoctor/BUCK
index 66a12c1..f8feb63 100644
--- a/lib/asciidoctor/BUCK
+++ b/lib/asciidoctor/BUCK
@@ -43,8 +43,8 @@
 
 maven_jar(
   name = 'asciidoctor',
-  id = 'org.asciidoctor:asciidoctorj:1.5.0',
-  sha1 = '192df5660f72a0fb76966dcc64193b94fba65f99',
+  id = 'org.asciidoctor:asciidoctorj:1.5.2',
+  sha1 = '39d33f739ec1c46f6e908a725264eb74b23c9f99',
   license = 'Apache2.0',
   visibility = [],
   attach_source = False,
@@ -52,8 +52,8 @@
 
 maven_jar(
   name = 'jruby',
-  id = 'org.jruby:jruby-complete:1.7.4',
-  sha1 = '74984d84846523bd7da49064679ed1ccf199e1db',
+  id = 'org.jruby:jruby-complete:1.7.18',
+  sha1 = 'a1be3e1790aace5c99614a87785454d875eb21c2',
   license = 'DO_NOT_DISTRIBUTE',
   visibility = [],
   attach_source = False,
diff --git a/lib/jetty/BUCK b/lib/jetty/BUCK
index 2e65abc..d02916f 100644
--- a/lib/jetty/BUCK
+++ b/lib/jetty/BUCK
@@ -1,12 +1,12 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '9.2.10.v20150310'
+VERSION = '9.2.12.v20150709'
 EXCLUDE = ['about.html']
 
 maven_jar(
   name = 'servlet',
   id = 'org.eclipse.jetty:jetty-servlet:' + VERSION,
-  sha1 = '9e923adf1671af7da09dba778e132ab0a9c62415',
+  sha1 = '50116cac18ad893c9628f0a1984390464b133921',
   license = 'Apache2.0',
   deps = [':security'],
   exclude = EXCLUDE,
@@ -15,7 +15,7 @@
 maven_jar(
   name = 'security',
   id = 'org.eclipse.jetty:jetty-security:' + VERSION,
-  sha1 = 'b56228088355023117ba9a9de0da00d652a7e655',
+  sha1 = '9ace95998fbaae8425b2621c90230a229a554784',
   license = 'Apache2.0',
   deps = [':server'],
   exclude = EXCLUDE,
@@ -25,7 +25,7 @@
 maven_jar(
   name = 'servlets',
   id = 'org.eclipse.jetty:jetty-servlets:' + VERSION,
-  sha1 = 'b48a9bb30e9d5e73dcedf8039f96abdb04a3892c',
+  sha1 = 'a1f9e7874e1db2f664213f524463d12bd5ab5db4',
   license = 'Apache2.0',
   exclude = EXCLUDE,
   visibility = [
@@ -37,7 +37,7 @@
 maven_jar(
   name = 'server',
   id = 'org.eclipse.jetty:jetty-server:' + VERSION,
-  sha1 = '0e6b8bff28b3e9ca6254415d2aa49603a5887fe8',
+  sha1 = '8c90ceffb6954385b024899d334192947d0e4077',
   license = 'Apache2.0',
   exported_deps = [
     ':continuation',
@@ -49,7 +49,7 @@
 maven_jar(
   name = 'jmx',
   id = 'org.eclipse.jetty:jetty-jmx:' + VERSION,
-  sha1 = 'fa94eb39f1dd63c40efe44471664f8f70bc7ca2e',
+  sha1 = '8bc0288abba26dbbf4e9225d6fe6fa6348f8da05',
   license = 'Apache2.0',
   exported_deps = [
     ':continuation',
@@ -61,7 +61,7 @@
 maven_jar(
   name = 'continuation',
   id = 'org.eclipse.jetty:jetty-continuation:' + VERSION,
-  sha1 = '1c9bc80037e9898974ada7318f11c984363d4707',
+  sha1 = '0578cb87b78b71eeda91f5dfa3e8bfbafb55cced',
   license = 'Apache2.0',
   exclude = EXCLUDE,
 )
@@ -69,7 +69,7 @@
 maven_jar(
   name = 'http',
   id = 'org.eclipse.jetty:jetty-http:' + VERSION,
-  sha1 = '886b628f62cd518bbb04b37bd1b308fa19340a53',
+  sha1 = '9a6c83f52c70c28e2272d83866b4111cd15ddbc5',
   license = 'Apache2.0',
   exported_deps = [':io'],
   exclude = EXCLUDE,
@@ -78,7 +78,7 @@
 maven_jar(
   name = 'io',
   id = 'org.eclipse.jetty:jetty-io:' + VERSION,
-  sha1 = '29bc6a5e2049d9858bfa811f2728a7a8efcdc1c0',
+  sha1 = 'c02e9e303d231a589e0c8866c1ee89bcdeb40a55',
   license = 'Apache2.0',
   exported_deps = [':util'],
   exclude = EXCLUDE,
@@ -88,7 +88,7 @@
 maven_jar(
   name = 'util',
   id = 'org.eclipse.jetty:jetty-util:' + VERSION,
-  sha1 = '90cc75668dc9a9885108733d4d46420907cf863c',
+  sha1 = 'd99d38adfdb5ec677643f04fa862554b0bb8b42e',
   license = 'Apache2.0',
   exclude = EXCLUDE,
   visibility = [],
diff --git a/lib/solr/BUCK b/lib/solr/BUCK
deleted file mode 100644
index cd39742..0000000
--- a/lib/solr/BUCK
+++ /dev/null
@@ -1,33 +0,0 @@
-include_defs('//lib/maven.defs')
-
-# Java client library to use Solr over the network.
-maven_jar(
-  name = 'solrj',
-  id = 'org.apache.solr:solr-solrj:4.3.1',
-  sha1 = '433fe37796e67eaeb4452f69eb1fae2de27cb7a8',
-  license = 'Apache2.0',
-  deps = [
-    ':noggit',
-    ':zookeeper',
-    '//lib/httpcomponents:httpclient',
-    '//lib/httpcomponents:httpmime',
-    '//lib/commons:io',
-  ],
-)
-
-maven_jar(
-  name = 'noggit',
-  id = 'org.noggit:noggit:0.5',
-  sha1 = '8e6e65624d2e09a30190c6434abe23b7d4e5413c',
-  license = 'Apache2.0',
-  visibility = [],
-)
-
-maven_jar(
-  name = 'zookeeper',
-  id = 'org.apache.zookeeper:zookeeper:3.4.5',
-  sha1 = 'c0f69fb36526552a8f0bc548a6c33c49cf08e562',
-  license = 'Apache2.0',
-  deps = ['//lib/log:api'],
-  visibility = [],
-)
diff --git a/plugins/replication b/plugins/replication
index 7a3a89f..cb9e977 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 7a3a89fc983b9bcb5b2c965affd7a83bb6b10bb2
+Subproject commit cb9e977ddbaa76214c8cefbeb74aa8420ce1d912