Store mergeable field from cache in index

Eventually this will no longer be part of the serialized change proto,
and even with the cache it is still expensive to check mergeability
for each change in a list of search results, as the submit type must
still be checked in order to look up the mergeability.

The new MERGEABLE field now loads from the cache, although ChangeJson
and several other callers still depend on the field in Change. This
will facilitate index schema upgrades, in that a full reindex will
also populate the persistent cache.

While we're at it, upgrade Lucene to 4.10.1, which contains some
important stability bugfixes[1].

[1] http://lucene.apache.org/core/4_10_1/changes/Changes.html#v4.10.1

Change-Id: I166b85f91bd596a3f0295616c2e72853b692dd54
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 9550055..cc910ad 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
@@ -119,8 +119,10 @@
   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 String MERGEABLE_FIELD = ChangeField.MERGEABLE.getName();
   private static final ImmutableSet<String> FIELDS = ImmutableSet.of(
-      ADDED_FIELD, APPROVAL_FIELD, CHANGE_FIELD, DELETED_FIELD, ID_FIELD);
+      ADDED_FIELD, APPROVAL_FIELD, CHANGE_FIELD, DELETED_FIELD, ID_FIELD,
+      MERGEABLE_FIELD);
   private static final Map<String, String> CUSTOM_CHAR_MAPPING = ImmutableMap.of(
       "_", " ", ".", " ");
 
@@ -138,6 +140,8 @@
     Version lucene47 = Version.LUCENE_47;
     @SuppressWarnings("deprecation")
     Version lucene48 = Version.LUCENE_48;
+    @SuppressWarnings("deprecation")
+    Version lucene410 = Version.LUCENE_4_10_0;
     for (Map.Entry<Integer, Schema<ChangeData>> e
         : ChangeSchemas.ALL.entrySet()) {
       if (e.getKey() <= 3) {
@@ -150,8 +154,10 @@
         versions.put(e.getValue(), lucene47);
       } else if (e.getKey() <= 11) {
         versions.put(e.getValue(), lucene48);
+      } else if (e.getKey() <= 13) {
+        versions.put(e.getValue(), lucene410);
       } else {
-        versions.put(e.getValue(), Version.LUCENE_4_10_0);
+        versions.put(e.getValue(), Version.LUCENE_4_10_1);
       }
     }
     LUCENE_VERSIONS = versions.build();
@@ -480,6 +486,14 @@
           deleted.numericValue().intValue());
     }
 
+    // Mergeable.
+    String mergeable = doc.get(MERGEABLE_FIELD);
+    if ("1".equals(mergeable)) {
+      cd.setMergeable(true);
+    } else if ("0".equals(mergeable)) {
+      cd.setMergeable(false);
+    }
+
     return cd;
   }
 
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 9848351..8cfcb70 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
@@ -21,39 +21,24 @@
 import com.google.gerrit.common.Die;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.lucene.LuceneIndexModule;
-import com.google.gerrit.pgm.util.BatchGitModule;
 import com.google.gerrit.pgm.util.BatchProgramModule;
 import com.google.gerrit.pgm.util.SiteProgram;
 import com.google.gerrit.pgm.util.ThreadLimiter;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
-import com.google.gerrit.server.change.MergeabilityCache;
 import com.google.gerrit.server.change.MergeabilityChecker;
-import com.google.gerrit.server.change.MergeabilityChecksExecutor;
-import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
-import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.MergeUtil;
-import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.index.ChangeBatchIndexer;
 import com.google.gerrit.server.index.ChangeIndex;
 import com.google.gerrit.server.index.ChangeSchemas;
 import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
-import com.google.gerrit.server.notedb.NoteDbModule;
 import com.google.gerrit.solr.SolrIndexModule;
-import com.google.inject.AbstractModule;
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.Module;
-import com.google.inject.Provides;
-import com.google.inject.Singleton;
-import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ProgressMonitor;
@@ -146,15 +131,6 @@
     }
     modules.add(changeIndexModule);
     modules.add(dbInjector.getInstance(BatchProgramModule.class));
-    modules.add(new AbstractModule() {
-      @Override
-      protected void configure() {
-        if (recheckMergeable) {
-          install(new MergeabilityModule());
-        }
-      }
-    });
-
     return dbInjector.createChildInjector(modules);
   }
 
@@ -167,38 +143,6 @@
     }
   }
 
-  private static class MergeabilityModule extends FactoryModule {
-    @Override
-    public void configure() {
-      factory(PatchSetInserter.Factory.class);
-      bind(ReplacePatchSetSender.Factory.class).toProvider(
-          Providers.<ReplacePatchSetSender.Factory>of(null));
-
-      factory(MergeUtil.Factory.class);
-      install(new NoteDbModule());
-      install(new BatchGitModule());
-      install(new DefaultCacheFactory.Module());
-      install(MergeabilityCache.module());
-    }
-
-    @Provides
-    @Singleton
-    @MergeabilityChecksExecutor(Priority.BACKGROUND)
-    public WorkQueue.Executor createMergeabilityChecksExecutor(
-        WorkQueue queues) {
-      return queues.createQueue(1, "MergeabilityChecks");
-    }
-
-    @Provides
-    @Singleton
-    @MergeabilityChecksExecutor(Priority.INTERACTIVE)
-    public WorkQueue.Executor createInteractiveMergeabilityChecksExecutor(
-        @MergeabilityChecksExecutor(Priority.BACKGROUND)
-          WorkQueue.Executor bg) {
-      return bg;
-    }
-  }
-
   private int indexAll() throws Exception {
     ReviewDb db = sysInjector.getInstance(ReviewDb.class);
     ProgressMonitor pm = new TextProgressMonitor();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index 8f9f1f4..4712ce8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -30,14 +30,22 @@
 import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
 import com.google.gerrit.server.change.ChangeKindCacheImpl;
+import com.google.gerrit.server.change.MergeabilityCache;
+import com.google.gerrit.server.change.MergeabilityChecksExecutor;
+import com.google.gerrit.server.change.MergeabilityChecksExecutor.Priority;
+import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.CanonicalWebUrlProvider;
 import com.google.gerrit.server.config.DisableReverseDnsLookup;
 import com.google.gerrit.server.config.DisableReverseDnsLookupProvider;
 import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.git.ChangeCache;
+import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.TagCache;
+import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.group.GroupModule;
+import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.notedb.NoteDbModule;
 import com.google.gerrit.server.patch.PatchListCacheImpl;
 import com.google.gerrit.server.project.AccessControlModule;
 import com.google.gerrit.server.project.CommentLinkInfo;
@@ -48,6 +56,8 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.inject.Inject;
 import com.google.inject.Module;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 import com.google.inject.util.Providers;
 
@@ -87,10 +97,16 @@
         .toProvider(DisableReverseDnsLookupProvider.class).in(SINGLETON);
     bind(IdentifiedUser.class)
       .toProvider(Providers.<IdentifiedUser> of(null));
+    bind(ReplacePatchSetSender.Factory.class).toProvider(
+        Providers.<ReplacePatchSetSender.Factory>of(null));
     bind(CurrentUser.class).to(IdentifiedUser.class);
+    factory(MergeUtil.Factory.class);
+    factory(PatchSetInserter.Factory.class);
     install(new AccessControlModule());
+    install(new BatchGitModule());
     install(new DefaultCacheFactory.Module());
     install(new GroupModule());
+    install(new NoteDbModule());
     install(new PrologModule());
     install(AccountByEmailCacheImpl.module());
     install(AccountCacheImpl.module());
@@ -100,9 +116,27 @@
     install(SectionSortCache.module());
     install(ChangeKindCacheImpl.module());
     install(ChangeCache.module());
+    install(MergeabilityCache.module());
     install(TagCache.module());
     factory(CapabilityControl.Factory.class);
     factory(ChangeData.Factory.class);
     factory(ProjectState.Factory.class);
   }
+
+  @Provides
+  @Singleton
+  @MergeabilityChecksExecutor(Priority.BACKGROUND)
+  public WorkQueue.Executor createMergeabilityChecksExecutor(
+      WorkQueue queues) {
+    return queues.createQueue(1, "MergeabilityChecks");
+  }
+
+  @Provides
+  @Singleton
+  @MergeabilityChecksExecutor(Priority.INTERACTIVE)
+  public WorkQueue.Executor createInteractiveMergeabilityChecksExecutor(
+      @MergeabilityChecksExecutor(Priority.BACKGROUND)
+        WorkQueue.Executor bg) {
+    return bg;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java
index b468f35..9a14f27 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCache.java
@@ -38,9 +38,12 @@
 import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.inject.Inject;
+import com.google.inject.Key;
 import com.google.inject.Module;
 import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
 import com.google.inject.name.Named;
+import com.google.inject.name.Names;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -84,6 +87,12 @@
         "SubmitType <-> char BiMap needs updating");
   }
 
+  @SuppressWarnings("rawtypes")
+  public static Key bindingKey() {
+    return Key.get(new TypeLiteral<LoadingCache<EntryKey, Boolean>>() {},
+        Names.named(CACHE_NAME));
+  }
+
   public static Module module() {
     return new CacheModule() {
       @Override
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 01db36f..2e2959c 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
@@ -441,13 +441,25 @@
       };
 
   /** Whether the change is mergeable. */
-  public static final FieldDef<ChangeData, String> MERGEABLE =
+  @Deprecated
+  public static final FieldDef<ChangeData, String> LEGACY_MERGEABLE =
       new FieldDef.Single<ChangeData, String>(
           ChangeQueryBuilder.FIELD_MERGEABLE, FieldType.EXACT, false) {
         @Override
         public String get(ChangeData input, FillArgs args)
             throws OrmException {
-          return input.change().isMergeable() ? "1" : null;
+          return input.isMergeable() ? "1" : null;
+        }
+      };
+
+  /** Whether the change is mergeable. */
+  public static final FieldDef<ChangeData, String> MERGEABLE =
+      new FieldDef.Single<ChangeData, String>(
+          "mergeable2", FieldType.EXACT, true) {
+        @Override
+        public String get(ChangeData input, FillArgs args)
+            throws OrmException {
+          return input.isMergeable() ? "1" : "0";
         }
       };
 
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 031e741..4b7850a 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
@@ -118,7 +118,7 @@
         ChangeField.COMMENT,
         ChangeField.CHANGE,
         ChangeField.APPROVAL,
-        ChangeField.MERGEABLE);
+        ChangeField.LEGACY_MERGEABLE);
 
   // For upgrade to Lucene 4.6.0 index format only.
   static final Schema<ChangeData> V6 = release(V5.getFields().values());
@@ -145,7 +145,7 @@
         ChangeField.COMMENT,
         ChangeField.CHANGE,
         ChangeField.APPROVAL,
-        ChangeField.MERGEABLE);
+        ChangeField.LEGACY_MERGEABLE);
 
   @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V8 = release(
@@ -168,7 +168,7 @@
         ChangeField.COMMENT,
         ChangeField.CHANGE,
         ChangeField.APPROVAL,
-        ChangeField.MERGEABLE);
+        ChangeField.LEGACY_MERGEABLE);
 
   @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V9 = release(
@@ -192,8 +192,9 @@
         ChangeField.COMMENT,
         ChangeField.CHANGE,
         ChangeField.APPROVAL,
-        ChangeField.MERGEABLE);
+        ChangeField.LEGACY_MERGEABLE);
 
+  @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V10 = release(
         ChangeField.LEGACY_ID,
         ChangeField.ID,
@@ -215,8 +216,9 @@
         ChangeField.COMMENT,
         ChangeField.CHANGE,
         ChangeField.APPROVAL,
-        ChangeField.MERGEABLE);
+        ChangeField.LEGACY_MERGEABLE);
 
+  @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V11 = release(
         ChangeField.LEGACY_ID,
         ChangeField.ID,
@@ -238,7 +240,7 @@
         ChangeField.COMMENT,
         ChangeField.CHANGE,
         ChangeField.APPROVAL,
-        ChangeField.MERGEABLE,
+        ChangeField.LEGACY_MERGEABLE,
         ChangeField.ADDED,
         ChangeField.DELETED,
         ChangeField.DELTA);
@@ -246,6 +248,7 @@
   // For upgrade to Lucene 4.10.0 index format only.
   static final Schema<ChangeData> V12 = release(V11.getFields().values());
 
+  @SuppressWarnings("deprecation")
   static final Schema<ChangeData> V13 = release(
       ChangeField.LEGACY_ID,
       ChangeField.ID,
@@ -267,6 +270,33 @@
       ChangeField.COMMENT,
       ChangeField.CHANGE,
       ChangeField.APPROVAL,
+      ChangeField.LEGACY_MERGEABLE,
+      ChangeField.ADDED,
+      ChangeField.DELETED,
+      ChangeField.DELTA,
+      ChangeField.HASHTAG);
+
+  static final Schema<ChangeData> V14 = 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,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
index 4c5ca27..87afc33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BasicChangeRewrites.java
@@ -26,11 +26,11 @@
 
 public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
   private static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
-      new ChangeQueryBuilder.Arguments( //
-          new InvalidProvider<ReviewDb>(), //
-          new InvalidProvider<ChangeQueryRewriter>(), //
-          null, null, null, null, null, null, null, //
-          null, null, null, null, null, null, null, null, null, null, null),
+      new ChangeQueryBuilder.Arguments(
+          new InvalidProvider<ReviewDb>(),
+          new InvalidProvider<ChangeQueryRewriter>(),
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null),
           null);
 
   private static final QueryRewriter.Definition<ChangeData, BasicChangeRewrites> mydef =
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 9707733..d45a58e 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
@@ -23,6 +23,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.SetMultimap;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -36,7 +37,9 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PatchLineCommentsUtil;
+import com.google.gerrit.server.change.MergeabilityCache;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.notedb.ReviewerState;
@@ -46,6 +49,8 @@
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.assistedinject.Assisted;
@@ -55,6 +60,7 @@
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.FooterLine;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -154,8 +160,8 @@
    * @return instance for testing.
    */
   static ChangeData createForTest(Change.Id id, int currentPatchSetId) {
-    ChangeData cd = new ChangeData(null, null, null, null, null,
-        null, null, null, null, null, id);
+    ChangeData cd = new ChangeData(null, null, null, null, null, null, null,
+        null, null, null, null, null, null, id);
     cd.currentPatchSet = new PatchSet(new PatchSet.Id(id, currentPatchSetId));
     return cd;
   }
@@ -164,12 +170,15 @@
   private final GitRepositoryManager repoManager;
   private final ChangeControl.GenericFactory changeControlFactory;
   private final IdentifiedUser.GenericFactory userFactory;
+  private final ProjectCache projectCache;
+  private final MergeUtil.Factory mergeUtilFactory;
   private final ChangeNotes.Factory notesFactory;
   private final ApprovalsUtil approvalsUtil;
   private final ChangeMessagesUtil cmUtil;
   private final PatchLineCommentsUtil plcUtil;
   private final PatchListCache patchListCache;
   private final NotesMigration notesMigration;
+  private final MergeabilityCache mergeabilityCache;
   private final Change.Id legacyId;
   private ChangeDataSource returnedBySource;
   private Change change;
@@ -187,30 +196,37 @@
   private List<ChangeMessage> messages;
   private List<SubmitRecord> submitRecords;
   private ChangedLines changedLines;
+  private Boolean mergeable;
 
   @AssistedInject
   private ChangeData(
       GitRepositoryManager repoManager,
       ChangeControl.GenericFactory changeControlFactory,
       IdentifiedUser.GenericFactory userFactory,
+      ProjectCache projectCache,
+      MergeUtil.Factory mergeUtilFactory,
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
       ChangeMessagesUtil cmUtil,
       PatchLineCommentsUtil plcUtil,
       PatchListCache patchListCache,
       NotesMigration notesMigration,
+      MergeabilityCache mergeabilityCache,
       @Assisted ReviewDb db,
       @Assisted Change.Id id) {
     this.db = db;
     this.repoManager = repoManager;
     this.changeControlFactory = changeControlFactory;
     this.userFactory = userFactory;
+    this.projectCache = projectCache;
+    this.mergeUtilFactory = mergeUtilFactory;
     this.notesFactory = notesFactory;
     this.approvalsUtil = approvalsUtil;
     this.cmUtil = cmUtil;
     this.plcUtil = plcUtil;
     this.patchListCache = patchListCache;
     this.notesMigration = notesMigration;
+    this.mergeabilityCache = mergeabilityCache;
     legacyId = id;
   }
 
@@ -219,24 +235,30 @@
       GitRepositoryManager repoManager,
       ChangeControl.GenericFactory changeControlFactory,
       IdentifiedUser.GenericFactory userFactory,
+      ProjectCache projectCache,
+      MergeUtil.Factory mergeUtilFactory,
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
       ChangeMessagesUtil cmUtil,
       PatchLineCommentsUtil plcUtil,
       PatchListCache patchListCache,
       NotesMigration notesMigration,
+      MergeabilityCache mergeabilityCache,
       @Assisted ReviewDb db,
       @Assisted Change c) {
     this.db = db;
     this.repoManager = repoManager;
     this.changeControlFactory = changeControlFactory;
     this.userFactory = userFactory;
+    this.projectCache = projectCache;
+    this.mergeUtilFactory = mergeUtilFactory;
     this.notesFactory = notesFactory;
     this.approvalsUtil = approvalsUtil;
     this.cmUtil = cmUtil;
     this.plcUtil = plcUtil;
     this.patchListCache = patchListCache;
     this.notesMigration = notesMigration;
+    this.mergeabilityCache = mergeabilityCache;
     legacyId = c.getId();
     change = c;
   }
@@ -246,24 +268,30 @@
       GitRepositoryManager repoManager,
       ChangeControl.GenericFactory changeControlFactory,
       IdentifiedUser.GenericFactory userFactory,
+      ProjectCache projectCache,
+      MergeUtil.Factory mergeUtilFactory,
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
       ChangeMessagesUtil cmUtil,
       PatchLineCommentsUtil plcUtil,
       PatchListCache patchListCache,
       NotesMigration notesMigration,
+      MergeabilityCache mergeabilityCache,
       @Assisted ReviewDb db,
       @Assisted ChangeControl c) {
     this.db = db;
     this.repoManager = repoManager;
     this.changeControlFactory = changeControlFactory;
     this.userFactory = userFactory;
+    this.projectCache = projectCache;
+    this.mergeUtilFactory = mergeUtilFactory;
     this.notesFactory = notesFactory;
     this.approvalsUtil = approvalsUtil;
     this.cmUtil = cmUtil;
     this.plcUtil = plcUtil;
     this.patchListCache = patchListCache;
     this.notesMigration = notesMigration;
+    this.mergeabilityCache = mergeabilityCache;
     legacyId = c.getChange().getId();
     change = c.getChange();
     changeControl = c;
@@ -555,6 +583,45 @@
     return submitRecords;
   }
 
+  public void setMergeable(boolean mergeable) {
+    this.mergeable = mergeable;
+  }
+
+  public boolean isMergeable() throws OrmException {
+    if (mergeable == null) {
+      Change c = change();
+      if (c.getStatus() == Change.Status.MERGED) {
+        mergeable = true;
+      } else {
+        PatchSet ps = currentPatchSet();
+        Repository repo = null;
+        try {
+          repo = repoManager.openRepository(c.getProject());
+          Ref ref = repo.getRef(c.getDest().get());
+          SubmitTypeRecord rec = new SubmitRuleEvaluator(this)
+              .getSubmitType();
+          if (rec.status != SubmitTypeRecord.Status.OK) {
+            throw new OrmException(
+                "Error in mergeability check: " + rec.errorMessage);
+          }
+          String mergeStrategy = mergeUtilFactory
+              .create(projectCache.get(c.getProject()))
+              .mergeStrategyName();
+          mergeable = mergeabilityCache.get(
+              ObjectId.fromString(ps.getRevision().get()),
+              ref, rec.type, mergeStrategy, c.getDest(), repo, db);
+        } catch (IOException e) {
+          throw new OrmException(e);
+        } finally {
+          if (repo != null) {
+            repo.close();
+          }
+        }
+      }
+    }
+    return mergeable;
+  }
+
   @Override
   public String toString() {
     return MoreObjects.toStringHelper(this).addValue(getId()).toString();
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 64ba2e9..5c39df8 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
@@ -37,6 +37,7 @@
 import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
 import com.google.gerrit.server.index.ChangeField;
 import com.google.gerrit.server.index.ChangeIndex;
+import com.google.gerrit.server.index.FieldDef;
 import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.patch.PatchListCache;
@@ -146,6 +147,7 @@
     final CapabilityControl.Factory capabilityControlFactory;
     final ChangeControl.GenericFactory changeControlGenericFactory;
     final ChangeData.Factory changeDataFactory;
+    final FieldDef.FillArgs fillArgs;
     final PatchLineCommentsUtil plcUtil;
     final AccountResolver accountResolver;
     final GroupBackend groupBackend;
@@ -169,6 +171,7 @@
         CapabilityControl.Factory capabilityControlFactory,
         ChangeControl.GenericFactory changeControlGenericFactory,
         ChangeData.Factory changeDataFactory,
+        FieldDef.FillArgs fillArgs,
         PatchLineCommentsUtil plcUtil,
         AccountResolver accountResolver,
         GroupBackend groupBackend,
@@ -189,6 +192,7 @@
       this.capabilityControlFactory = capabilityControlFactory;
       this.changeControlGenericFactory = changeControlGenericFactory;
       this.changeDataFactory = changeDataFactory;
+      this.fillArgs = fillArgs;
       this.plcUtil = plcUtil;
       this.accountResolver = accountResolver;
       this.groupBackend = groupBackend;
@@ -326,7 +330,7 @@
     }
 
     if ("mergeable".equalsIgnoreCase(value)) {
-      return new IsMergeablePredicate();
+      return new IsMergeablePredicate(schema(args.indexes), args.fillArgs);
     }
 
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java
index 787b90e..6ef4ab6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergeablePredicate.java
@@ -14,20 +14,39 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.reviewdb.client.Change;
+import static com.google.gerrit.server.index.ChangeField.MERGEABLE;
+
 import com.google.gerrit.server.index.ChangeField;
+import com.google.gerrit.server.index.FieldDef;
+import com.google.gerrit.server.index.FieldDef.FillArgs;
 import com.google.gerrit.server.index.IndexPredicate;
+import com.google.gerrit.server.index.Schema;
 import com.google.gwtorm.server.OrmException;
 
 class IsMergeablePredicate extends IndexPredicate<ChangeData> {
-  IsMergeablePredicate() {
-    super(ChangeField.MERGEABLE, "1");
+  @SuppressWarnings("deprecation")
+  static FieldDef<ChangeData, ?> mergeableField(Schema<ChangeData> schema) {
+    if (schema == null) {
+      return ChangeField.LEGACY_MERGEABLE;
+    }
+    FieldDef<ChangeData, ?> f = schema.getFields().get(MERGEABLE.getName());
+    if (f != null) {
+      return f;
+    }
+    return schema.getFields().get(ChangeField.LEGACY_MERGEABLE.getName());
+  }
+
+  private final FillArgs args;
+
+  IsMergeablePredicate(Schema<ChangeData> schema,
+      FillArgs args) {
+    super(mergeableField(schema), "1");
+    this.args = args;
   }
 
   @Override
   public boolean match(ChangeData object) throws OrmException {
-    Change c = object.change();
-    return c != null && c.isMergeable();
+    return getValue().equals(getField().get(object, args));
   }
 
   @Override
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
index 7090f0d..c6425f5 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/FakeQueryBuilder.java
@@ -26,8 +26,8 @@
         new FakeQueryBuilder.Definition<>(
           FakeQueryBuilder.class),
         new ChangeQueryBuilder.Arguments(null, null, null, null, null, null,
-          null, null, null, null, null, null, null, null, null, indexes, null,
-          null, null, null),
+          null, null, null, null, null, null, null, null, null, null, indexes,
+          null, null, null, null),
         null);
   }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
index a44666b..9e58bea 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
@@ -40,6 +40,7 @@
 import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.gerrit.server.change.ChangeKindCache;
 import com.google.gerrit.server.change.ChangeKindCacheImpl;
+import com.google.gerrit.server.change.MergeabilityCache;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
@@ -50,6 +51,7 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.patch.PatchListCache;
@@ -58,6 +60,7 @@
 import com.google.gerrit.testutil.InMemoryRepositoryManager;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+import com.google.inject.Provider;
 import com.google.inject.util.Providers;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -253,18 +256,20 @@
     };
 
     Injector injector = Guice.createInjector(new FactoryModule() {
+      @SuppressWarnings({"rawtypes", "unchecked"})
       @Override
       protected void configure() {
+        Provider nullProvider = Providers.of(null);
         bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(
             new Config());
-        bind(ReviewDb.class).toProvider(Providers.<ReviewDb> of(null));
+        bind(ReviewDb.class).toProvider(nullProvider);
         bind(GitRepositoryManager.class).toInstance(repoManager);
-        bind(PatchListCache.class)
-            .toProvider(Providers.<PatchListCache> of(null));
+        bind(PatchListCache.class).toProvider(nullProvider);
 
         factory(CapabilityControl.Factory.class);
         factory(ChangeControl.AssistedFactory.class);
         factory(ChangeData.Factory.class);
+        factory(MergeUtil.Factory.class);
         bind(ProjectCache.class).toInstance(projectCache);
         bind(AccountCache.class).toInstance(new FakeAccountCache());
         bind(GroupBackend.class).to(SystemGroupBackend.class);
@@ -275,6 +280,7 @@
         bind(String.class).annotatedWith(AnonymousCowardName.class)
             .toProvider(AnonymousCowardNameProvider.class);
         bind(ChangeKindCache.class).to(ChangeKindCacheImpl.NoCache.class);
+        bind(MergeabilityCache.bindingKey()).toProvider(nullProvider);
       }
     });
 
diff --git a/lib/asciidoctor/java/DocIndexer.java b/lib/asciidoctor/java/DocIndexer.java
index 4736b0b..27bf30a 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_4_10_0;
+  private static final Version LUCENE_VERSION = Version.LUCENE_4_10_1;
   private static final Pattern SECTION_HEADER = Pattern.compile("^=+ (.*)");
 
   @Option(name = "-o", usage = "output JAR file")
diff --git a/lib/lucene/BUCK b/lib/lucene/BUCK
index dd148b1..df14528 100644
--- a/lib/lucene/BUCK
+++ b/lib/lucene/BUCK
@@ -1,11 +1,11 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '4.10.0'
+VERSION = '4.10.1'
 
 maven_jar(
   name = 'core',
   id = 'org.apache.lucene:lucene-core:' + VERSION,
-  sha1 = 'a4ceea9a80e81fe84e81fe4fccce9e9930dc703a',
+  sha1 = '4ff28101d9de465b7f3cf59d7bc2892c1c118b4b',
   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 = '912962d436d9851dc90091e48251c802d3b65941',
+  sha1 = '6491c6019c32e7c4f7674f238d5beaa84d3108a6',
   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 = '7a00eb4b97a6cb7a5a29957b62c7f002dd71b7ff',
+  sha1 = '0174ffd89d5289037ae24759f38111285b98636d',
   license = 'Apache2.0',
 )